import api from "./api";
import type { ConsultationMessage } from "./consultation";
import { getDeviceAuthInfo } from "./deviceInfo";

/*
* Confirmed live against the real backend: the astrologer API family
* uses a boolean `status` field for success (e.g.
* `{"status":true,"message":"...","data":{...}}`), NOT `success` like
* the end-user API. Every helper below normalizes the response so
* every caller can just check `response.success` regardless of which
* field the backend actually used for a given endpoint.
*/
function normalize(data: any) {
  if (data && typeof data === "object" && data.success === undefined) {
    return { ...data, success: data.status };
  }

  return data;
}

/*
* A handful of astrologer endpoints pass a raw MySQL/Go SQL driver
* error straight through as `message` when a backend-side schema/data
* issue trips — confirmed twice now: profile/education's "Error 1452
* (23000): ... foreign key constraint fails ..." and the reviews
* endpoint's "sql: Scan error on column index 7, name \"experience\":
* converting driver.Value type []uint8 (\"2.0\") to a int" (a stored
* decimal-string being scanned into an int column). Never show either
* to an end user — log it for debugging and surface a generic message
* instead.
*/
function looksLikeRawDbError(message: string | undefined): boolean {
  return (
    !!message &&
    /error \d+ \(\d+\)|sqlstate|foreign key constraint|constraint `|^sql: /i.test(
      message
    )
  );
}

export function friendlyErrorMessage(
  message: string | undefined,
  fallback = "Something went wrong. Please try again."
): string {
  if (!message) return fallback;

  if (looksLikeRawDbError(message)) {
    // console.warn (not console.error) deliberately — this is already
    // handled gracefully (friendly toast + upstream retry), so it
    // shouldn't trigger Next.js's intrusive full-screen dev error
    // overlay. Still logged for debugging.
    console.warn("Astrologer API returned a raw backend error:", message);
    return "We hit a problem on our end. Please try again in a moment, or contact support if it keeps happening.";
  }

  return message;
}

/*
* Confirmed live: every boolean toggle field on the dashboard endpoints
* (online-status, chat-status, call-status, busy-status, quick-dnd) is
* tagged `binding:"required"` on the Go backend, and go-playground's
* validator treats `false` as an unset zero value for a plain bool —
* so switching one of these OFF always 400s with a message like
* "Field validation for 'IsOnline' failed on the 'required' tag", while
* switching it ON works fine. This is a backend validation bug, not
* something fixable here — callers should revert the toggle and show a
* clear message instead of the raw Go error text.
*/
export function isRequiredToggleOffError(message: string | undefined): boolean {
  return (
    !!message &&
    /field validation for '\w+' failed on the 'required' tag/i.test(message)
  );
}

/*
* Confirmed live: profile/education can transiently fail with a MySQL
* foreign-key error ("astrologer_educations references users(id)")
* immediately after a fresh registration — it succeeds moments later
* with the exact same payload, pointing to backend replication lag
* right after account creation rather than a real data problem.
* Retry a couple of times with a short delay before giving up, so a
* real user doesn't hit a dead end for a timing issue outside their
* control.
*/
async function withDbRaceRetry<T extends { success?: boolean; message?: string }>(
  fn: () => Promise<T>,
  attempts = 3,
  delayMs = 1000
): Promise<T> {
  let result = await fn();

  for (
    let attempt = 1;
    attempt < attempts && !result.success && looksLikeRawDbError(result.message);
    attempt++
  ) {
    await new Promise((resolve) => setTimeout(resolve, delayMs));
    result = await fn();
  }

  return result;
}

/*
|--------------------------------------------------------------------------
| Astrologer Auth
|--------------------------------------------------------------------------
*/

export interface RegisterAstrologerPayload {
  name: string;
  email: string;
  mobile: string;
  country_code: string;
  password: string;
  device_type?: string;
  device_token?: string;
  referral_code?: string;
}

export const registerAstrologer = async (
  payload: RegisterAstrologerPayload
) => {
  const { data } = await api.post("/astrologer/register", payload);
  return normalize(data);
};

export const verifyAstrologerMobileOtp = async (
  mobile: string,
  otp: string
) => {
  const { data } = await api.post("/astrologer/verify-mobile", {
    mobile,
    otp,
  });
  return normalize(data);
};

export const verifyAstrologerEmailOtp = async (
  email: string,
  otp: string
) => {
  const { data } = await api.post("/astrologer/verify-email", {
    email,
    otp,
    // ip_address/device_name/platform match the format confirmed via
    // a real Postman example — resolved client-side (see
    // app/lib/deviceInfo.ts) since that works consistently in both
    // local dev and production, unlike relying on the proxy route's
    // server-side X-Forwarded-For override alone.
    ...(await getDeviceAuthInfo()),
  });
  return normalize(data);
};

export const astrologerLogin = async (
  mobile: string,
  countryCode = "+91"
) => {
  // Confirmed live: this call happens before OTP verification, so
  // there's no astrologer session yet to attach a real FCM token to —
  // dropped the placeholder device_type/device_token that used to be
  // sent here. The real device-token sync happens post-login via
  // updateAstrologerDeviceToken() once verifyAstrologerLoginOtp()
  // returns a session token.
  const { data } = await api.post("/astrologer/login", {
    mobile,
    country_code: countryCode,
  });
  return normalize(data);
};

export const verifyAstrologerLoginOtp = async (
  mobile: string,
  otp: string
) => {
  const { data } = await api.post("/astrologer/verify-login-otp", {
    mobile,
    otp,
    type: "LOGIN",
    // ip_address/device_name/platform match the format confirmed via
    // a real Postman example — resolved client-side (see
    // app/lib/deviceInfo.ts) since that works consistently in both
    // local dev and production, unlike relying on the proxy route's
    // server-side X-Forwarded-For override alone.
    ...(await getDeviceAuthInfo()),
  });
  return normalize(data);
};

export const updateAstrologerDeviceToken = async (
  deviceToken: string,
  token: string,
  deviceType = "web"
) => {
  const { data } = await api.post(
    "/astrologer/device-token",
    { device_type: deviceType, device_token: deviceToken },
    { headers: { Authorization: token } }
  );
  return normalize(data);
};

export const resendAstrologerOtp = async (
  mobile: string,
  type: "mobile" | "email" = "mobile"
) => {
  const { data } = await api.post("/astrologer/resend-otp", {
    mobile,
    type,
  });
  return normalize(data);
};

export const astrologerLogout = async (token: string) => {
  const { data } = await api.post(
    "/astrologer/logout",
    {},
    { headers: { Authorization: token } }
  );
  return normalize(data);
};

/*
|--------------------------------------------------------------------------
| Profile Progress
|--------------------------------------------------------------------------
*/

export const getProfileProgress = async (token: string) => {
  const { data } = await api.get("/astrologer/profile/progress", {
    headers: { Authorization: token },
  });
  return normalize(data);
};

export const submitAstrologerProfile = async (token: string) => {
  const { data } = await api.post(
    "/astrologer/profile/submit",
    {},
    { headers: { Authorization: token } }
  );
  return normalize(data);
};

/*
|--------------------------------------------------------------------------
| Step 1 — Basic Details
|--------------------------------------------------------------------------
*/

export const saveBasicProfile = async (payload: any, token: string) => {
  const { data } = await api.post("/astrologer/profile/basic", payload, {
    headers: { Authorization: token },
  });
  return normalize(data);
};

/*
|--------------------------------------------------------------------------
| Step 2 — Languages
|--------------------------------------------------------------------------
*/

export const getMasterLanguages = async (token: string) => {
  const { data } = await api.get("/astrologer/master/languages", {
    headers: { Authorization: token },
  });
  return normalize(data);
};

export const saveLanguages = async (
  languageIds: number[],
  token: string
) => {
  const { data } = await api.post(
    "/astrologer/profile/languages",
    { language_ids: languageIds },
    { headers: { Authorization: token } }
  );
  return normalize(data);
};

export const updateLanguages = async (
  languageIds: number[],
  token: string
) => {
  const { data } = await api.put(
    "/astrologer/update-languages",
    { language_ids: languageIds },
    { headers: { Authorization: token } }
  );
  return normalize(data);
};

/*
|--------------------------------------------------------------------------
| Step 3 — Skills
|--------------------------------------------------------------------------
*/

export const getMasterSkills = async (token: string) => {
  const { data } = await api.get("/astrologer/master/skills", {
    headers: { Authorization: token },
  });
  return normalize(data);
};

export interface AstrologerSkillInput {
  skill_id: number;
  experience: number;
  is_primary: boolean;
}

export const saveSkills = async (
  skills: AstrologerSkillInput[],
  token: string
) => {
  const { data } = await api.post(
    "/astrologer/profile/skills",
    { skills },
    { headers: { Authorization: token } }
  );
  return normalize(data);
};

export const updateSkills = async (
  skillIds: number[],
  token: string
) => {
  const { data } = await api.put(
    "/astrologer/update-skills",
    { skill_ids: skillIds },
    { headers: { Authorization: token } }
  );
  return normalize(data);
};

/*
|--------------------------------------------------------------------------
| Step 4 — Profile Image
|--------------------------------------------------------------------------
*/

export const uploadAstrologerProfileImage = async (
  file: File,
  token: string
) => {
  const formData = new FormData();

  formData.append("profile_image", file);

  // Deliberately plain fetch, not the shared `api` axios instance —
  // it forces "Content-Type: application/json", which stomps the
  // multipart boundary axios/fetch would otherwise set for FormData.
  const response = await fetch("/api/astrologer/profile/upload-profile-image", {
    method: "POST",
    headers: { Authorization: token },
    body: formData,
  });

  return normalize(await response.json());
};

export const updateAstrologerProfileImage = async (
  file: File,
  token: string
) => {
  const formData = new FormData();

  formData.append("profile_image", file);

  const response = await fetch("/api/astrologer/profile/update-image", {
    method: "PUT",
    headers: { Authorization: token },
    body: formData,
  });

  return normalize(await response.json());
};

export const deleteAstrologerProfileImage = async (token: string) => {
  const response = await fetch("/api/astrologer/profile/delete-image", {
    method: "DELETE",
    headers: { Authorization: token },
  });

  return normalize(await response.json());
};

/*
|--------------------------------------------------------------------------
| Step 5 — Education
|--------------------------------------------------------------------------
*/

async function postEducationOnce(payload: any, token: string) {
  try {
    const { data } = await api.post("/astrologer/profile/education", payload, {
      headers: { Authorization: token },
    });
    return normalize(data);
  } catch (error: any) {
    return normalize(error.response?.data) ?? { success: false };
  }
}

async function putEducationOnce(payload: any, token: string) {
  try {
    const { data } = await api.put("/astrologer/update-education", payload, {
      headers: { Authorization: token },
    });
    return normalize(data);
  } catch (error: any) {
    return normalize(error.response?.data) ?? { success: false };
  }
}

export const saveEducation = async (payload: any, token: string) =>
  withDbRaceRetry(() => postEducationOnce(payload, token));

export const updateEducation = async (payload: any, token: string) =>
  withDbRaceRetry(() => putEducationOnce(payload, token));

/*
|--------------------------------------------------------------------------
| Step 6 — Professional
|--------------------------------------------------------------------------
*/

export const saveProfessional = async (
  professionals: any[],
  token: string
) => {
  const { data } = await api.post(
    "/astrologer/profile/professional",
    { professionals },
    { headers: { Authorization: token } }
  );
  return normalize(data);
};

export const updateProfessional = async (
  professionals: any[],
  token: string
) => {
  const { data } = await api.put(
    "/astrologer/update-professional",
    { professionals },
    { headers: { Authorization: token } }
  );
  return normalize(data);
};

/*
|--------------------------------------------------------------------------
| Step 7 — Experience
|--------------------------------------------------------------------------
*/

export const saveExperience = async (
  experience: any[],
  token: string
) => {
  const { data } = await api.post(
    "/astrologer/profile/experience",
    { experience },
    { headers: { Authorization: token } }
  );
  return normalize(data);
};

export const updateExperience = async (
  experiences: any[],
  token: string
) => {
  const { data } = await api.put(
    "/astrologer/update-experience",
    { experiences },
    { headers: { Authorization: token } }
  );
  return normalize(data);
};

/*
|--------------------------------------------------------------------------
| Step 8 — Bank Details
|--------------------------------------------------------------------------
*/

export const saveBank = async (payload: any, token: string) => {
  const { data } = await api.post("/astrologer/profile/bank", payload, {
    headers: { Authorization: token },
  });
  return normalize(data);
};

export const updateBankAccount = async (payload: any, token: string) => {
  const { data } = await api.put("/astrologer/bank-account", payload, {
    headers: { Authorization: token },
  });
  return normalize(data);
};

/*
|--------------------------------------------------------------------------
| Step 9 — Documents
|--------------------------------------------------------------------------
*/

export const getMasterDocumentTypes = async (token: string) => {
  const { data } = await api.get("/astrologer/master/document-types", {
    headers: { Authorization: token },
  });
  return normalize(data);
};

export const uploadAstrologerDocument = async (
  payload: {
    file: File;
    documentType: string;
    documentNumber?: string;
    masterType: string;
  },
  token: string
) => {
  const formData = new FormData();

  formData.append("document", payload.file);
  formData.append("document_type", payload.documentType);
  formData.append("master_type", payload.masterType);

  if (payload.documentNumber) {
    formData.append("document_number", payload.documentNumber);
  }

  const response = await fetch("/api/astrologer/profile/document", {
    method: "POST",
    headers: { Authorization: token },
    body: formData,
  });

  return normalize(await response.json());
};

/*
|--------------------------------------------------------------------------
| Step 10 — Availability
|--------------------------------------------------------------------------
*/

export const getMasterWorkingHours = async (token: string) => {
  const { data } = await api.get("/astrologer/master/working-hours", {
    headers: { Authorization: token },
  });
  return normalize(data);
};

export const getAvailability = async (token: string) => {
  const { data } = await api.get("/astrologer/profile/availability", {
    headers: { Authorization: token },
  });
  return normalize(data);
};

export const saveAvailability = async (payload: any, token: string) => {
  const { data } = await api.post(
    "/astrologer/profile/availability",
    payload,
    { headers: { Authorization: token } }
  );
  return normalize(data);
};

export const updateAvailability = async (payload: any, token: string) => {
  const { data } = await api.put(
    "/astrologer/profile/update-availability",
    payload,
    { headers: { Authorization: token } }
  );
  return normalize(data);
};

/*
|--------------------------------------------------------------------------
| Profile hub (post-registration)
|--------------------------------------------------------------------------
*/

export const getAstrologerProfile = async (token: string) => {
  const { data } = await api.get("/astrologer/profile", {
    headers: { Authorization: token },
  });
  return normalize(data);
};

export const updateAstrologerProfile = async (
  payload: any,
  token: string
) => {
  const { data } = await api.put("/astrologer/profile/update", payload, {
    headers: { Authorization: token },
  });
  return normalize(data);
};

export const deleteAstrologerAccount = async (token: string) => {
  const { data } = await api.post(
    "/astrologer/delete-account",
    {},
    { headers: { Authorization: token } }
  );
  return normalize(data);
};

/*
|--------------------------------------------------------------------------
| Home dashboard (Home Activity API family)
|--------------------------------------------------------------------------
*/

// Confirmed live: GET /astrologer/dashboard returns the live snapshot
// shown on the Home screen — name, current_status ("Online"/"OFFLINE"/
// "busy"/"dnd"), the is_online/is_chat_online/is_call_online/quick_dnd
// switches, wallet_balance, and today's earning/calls/chats/minutes.
export const getAstrologerDashboard = async (token: string) => {
  const { data } = await api.get("/astrologer/dashboard", {
    headers: { Authorization: token },
  });
  return normalize(data);
};

// Confirmed live: GET /astrologer/dashboard/statistics returns the
// same day's totals broken out further (completed/missed/cancelled
// calls & chats, earning split by call/chat/report/gift, ratings).
export const getDashboardStatistics = async (token: string) => {
  const { data } = await api.get("/astrologer/dashboard/statistics", {
    headers: { Authorization: token },
  });
  return normalize(data);
};

export const setOnlineStatus = async (isOnline: boolean, token: string) => {
  const { data } = await api.put(
    "/astrologer/dashboard/online-status",
    { is_online: isOnline },
    { headers: { Authorization: token } }
  );
  return normalize(data);
};

export const setChatStatus = async (isAvailable: boolean, token: string) => {
  const { data } = await api.put(
    "/astrologer/dashboard/chat-status",
    { is_available: isAvailable },
    { headers: { Authorization: token } }
  );
  return normalize(data);
};

export const setCallStatus = async (isAvailable: boolean, token: string) => {
  const { data } = await api.put(
    "/astrologer/dashboard/call-status",
    { is_available: isAvailable },
    { headers: { Authorization: token } }
  );
  return normalize(data);
};

// Confirmed live: this is a genuinely separate endpoint from
// call-status — the Postman collection's "Update Busy Status" entry is
// mislabeled and copy-pasted the call-status URL (see CLAUDE.md's note
// on mislabeled Postman entries). The real endpoint is
// `dashboard/busy-status` and takes `is_busy`, not `is_available`.
export const setBusyStatus = async (isBusy: boolean, token: string) => {
  const { data } = await api.put(
    "/astrologer/dashboard/busy-status",
    { is_busy: isBusy },
    { headers: { Authorization: token } }
  );
  return normalize(data);
};

export const setQuickDnd = async (enable: boolean, token: string) => {
  const { data } = await api.put(
    "/astrologer/dashboard/quick-dnd",
    { enable },
    { headers: { Authorization: token } }
  );
  return normalize(data);
};

export interface AstrologerOfferInput {
  po5_enabled: boolean;
  po5_amount: number;
  free_chat_enabled: boolean;
  free_chat_minutes: number;
  free_call_enabled: boolean;
  free_call_minutes: number;
  campaign_enabled: boolean;
  campaign_title: string;
  campaign_description: string;
  start_date: string;
  end_date: string;
}

// Note: there is no GET counterpart for offer settings in the Postman
// collection or the live backend, so the Offers card can only ever
// send new settings — it can't pre-fill switches with the astrologer's
// actual saved state. Matches this codebase's "honest empty state over
// fake wire-up" convention (see CLAUDE.md's Wallet page note) rather
// than guessing a shape for a read endpoint that doesn't exist.
export const updateOffer = async (
  payload: AstrologerOfferInput,
  token: string
) => {
  const { data } = await api.put("/astrologer/dashboard/offer", payload, {
    headers: { Authorization: token },
  });
  return normalize(data);
};


/*
|--------------------------------------------------------------------------
| Astrologer Wallet
|--------------------------------------------------------------------------
*/

export interface AstrologerWalletSummary {
  lastThreeMonthEarnings?: number | string;
  monthlyEarnings?: number | string;
  weeklyEarnings?: number | string;
  rank?: number | string;
  availableBalance?: number | string;
  payableAmount?: number | string;
  todayAstromallEarnings?: number | string;
  pendingEarnings?: number | string;
  transactionCount?: number | string;
  [key: string]: any;
}

export interface AstrologerWalletTransaction {
  id?: string | number;
  transactionId?: string | number;
  type?: string;
  transactionType?: string;
  transaction_type?: string;
  category?: string;
  description?: string;
  title?: string;
  name?: string;
  date?: string;
  createdAt?: string;
  created_at?: string;
  transactionDate?: string;
  transactionTime?: string;
  transaction_date?: string;
  updatedAt?: string;
  updated_at?: string;
  amount?: number | string;
  transactionAmount?: number | string;
  transaction_amount?: number | string;
  value?: number | string;
  status?: string;
  transactionStatus?: string;
  [key: string]: any;
}

export const getAstrologerWallet = async (token: string) => {
  const response = await fetch("/api/astrologer/wallet", {
    method: "GET",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      Authorization: token,
    },
    cache: "no-store",
  });

  const data = await response.json();

  if (!response.ok) {
    throw new Error(
      data?.message || `Wallet API failed with status ${response.status}`
    );
  }

  return normalize(data);
};

export const getAstrologerWalletTransactions = async (
  token: string,
  page = 1,
  limit = 10,
  type: "ALL" | "EARNINGS" | "WITHDRAWALS" = "ALL"
) => {
  const params = new URLSearchParams({
    page: String(page),
    limit: String(limit),
    type,
  });

  const response = await fetch(
    `/api/astrologer/wallet/transactions?${params.toString()}`,
    {
      method: "GET",
      headers: {
        Accept: "application/json",
        "Content-Type": "application/json",
        Authorization: token,
      },
      cache: "no-store",
    }
  );

  const data = await response.json();

  if (!response.ok) {
    throw new Error(
      data?.message ||
        `Wallet transactions API failed with status ${response.status}`
    );
  }

  return normalize(data);
};

/*
|--------------------------------------------------------------------------
| Astrologer Wallet Withdraw History
|--------------------------------------------------------------------------
*/

export interface AstrologerWalletWithdrawHistory {
  id?: string | number;
  amount?: number | string;
  status?: string;
  paymentMethod?: string;
  requestDate?: string;
  [key: string]: any;
}

export const getAstrologerWalletWithdrawHistory = async (token: string) => {
  const response = await fetch("/api/astrologer/wallet/withdraw-history", {
    method: "GET",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      Authorization: token,
    },
    cache: "no-store",
  });

  const data = await response.json();

  if (!response.ok) {
    throw new Error(
      data?.message ||
        `Wallet withdraw history API failed with status ${response.status}`
    );
  }

  return normalize(data);
};


/*
|--------------------------------------------------------------------------
| Consultation Requests
|--------------------------------------------------------------------------
*/

export interface ConsultationRequest {
  consultation_id: number;
  user_id: number;

  user_name?: string;
  consultee_name?: string;

  medium: "CHAT" | "CALL" | string;

  is_free_chat?: boolean;
  free_minutes?: number;

  rate_per_minute?: number;
  estimated_earning?: number;

  consultee_birth_date?: string;
  consultee_birth_time?: string;
  consultee_birth_place?: string;

  // Unconfirmed whether the backend actually returns these — sent by
  // the customer at /consultation/start (see StartConsultationPayload)
  // but never verified live on this response. Read defensively via
  // extractCustomerDetails() below rather than assumed present.
  consultee_gender?: string;
  gender?: string;
  consultee_marital_status?: string;
  marital_status?: string;
  consultee_topic?: string;
  topic?: string;

  ring_expires_at?: string;
  seconds_to_expiry?: number;

  [key: string]: any;
}

export interface ConsultationRequestsResponse {
  items: ConsultationRequest[];
  count: number;
  poll_interval_seconds?: number;
}

export const getConsultationRequests = async (
  token: string,
) => {
  const { data } = await api.get(
    "/astrologer/consultation/requests",
    {
      headers: {
        Authorization: token,
      },
    },
  );

  return normalize(data);
};

/*
 * Waiting queue (3 Oct 2026 API) — the customers queued for THIS
 * astrologer. Lists WAITING / NOTIFIED / CONNECTING entries in queue
 * order; `status` says which, `queuePosition` counts chat and call
 * together (one session at a time). Removing an entry marks it
 * REJECTED / REMOVED_BY_ASTROLOGER and, if it held the turn, hands the
 * turn to the next customer. The response shape was documented as
 * "unchanged" from before the fix, so the row fields are read
 * defensively (see normalizeWaitlistEntry) rather than trusted.
 */
export interface AstrologerWaitlistEntry {
  id: number;
  name: string;
  status: string;
  position: number | null;
  type: string;
  requestedAt: string;
}

const pick = (source: any, keys: string[]) => {
  for (const key of keys) {
    if (source?.[key] != null && source[key] !== "") return source[key];
  }
  return undefined;
};

export const normalizeWaitlistEntry = (row: any): AstrologerWaitlistEntry => ({
  id: Number(pick(row, ["id", "queue_id", "queueId", "ID"]) ?? 0),
  name: String(
    pick(row, [
      "userName",
      "user_name",
      "customerName",
      "customer_name",
      "name",
      "Name",
      "user.name",
    ]) ?? pick(row?.user ?? row?.User, ["name", "Name"]) ?? "Customer",
  ),
  status: String(pick(row, ["status", "Status"]) ?? "WAITING"),
  position: pick(row, ["queuePosition", "queue_position", "position"]) ?? null,
  type: String(pick(row, ["waitingType", "waiting_type", "medium", "type"]) ?? ""),
  requestedAt: String(pick(row, ["requestedAt", "requested_at", "createdAt", "created_at"]) ?? ""),
});

export const getAstrologerWaitlist = async (
  token: string,
  params: { page?: number; limit?: number; search?: string } = {},
) => {
  const { data } = await api.get("/astrologer/activity/waitlist", {
    params: { page: params.page ?? 1, limit: params.limit ?? 50, search: params.search ?? "" },
    headers: { Authorization: token },
  });

  const result = normalize(data);
  const payload = result?.data ?? data;
  const rows: any[] = Array.isArray(payload)
    ? payload
    : (payload?.list ?? payload?.items ?? payload?.rows ?? payload?.data ?? []);

  return {
    ...result,
    entries: (Array.isArray(rows) ? rows : []).map(normalizeWaitlistEntry),
  };
};

export const removeAstrologerWaitlistEntry = async (id: number, token: string) => {
  const { data } = await api.delete(`/astrologer/activity/waitlist/${id}`, {
    headers: { Authorization: token },
  });

  return normalize(data);
};

// Confirmed live: the customer's own /active response has NO name field
// at all (only astrologer_name/astrologer_id) — the customer's name is
// only ever present in the accept response and the ring-queue item, not
// on any later poll. Session-storage it here so the session detail page
// (which only ever calls /active once it's mounted) has a fallback
// instead of showing a blank "?" avatar for the whole call.
function cacheConsultationCustomerName(consultationId: number, name?: string) {
  if (!name || typeof window === "undefined") return;
  try {
    sessionStorage.setItem(`consultation_${consultationId}_customer_name`, name);
  } catch {
    // Best-effort — a missing name just falls back to "?" initials.
  }
}

export function getCachedConsultationCustomerName(consultationId: number): string {
  if (typeof window === "undefined") return "";
  try {
    return sessionStorage.getItem(`consultation_${consultationId}_customer_name`) ?? "";
  } catch {
    return "";
  }
}

// Fuller version of the name cache above — birth details/gender/
// marital status/topic for the astrologer-side chat-start details
// banner (see app/astrologer/consultation/[id]/page.tsx). Same
// reasoning as the name cache: the accept response is the only place
// these are ever likely to appear (later polls only carry the name),
// so whatever's present at accept time is cached for the rest of the
// session. Never assume every field is present — extractCustomerDetails()
// alias-scans since it's unconfirmed which of these the backend
// actually returns.
export interface CachedCustomerDetails {
  name?: string;
  gender?: string;
  birthDate?: string;
  birthTime?: string;
  birthPlace?: string;
  maritalStatus?: string;
  topic?: string;
}

export function extractCustomerDetails(data: any): CachedCustomerDetails {
  if (!data || typeof data !== "object") return {};

  return {
    name: data.user_name ?? data.consultee_name ?? undefined,
    gender: data.consultee_gender ?? data.gender ?? undefined,
    birthDate: data.consultee_birth_date ?? data.birth_date ?? undefined,
    birthTime: data.consultee_birth_time ?? data.birth_time ?? undefined,
    birthPlace: data.consultee_birth_place ?? data.birth_place ?? undefined,
    maritalStatus: data.consultee_marital_status ?? data.marital_status ?? undefined,
    topic: data.consultee_topic ?? data.topic ?? undefined,
  };
}

export function cacheConsultationCustomerDetails(consultationId: number, details: CachedCustomerDetails) {
  if (typeof window === "undefined") return;

  const hasAny = Object.values(details).some((value) => !!value);
  if (!hasAny) return;

  try {
    sessionStorage.setItem(
      `consultation_${consultationId}_customer_details`,
      JSON.stringify(details),
    );
  } catch {
    // Best-effort — the details banner just omits whatever's missing.
  }
}

export function getCachedConsultationCustomerDetails(consultationId: number): CachedCustomerDetails {
  if (typeof window === "undefined") return {};

  try {
    const raw = sessionStorage.getItem(`consultation_${consultationId}_customer_details`);
    return raw ? JSON.parse(raw) : {};
  } catch {
    return {};
  }
}

// Confirmed live (Consultation API Handbook): marks the astrologer
// busy, claims the customer's free chat if one is in play, and mints
// the astrologer's OWN Agora credentials — a different token from the
// customer's, bound to a different uid. started_at stays empty until
// the customer confirms; this astrologer app never ticks, it runs a
// local countdown anchored to server_time and learns about the end
// from a push or /active on resume.
//
// `ringQueueItem` is optional and comes from whichever page is
// calling this (the Orders list, the dashboard's "Waiting now" panel,
// a notification card) — it's the SAME ConsultationRequest object
// already rendered in that list, which is the only place birth
// details/gender/marital-status/topic have actually been observed at
// all; confirmed live the accept response's own `data` does NOT
// reliably carry them (the astrologer-side details banner rendered
// empty for a real session even though the customer had filled in the
// full form). Passing it here means cacheConsultationCustomerDetails
// has real data to work with instead of silently caching nothing.
export const acceptConsultation = async (
  consultationId: number,
  token: string,
  ringQueueItem?: ConsultationRequest,
) => {
  const { data } = await api.post(
    "/astrologer/consultation/accept",
    { consultation_id: consultationId },
    { headers: { Authorization: token } },
  );

  const result = normalize(data);
  const name = result?.data?.user_name ?? result?.data?.consultee_name;
  cacheConsultationCustomerName(consultationId, name);

  // Ring-queue item first (it's confirmed to carry at least the birth
  // fields — see ConsultationRequest above), accept response layered
  // on top for anything it happens to add/override.
  const merged = {
    ...extractCustomerDetails(ringQueueItem),
    ...extractCustomerDetails(result?.data),
  };
  cacheConsultationCustomerDetails(consultationId, merged);

  return result;
};

// The reason is shown to the customer verbatim. Nothing billed; their
// free chat is untouched.
export const rejectConsultation = async (
  consultationId: number,
  reason: string,
  token: string,
) => {
  const { data } = await api.post(
    "/astrologer/consultation/reject",
    { consultation_id: consultationId, reason },
    { headers: { Authorization: token } },
  );

  return normalize(data);
};

// Read-only, unlike the customer's /active — it never bills or closes
// a session, since the astrologer isn't the party whose ticks define
// liveness.
export const getAstrologerActiveConsultation = async (token: string) => {
  const { data } = await api.get(
    "/astrologer/consultation/active",
    { headers: { Authorization: token } },
  );

  return normalize(data);
};

// The /end receipt is the shared snake_case one — its wallet_balance
// is the CUSTOMER's, not the astrologer's; render astrologer_earning
// and settlement_status instead.
export const endAstrologerConsultation = async (
  consultationId: number,
  token: string,
) => {
  const { data } = await api.post(
    "/astrologer/consultation/end",
    { consultation_id: consultationId },
    { headers: { Authorization: token } },
  );

  return normalize(data);
};

export const getAstrologerConsultationAgoraToken = async (
  consultationId: number,
  token: string,
) => {
  const { data } = await api.post(
    "/astrologer/consultation/agora-token",
    { consultation_id: consultationId },
    { headers: { Authorization: token } },
  );

  return normalize(data);
};

// Confirmed live: /astrologer/consultation/messages mirrors the
// customer-side /user/consultation/messages read-only transcript
// endpoint exactly (same {consultation_id, consultation_no, items,
// page, limit, total, total_pages} shape) — used to reload prior
// messages when resuming an ONGOING session after a page refresh,
// since the live message list otherwise only ever grows from RTM
// events and resets empty on remount.
export const getAstrologerConsultationMessages = async (
  consultationId: number,
  token: string,
  page = 1,
  limit = 200,
) => {
  const { data } = await api.get("/astrologer/consultation/messages", {
    params: { consultation_id: consultationId, page, limit },
    headers: { Authorization: token },
  });

  return normalize(data);
};

// Confirmed live: /astrologer/consultation/messages/sync exists and
// mirrors the customer endpoint's request shape. This is the fix for
// a real bug — the astrologer page previously synced its own outgoing
// messages through the CUSTOMER-side syncConsultationMessages()
// (app/lib/consultation.ts), which authenticates via getAuthToken()
// reading the end-user `token` key. That only ever appeared to work
// during this session's own testing because both tokens sat in the
// same browser's localStorage at once; on a real, separate astrologer
// device (no end-user token present at all) the call silently failed
// with "Please login first" and the astrologer's own chat messages
// were never durably saved — confirmed live via a real one-to-one
// session where the customer's messages survived a refresh but the
// astrologer's own reply didn't.
// Deliberately a raw fetch(), not the api.post() axios instance used
// elsewhere in this file — axios 1.x's default browser (XHR) adapter
// has no `keepalive` concept, so a `keepalive: true` option passed
// into its config is silently dropped. `keepalive` is only meaningful
// on the underlying fetch() call itself (see the matching
// beforeunload/pagehide flush in app/astrologer/consultation/[id]/
// page.tsx), so this needs to bypass axios to actually work.
export const syncAstrologerConsultationMessages = async (
  consultationId: number,
  messages: ConsultationMessage[],
  isFinal: boolean,
  token: string,
  keepalive = false,
) => {
  const response = await fetch("/api/astrologer/consultation/messages/sync", {
    method: "POST",
    headers: { "Content-Type": "application/json", Authorization: token },
    body: JSON.stringify({ consultation_id: consultationId, is_final: isFinal, messages }),
    keepalive,
  });

  const data = await response.json();
  return normalize(data);
};


/*
|--------------------------------------------------------------------------
| Settlement Consultations — full consultation history + earnings
|--------------------------------------------------------------------------
|
| Confirmed live: GET /astrologer/settlement/consultations returns every
| consultation that has reached a settlement stage (real
| status=ALL&medium=ALL&page=1&limit=20 capture), each row already
| carrying its own gross/platform-fee/earning split and a
| server-computed statusLabel — no separate per-id endpoint exists, so
| the detail page re-fetches this same list and finds the matching
| consultationId client-side (same "honest, no invented endpoint"
| pattern as wallet/settlement-history/[id] and wallet/transaction/[id]
| — see CLAUDE.md).
|
*/

export interface AstrologerSettlementConsultation {
  consultationId: number;
  consultationNo: string;
  medium: "CHAT" | "AUDIO" | string;
  duration: string;
  billedMinutes: number;
  ratePerMinute: number;
  grossAmount: number;
  platformFee: number;
  astrologerEarning: number;
  settlementStatus: string;
  statusLabel: string;
  isFreeChat?: boolean;
  freeMinutes?: number;
  consultationDate: string;
  [key: string]: any;
}

export interface AstrologerSettlementConsultationsData {
  items: AstrologerSettlementConsultation[];
  page: number;
  limit: number;
  total: number;
  totalPages: number;
  pendingAmount: number;
  toBeSettledAmount: number;
  onHoldAmount: number;
  settledAmount: number;
  totalEarning: number;
}

export const getAstrologerSettlementConsultations = async (
  token: string,
  options: {
    status?: string;
    medium?: string;
    page?: number;
    limit?: number;
  } = {},
) => {
  const { status = "ALL", medium = "ALL", page = 1, limit = 20 } = options;

  const { data } = await api.get("/astrologer/settlement/consultations", {
    params: { status, medium, page, limit },
    headers: { Authorization: token },
  });

  return normalize(data);
};


/*
|--------------------------------------------------------------------------
| Reviews (read through the end-user API, astrologer's own token works)
|--------------------------------------------------------------------------
*/

// Confirmed live: on success this returns
// {status: 200, message, recordList, totalRecord} — `status` here is
// a raw HTTP-style status *code* (a number), not the boolean every
// other astrologer endpoint uses, so normalize()'s `success: data.status`
// aliasing happens to work for the success path (200 is truthy) but
// would be actively wrong on failure: a 400 error body is also
// `{status: 400, message: "..."}}`, and 400 is ALSO truthy, so
// normalize() would misreport a real failure as success. Skip
// normalize() for the error path and hard-code success: false instead.
// Also confirmed live: this 400s with a raw SQL scan error for any
// astrologer whose `experience` column is a decimal-looking string
// ("8.0") — a real backend data bug, not something to retry around.
export const getAstrologerReviews = async (
  astrologerId: number,
  token: string,
  startIndex = 0,
  fetchRecord = 10
) => {
  try {
    const { data } = await api.post(
      "/astrologer/reviews",
      { astrologerId, startIndex, fetchRecord },
      { headers: { Authorization: token } }
    );
    return normalize(data);
  } catch (error: any) {
    return {
      success: false,
      message: error.response?.data?.message ?? "Unable to load reviews.",
      recordList: [],
    };
  }
};
