import { getAuthToken } from "./auth-token";

/*
|--------------------------------------------------------------------------
| Shared Request Helper
|--------------------------------------------------------------------------
|
| Same shape as app/lib/wallet.ts's walletFetch — one consistent
| {success, data, message} envelope. Confirmed live (Consultation API
| Handbook): /user/consultation/* responses are
| {status, statusCode, message, data, errors} — `status` is the boolean
| success flag here, distinct from the astrologer family's envelope and
| from /user/wallet's numeric-status envelope. Don't assume this shape
| generalizes to other /user/* endpoints.
|
*/

async function consultationFetch(url: string, init: RequestInit) {
  const token = getAuthToken();

  if (!token) {
    return {
      authenticated: false,
      success: false,
      data: null,
      message: "Please login first.",
    };
  }

  try {
    const response = await fetch(url, {
      ...init,

      headers: {
        ...(init.headers || {}),
        Authorization: `Bearer ${token}`,
      },

      cache: "no-store",
    });

    const result = await response.json();

    if (response.status === 401) {
      return {
        authenticated: false,
        success: false,
        data: null,
        message: result?.message || "Authentication expired.",
      };
    }

    if (!response.ok || result?.status === false) {
      return {
        authenticated: true,
        success: false,
        data: null,
        message: result?.message || "Consultation request failed.",
      };
    }

    return {
      authenticated: true,
      success: true,
      data: result.data ?? result,
      message: result.message,
    };
  } catch (error) {
    console.error("Consultation request error:", error);

    return {
      authenticated: true,
      success: false,
      data: null,
      message: "Unable to connect to the consultation API.",
    };
  }
}

export type ConsultationMedium = "CHAT" | "AUDIO" | "VIDEO";

/*
|--------------------------------------------------------------------------
| Precheck
|--------------------------------------------------------------------------
|
| Confirmed live: the only place free-chat entitlement, required
| balance and live availability are resolved together — do not cache
| it, balance and online state both move. blocked_reason is one of
| ASTROLOGER_OFFLINE, ASTROLOGER_BUSY, INSUFFICIENT_BALANCE,
| MEDIUM_DISABLED, NO_RATE_CONFIGURED, SESSION_ALREADY_OPEN,
| SERVICE_NOT_CONFIGURED.
|
*/

export async function precheckConsultation(astrologerId: number, medium: ConsultationMedium) {
  const params = new URLSearchParams({
    astrologer_id: String(astrologerId),
    medium,
  });

  return consultationFetch(`/api/user/consultation/precheck?${params.toString()}`, {
    method: "GET",
  });
}

/*
|--------------------------------------------------------------------------
| Start (ring the astrologer) — NEW MEANING, confirmed live
|--------------------------------------------------------------------------
|
| Nothing is billed, no channel is joined, no token is minted. Returns
| status: "REQUESTED". channel_name is no longer accepted in the body —
| the server owns it now; don't send one.
|
*/

export interface StartConsultationPayload {
  astrologer_id: number;
  medium: ConsultationMedium;
  name?: string;
  birth_date?: string;
  birth_time?: string;
  birth_place?: string;
  gender?: string;
  // Confirmed live these are NOT round-tripped back to the astrologer
  // anywhere (ring-queue item, accept response, active response all
  // came back empty for a real test session) — still sent here so the
  // backend has them if it ever starts using them, but the astrologer
  // side actually learns the customer's details from the real
  // auto-sent chat message below, not from any of these API responses.
  marital_status?: string;
  topic?: string;
}

/*
|--------------------------------------------------------------------------
| Consultee auto-details message — sent as a REAL chat message by the
| customer's own session the moment chat starts (sender_type "USER",
| the customer's own real role — no cross-role sender_type risk),
| since no astrologer-facing API endpoint has been confirmed to return
| the customer's birth details/gender/marital-status/topic at all.
| build/parse are symmetric: the customer composes it from what they
| just typed into StartConsultationModal, the astrologer parses it back
| out of the real message body (see the astrologer page's onRtmMessage
| handler and its loadPriorMessages() fallback for history reloads).
|--------------------------------------------------------------------------
*/

export interface ConsulteeAutoDetails {
  name?: string;
  gender?: string;
  birthDate?: string;
  birthTime?: string;
  birthPlace?: string;
  maritalStatus?: string;
  topic?: string;
}

const AUTO_DETAILS_LABELS: { key: keyof ConsulteeAutoDetails; label: string }[] = [
  { key: "name", label: "Name" },
  { key: "gender", label: "Gender" },
  { key: "birthDate", label: "DOB" },
  { key: "birthTime", label: "TOB" },
  { key: "birthPlace", label: "POB" },
  { key: "maritalStatus", label: "Marital status" },
  { key: "topic", label: "TOPIC" },
];

// Per explicit request: this message is meant to be read by the
// astrologer only — the customer's own chat window should never show
// it (even though it's a real synced message under their own
// sender_type, for the reasons explained above). The customer's page
// uses this to filter it out of both the live send-echo and any
// history reload, while the astrologer's page uses the exact same
// marker text as the anchor parseAutoDetailsMessage() searches from.
export function isAutoDetailsMessage(body: string): boolean {
  return (
    typeof body === "string" &&
    body.includes("My details:") &&
    body.includes("This is an automated message to confirm that chat has started")
  );
}

export function buildAutoDetailsMessage(
  details: ConsulteeAutoDetails,
  astrologerFirstName: string,
): string {
  const parts = AUTO_DETAILS_LABELS.filter(({ key }) => !!details[key]).map(
    ({ key, label }) => `${label}: ${details[key]}`,
  );

  if (parts.length === 0) return "";

  const greeting = astrologerFirstName ? `Hi ${astrologerFirstName}, ` : "";
  return `${greeting}My details: ${parts.join(", ")}. This is an automated message to confirm that chat has started.`;
}

// POB values routinely contain commas themselves ("Jaipur, Rajasthan,
// India"), so this can't just split on ", " — it looks for where the
// NEXT known label starts to know where the current value ends.
export function parseAutoDetailsMessage(body: string): ConsulteeAutoDetails {
  const result: ConsulteeAutoDetails = {};
  const marker = "My details:";
  const markerIdx = body.indexOf(marker);
  if (markerIdx === -1) return result;

  const section = body.slice(markerIdx + marker.length);

  AUTO_DETAILS_LABELS.forEach(({ key, label }, index) => {
    const startMarker = `${label}:`;
    const startIdx = section.indexOf(startMarker);
    if (startIdx === -1) return;

    const valueStart = startIdx + startMarker.length;
    let endIdx = section.length;

    for (let j = index + 1; j < AUTO_DETAILS_LABELS.length; j++) {
      const nextIdx = section.indexOf(`, ${AUTO_DETAILS_LABELS[j].label}:`, valueStart);
      if (nextIdx !== -1) {
        endIdx = nextIdx;
        break;
      }
    }

    const trailingIdx = section.indexOf(". This is an automated", valueStart);
    if (trailingIdx !== -1 && trailingIdx < endIdx) endIdx = trailingIdx;

    const value = section.slice(valueStart, endIdx).replace(/,\s*$/, "").trim();
    if (value) result[key] = value;
  });

  return result;
}

// What the customer actually typed into StartConsultationModal for
// THIS consultation — that form's own state is discarded the instant
// the modal closes, and nothing about it survives in any later API
// response on the customer's own page either, so it's cached here at
// submit time purely to compose the auto-details message once chat
// starts.
export function cacheSubmittedConsulteeDetails(
  consultationId: number,
  details: ConsulteeAutoDetails,
) {
  if (typeof window === "undefined") return;
  try {
    sessionStorage.setItem(
      `consultation_${consultationId}_submitted_details`,
      JSON.stringify(details),
    );
  } catch {
    // Best-effort — the auto-message just omits whatever's missing.
  }
}

export function getCachedSubmittedConsulteeDetails(consultationId: number): ConsulteeAutoDetails {
  if (typeof window === "undefined") return {};
  try {
    const raw = sessionStorage.getItem(`consultation_${consultationId}_submitted_details`);
    return raw ? JSON.parse(raw) : {};
  } catch {
    return {};
  }
}

export async function startConsultation(payload: StartConsultationPayload) {
  return consultationFetch("/api/user/consultation/start", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });
}

/*
|--------------------------------------------------------------------------
| Status — poll every 3s while REQUESTED, stop the moment it isn't
|--------------------------------------------------------------------------
*/

export async function getConsultationStatus(consultationId: number) {
  return consultationFetch(
    `/api/user/consultation/status?consultation_id=${consultationId}`,
    { method: "GET" }
  );
}

/*
|--------------------------------------------------------------------------
| Start chat — the customer confirms, billing begins, Agora creds mint
|--------------------------------------------------------------------------
*/

export async function startChatConsultation(consultationId: number) {
  return consultationFetch("/api/user/consultation/start-chat", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ consultation_id: consultationId }),
  });
}

/*
|--------------------------------------------------------------------------
| Cancel — customer cancels while still REQUESTED
|--------------------------------------------------------------------------
*/

export async function cancelConsultation(consultationId: number) {
  return consultationFetch("/api/user/consultation/cancel", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ consultation_id: consultationId }),
  });
}

/*
|--------------------------------------------------------------------------
| Tick — poll at tick_interval_seconds (30s), never at
| tick_timeout_seconds (240 chat / 120 call) — confirmed live, these
| are separate fields with a 4x ratio and polling at the timeout gets
| the session swept as abandoned and billed for it.
|--------------------------------------------------------------------------
*/

export async function tickConsultation(consultationId: number) {
  return consultationFetch("/api/user/consultation/tick", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ consultation_id: consultationId }),
  });
}

/*
|--------------------------------------------------------------------------
| Continue past free chat / extend after a recharge
|--------------------------------------------------------------------------
*/

export async function continueConsultationPaid(consultationId: number) {
  return consultationFetch("/api/user/consultation/continue-paid", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ consultation_id: consultationId }),
  });
}

export async function extendConsultation(consultationId: number) {
  return consultationFetch("/api/user/consultation/extend", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ consultation_id: consultationId }),
  });
}

/*
|--------------------------------------------------------------------------
| Messages sync — Agora RTM stores nothing; this is the only durable
| record of a conversation. client_message_id must be generated once
| by the SENDING device and carried inside the RTM payload so the
| receiver uploads the SAME id (see app/lib/agora.ts).
|--------------------------------------------------------------------------
*/

export interface ConsultationMessage {
  client_message_id: string;
  sender_type: "USER" | "ASTROLOGER";
  message_type: "TEXT";
  body: string;
  sent_at: string;
  sequence: number;
}

export async function syncConsultationMessages(
  consultationId: number,
  messages: ConsultationMessage[],
  isFinal = false,
  // `keepalive` lets the request survive a page unload (tab close,
  // refresh, navigation away) — used by the beforeunload/pagehide
  // flush below so messages typed just before a refresh (still batched
  // client-side, not yet hit the 30s timer or the 25-message batch
  // size) aren't silently lost. A plain fetch gets aborted mid-flight
  // the moment the page unloads; keepalive: true is the browser's
  // sanctioned way around that (same mechanism sendBeacon uses, but
  // sendBeacon can't carry a custom Authorization header).
  keepalive = false
) {
  return consultationFetch("/api/user/consultation/messages/sync", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    keepalive,
    body: JSON.stringify({
      consultation_id: consultationId,
      is_final: isFinal,
      messages,
    }),
  });
}

/*
|--------------------------------------------------------------------------
| End Consultation
|--------------------------------------------------------------------------
|
| Idempotent — calling it twice returns the first receipt rather than
| billing again. Only USER_ENDED, ASTROLOGER_ENDED and
| INSUFFICIENT_BALANCE are accepted from a client.
|
*/

export type EndConsultationReason = "USER_ENDED" | "ASTROLOGER_ENDED" | "INSUFFICIENT_BALANCE";

export async function endConsultation(
  consultationId: number,
  endReason: EndConsultationReason = "USER_ENDED"
) {
  return consultationFetch("/api/user/consultation/end", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      consultation_id: consultationId,
      end_reason: endReason,
      client_duration_seconds: 0,
    }),
  });
}

/*
|--------------------------------------------------------------------------
| Active Consultation — recovery, call before rendering anything
|--------------------------------------------------------------------------
|
| Confirmed live: this endpoint can end and bill the session as a side
| effect if the last tick is older than tick_timeout_seconds — call it
| on resume only, never on a timer while a session is live, and never
| wire it to pull-to-refresh.
|
*/

export async function getActiveConsultation() {
  return consultationFetch("/api/user/consultation/active", { method: "GET" });
}

/*
|--------------------------------------------------------------------------
| Fresh Agora credentials (token refresh)
|--------------------------------------------------------------------------
*/

export async function getConsultationAgoraToken(consultationId: number) {
  return consultationFetch("/api/user/consultation/agora-token", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ consultation_id: consultationId }),
  });
}

/*
|--------------------------------------------------------------------------
| Messages — the durable transcript messages/sync wrote. A plain GET
| with query params (not the POST the handbook's endpoint list implied
| by grouping it with agora-token/cancel/history).
|--------------------------------------------------------------------------
*/

export interface ConsultationChatMessage {
  id: number;
  client_message_id: string;
  sender_type: "USER" | "ASTROLOGER";
  sender_id: number;
  message_type: "TEXT";
  body: string;
  sent_at: string;
}

export async function getConsultationMessages(
  consultationId: number,
  page = 1,
  limit = 50
) {
  const query = new URLSearchParams({
    consultation_id: String(consultationId),
    page: String(page),
    limit: String(limit),
  });

  return consultationFetch(`/api/user/consultation/messages?${query.toString()}`, {
    method: "GET",
  });
}

/*
|--------------------------------------------------------------------------
| History — internal lookup only, NOT the removed customer-facing
| history page/feature (deliberately deleted earlier per explicit
| request — see CLAUDE.md, don't rebuild that page from this).
|--------------------------------------------------------------------------
|
| Re-added narrowly because /wallet/transaction/[id]'s chat-transcript
| section needs it: the wallet drill-down endpoint's own `id` is the
| WALLET transaction id, not the numeric consultation_id the messages
| endpoint requires — confirmed by testing live, the transcript never
| loaded when the two were assumed to be the same value. This list is
| the only place a numeric consultation_id is exposed at all; the
| wallet drill-down only ever exposes the consultation_no (as
| `transactionNumber`), so resolving it means fetching this list and
| matching by consultation_no client-side, the same "no per-id
| endpoint" pattern used elsewhere in this app (settlement-history/[id],
| wallet/transaction/[id] itself). Confirmed live (verbose curl): this
| really is a POST reading its parameters from the query string with an
| empty body — a plain GET 404s at the Apache level. status can be
| REQUESTED/ACCEPTED/ONGOING/COMPLETED/REJECTED/CANCELLED/MISSED.
|--------------------------------------------------------------------------
*/

export interface ConsultationHistoryItem {
  consultation_id: number;
  consultation_no: string;
  astrologer_id: number;
  astrologer_name: string;
  astrologer_image: string;
  medium: ConsultationMedium;
  status: string;
  duration: string;
  billed_minutes: number;
  rate_per_minute: number;
  deducted_amount: number;
  consultee_name: string;
  date: string;
  [key: string]: any;
}

export async function getConsultationHistory(
  params: { medium?: ConsultationMedium; page?: number; limit?: number } = {}
) {
  const query = new URLSearchParams();

  if (params.medium) query.set("medium", params.medium);
  query.set("page", String(params.page ?? 1));
  query.set("limit", String(params.limit ?? 100));

  return consultationFetch(`/api/user/consultation/history?${query.toString()}`, {
    method: "POST",
  });
}

/*
|--------------------------------------------------------------------------
| Waiting queue (3 Oct 2026 API) — join when the astrologer is busy
|--------------------------------------------------------------------------
|
| precheck/start report ASTROLOGER_BUSY or QUEUE_RESERVED -> join the
| queue instead of dead-ending. WAITING -> NOTIFIED (our turn, tap
| Connect) -> CONNECTING (place the request via the existing /start
| before connect_expires_at) -> CONNECTED/COMPLETED. WAITING, NOTIFIED
| and CONNECTING hold a place; every other status is final.
|
| Drive the UI off `next_action`, never off a guess from `status`.
| Poll queue/status every `poll_interval_seconds` (5) — that poll is
| also what moves the server's countdowns/timeouts along.
|
*/

export type QueueStatus =
  | "WAITING"
  | "NOTIFIED"
  | "CONNECTING"
  | "CONNECTED"
  | "EXPIRED"
  | "CANCELLED"
  | "SKIPPED"
  | "REJECTED"
  | "COMPLETED";

export type QueueNextAction = "WAIT" | "CONNECT" | "START_CONSULTATION" | "NONE";

export interface QueueEntry {
  queue_id: number;
  astrologer_id: number;
  astrologer_name?: string;
  astrologer_image?: string;
  waiting_type?: string;
  medium?: string;
  status: QueueStatus;
  position?: number;
  total_in_queue?: number;
  requested_at?: string;
  wait_expires_at?: string;
  notified_at?: string;
  notify_expires_at?: string;
  connecting_at?: string;
  connect_expires_at?: string;
  consultation_id?: number | null;
  seconds_remaining?: number;
  next_action?: QueueNextAction;
  end_reason?: string;
  message?: string;
  server_time?: string;
  poll_interval_seconds?: number;
}

const QUEUE_HOLDING_STATUSES: QueueStatus[] = ["WAITING", "NOTIFIED", "CONNECTING"];

export function isQueueHoldingPlace(status?: string): boolean {
  return QUEUE_HOLDING_STATUSES.includes(status as QueueStatus);
}

export async function joinConsultationQueue(astrologerId: number, medium: ConsultationMedium) {
  return consultationFetch("/api/user/consultation/queue/join", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ astrologer_id: astrologerId, medium }),
  });
}

// With a queue_id: that one entry, whatever its status (final ones
// included — data.list has a single row). Without: every active entry.
export async function getConsultationQueueStatus(queueId?: number) {
  const query = queueId ? `?queue_id=${queueId}` : "";

  return consultationFetch(`/api/user/consultation/queue/status${query}`, {
    method: "GET",
  });
}

export async function connectConsultationQueue(queueId: number) {
  return consultationFetch("/api/user/consultation/queue/connect", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ queue_id: queueId }),
  });
}

export async function cancelConsultationQueue(queueId: number) {
  return consultationFetch("/api/user/consultation/queue/cancel", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ queue_id: queueId }),
  });
}

// Pulls the entry out of either response shape: status returns
// {list:[...]}, join/connect/cancel return the entry directly.
export function extractQueueEntry(data: any): QueueEntry | null {
  if (!data) return null;
  if (Array.isArray(data.list)) return data.list[0] ?? null;
  if (data.queue_id) return data as QueueEntry;
  return null;
}

/*
| The consultee form is collected once, up front, when the customer
| joins — a queue turn can arrive minutes later (even from a push on
| another tab), so the /start call that follows Connect must not need
| a second form. Cached per queue entry; the queue page falls back to
| the profile prefill if it's missing (e.g. deep-linked from a push).
*/

export type QueueConsulteeDetails = Omit<StartConsultationPayload, "astrologer_id" | "medium">;

export function cacheQueueConsulteeDetails(queueId: number, details: QueueConsulteeDetails) {
  if (typeof window === "undefined") return;
  try {
    sessionStorage.setItem(`queue_${queueId}_details`, JSON.stringify(details));
  } catch {
    // non-fatal
  }
}

export function getCachedQueueConsulteeDetails(queueId: number): QueueConsulteeDetails | null {
  if (typeof window === "undefined") return null;
  try {
    const raw = sessionStorage.getItem(`queue_${queueId}_details`);
    return raw ? JSON.parse(raw) : null;
  } catch {
    return null;
  }
}
