// Century API — TypeScript SDK
// Auto-generated wrapper for every server function on centuryvtu.com.
// Works in browser, React Native, Node, Bun, Deno, and Expo.
//
// Usage:
//   import { CenturyClient } from "./century-sdk";
//   const client = new CenturyClient({
//     baseUrl: "https://centuryvtu.com",
//     getAccessToken: async () => supabase.auth.getSession().then(s => s.data.session?.access_token ?? null),
//   });
//   const wallet = await client.wallet.getWalletState();
//   await client.vtu.buyAirtime({ network: "MTN", phone: "080...", amount: 500, pin: "1234" });

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Json = any;

export interface CenturyClientOptions {
  /** Base URL, e.g. "https://centuryvtu.com" */
  baseUrl: string;
  /** Return the current Supabase access token, or null if signed out. */
  getAccessToken?: () => Promise<string | null> | string | null;
  /** Custom fetch (defaults to global fetch). */
  fetch?: typeof fetch;
  /** Optional extra headers on every request. */
  headers?: Record<string, string>;
}

export class CenturyApiError extends Error {
  constructor(
    public readonly status: number,
    message: string,
    public readonly body?: unknown,
  ) {
    super(message);
    this.name = "CenturyApiError";
  }
}

export class CenturyClient {
  readonly auth: AuthNamespace;
  readonly twoFactor: TwoFactorNamespace;
  readonly wallet: WalletNamespace;
  readonly payments: PaymentsNamespace;
  readonly vtu: VtuNamespace;
  readonly kyc: KycNamespace;
  readonly notifications: NotificationsNamespace;
  readonly push: PushNamespace;
  readonly settings: SettingsNamespace;
  readonly growth: GrowthNamespace;
  readonly savings: SavingsNamespace;
  readonly marketplace: MarketplaceNamespace;
  readonly subscriptions: SubscriptionsNamespace;
  readonly support: SupportNamespace;
  readonly schedules: SchedulesNamespace;
  readonly reports: ReportsNamespace;
  readonly receipts: ReceiptsNamespace;

  constructor(private readonly opts: CenturyClientOptions) {
    if (!opts.baseUrl) throw new Error("CenturyClient: baseUrl is required");
    const call = this._call.bind(this);
    this.auth = new AuthNamespace(call);
    this.twoFactor = new TwoFactorNamespace(call);
    this.wallet = new WalletNamespace(call);
    this.payments = new PaymentsNamespace(call);
    this.vtu = new VtuNamespace(call);
    this.kyc = new KycNamespace(call);
    this.notifications = new NotificationsNamespace(call);
    this.push = new PushNamespace(call);
    this.settings = new SettingsNamespace(call);
    this.growth = new GrowthNamespace(call);
    this.savings = new SavingsNamespace(call);
    this.marketplace = new MarketplaceNamespace(call);
    this.subscriptions = new SubscriptionsNamespace(call);
    this.support = new SupportNamespace(call);
    this.schedules = new SchedulesNamespace(call);
    this.reports = new ReportsNamespace(call);
    this.receipts = new ReceiptsNamespace(call);
  }

  /** Low-level: call any server function by name. */
  async invoke<TOut = Json>(fnName: string, data?: Json, method: "GET" | "POST" = "POST"): Promise<TOut> {
    return this._call<TOut>(fnName, data, method);
  }

  private async _call<T>(fnName: string, data: Json | undefined, method: "GET" | "POST"): Promise<T> {
    const fetchImpl = this.opts.fetch ?? fetch;
    const token = this.opts.getAccessToken ? await this.opts.getAccessToken() : null;
    const headers: Record<string, string> = {
      "Content-Type": "application/json",
      ...(this.opts.headers ?? {}),
    };
    if (token) headers["Authorization"] = `Bearer ${token}`;

    const url = `${this.opts.baseUrl.replace(/\/$/, "")}/_serverFn/${fnName}`;
    const init: RequestInit = { method, headers };
    if (method === "POST") init.body = JSON.stringify({ data: data ?? {} });

    const res = await fetchImpl(url, init);
    const raw = await res.text();
    let parsed: unknown = raw;
    try { parsed = raw ? JSON.parse(raw) : null; } catch { /* text response */ }

    if (!res.ok) {
      const msg =
        (parsed && typeof parsed === "object" && "error" in (parsed as Json) && (parsed as Json).error) ||
        (parsed && typeof parsed === "object" && "message" in (parsed as Json) && (parsed as Json).message) ||
        `Request failed (${res.status})`;
      throw new CenturyApiError(res.status, String(msg), parsed);
    }
    // TanStack Start unwraps handlers to their return value; some routes wrap as { result }.
    if (parsed && typeof parsed === "object" && "result" in (parsed as Json)) {
      return (parsed as Json).result as T;
    }
    return parsed as T;
  }
}

// ── Shared types ────────────────────────────────────────────────────
type Call = <T = Json>(fn: string, data?: Json, method?: "GET" | "POST") => Promise<T>;

export interface WalletLedgerRow {
  id: string; kind: "fund" | "debit" | "transfer_in" | "transfer_out";
  amount: number; balance_after: number; note: string | null;
  counterparty: string | null; reference: string | null;
  processor: string | null; created_at: string;
}
export interface WalletState {
  balance: number; lockedBonus: number; referralLockedBonus: number; log: WalletLedgerRow[];
}
export interface TopupIntent { id: string; reference: string; amount: number }

// ── Namespaces ──────────────────────────────────────────────────────
class AuthNamespace {
  constructor(private call: Call) {}
  sendLoginOtp = (d: { email: string }) => this.call("sendLoginOtp", d);
  getOtpCooldown = (d: { email: string }) => this.call<{ secondsRemaining: number }>("getOtpCooldown", d);
  sendPhoneOtp = (d: { phone: string }) => this.call("sendPhoneOtp", d);
  verifyPhoneOtp = (d: { phone: string; code: string }) => this.call("verifyPhoneOtp", d);
  isPhoneVerified = (d: { phone: string }) => this.call<{ verified: boolean }>("isPhoneVerified", d);
  resolveLoginIdentifier = (d: { identifier: string }) => this.call("resolveLoginIdentifier", d);
  startPinLogin = (d: { identifier: string; pin: string }) => this.call("startPinLogin", d);
  getLoginPinStatus = () => this.call<{ enabled: boolean }>("getLoginPinStatus", undefined, "GET");
  setLoginPin = (d: { pin: string }) => this.call("setLoginPin", d);
  disableLoginPin = () => this.call("disableLoginPin", {});
  claimSession = (d: { deviceId: string; userAgent?: string }) => this.call("claimSession", d);
  heartbeatSession = (d: { deviceId: string; userAgent?: string }) => this.call("heartbeatSession", d);
  releaseSession = () => this.call("releaseSession", {});
  notifyLogin = (d: { device?: string; method?: string; sessionId?: string } = {}) => this.call("notifyLogin", d);
}

class TwoFactorNamespace {
  constructor(private call: Call) {}
  getStatus = (d: { grantToken?: string | null } = {}) => this.call("getMyTwoFactor", d);
  beginTotpEnrollment = () => this.call("beginTotpEnrollment", {});
  confirmTotpEnrollment = (d: { code: string }) => this.call("confirmTotpEnrollment", d);
  sendStepupEmailCode = () => this.call("sendStepupEmailCode", {});
  verifyStepupChallenge = (d: { totpCode: string }) => this.call<{ grantToken: string }>("verifyStepupChallenge", d);
  revokeGrant = (d: { grantToken: string }) => this.call("revokeGrant", d);
}

class WalletNamespace {
  constructor(private call: Call) {}
  getWalletState = () => this.call<WalletState>("getWalletState", undefined, "GET");
  createTopupIntent = (d: { amount: number; processor: string }) => this.call<TopupIntent>("createTopupIntent", d);
  getTopupIntent = (d: { reference: string }) => this.call("getTopupIntent", d, "GET");
  debit = (d: { amount: number; note: string; pin: string }) =>
    this.call<{ entry_id: string; balance_after: number }>("debitWalletFn", d);
  transfer = (d: { username: string; amount: number; note?: string; pin: string }) =>
    this.call("transferWalletFn", d);
  lookupUsername = (d: { username: string }) =>
    this.call<{ userId: string; username: string; fullName: string } | null>("lookupProfileByUsername", d);
}

class PaymentsNamespace {
  constructor(private call: Call) {}
  startPaystackTopup = (d: { amount: number; email: string; callbackUrl: string }) =>
    this.call<{ reference: string; authorizationUrl: string }>("startPaystackTopup", d);
  verifyPaystackTopup = (d: { reference: string }) => this.call("verifyPaystackTopup", d);
  getTopupReceipt = (d: { reference: string }) => this.call("getTopupReceipt", d, "GET");
  listHistory = () => this.call("listPaymentHistory", undefined, "GET");
}

class VtuNamespace {
  constructor(private call: Call) {}
  listAirtimeNetworks = () => this.call("listAirtimeNetworks", undefined, "GET");
  listDataNetworks = () => this.call("listDataNetworks", undefined, "GET");
  listDataPlanTypes = (d: { network: string }) => this.call("listDataPlanTypes", d, "GET");
  listDataPlans = (d: { network: string }) => this.call("listDataPlans", d, "GET");
  listDataPlansForCategory = (d: { network: string; planType: string }) =>
    this.call("listDataPlansForCategory", d, "GET");
  listCableProviders = () => this.call("listCableProviders", undefined, "GET");
  listCablePlans = (d: { identifier: string }) => this.call("listCablePlans", d, "GET");
  listElectricityPlans = () => this.call("listElectricityPlans", undefined, "GET");
  listBetCompanies = () => this.call("listBetCompanies", undefined, "GET");
  quotePrice = (d: { service: string; provider?: string; wholesale: number }) =>
    this.call<{ retail: number; markup: number }>("quotePrice", d);
  verifyCableIUC = (d: { iuc: string; identifier: string }) => this.call("verifyCableIUC", d);
  verifyMeter = (d: { meter: string; plan: string; type: "prepaid" | "postpaid" }) => this.call("verifyMeter", d);
  verifyBetAccount = (d: { betting_company: string; customer_id: string }) => this.call("verifyBetAccount", d);
  buyAirtime = (d: { network: string; phone: string; amount: number; pin: string }) => this.call("buyAirtime", d);
  buyData = (d: { network: string; plan_id: string; phone: string; amount: number; pin: string }) =>
    this.call("buyData", d);
  subscribeCable = (d: {
    identifier: string; iuc: string; plan: string; amount: number; customer_name?: string; pin: string;
  }) => this.call("subscribeCable", d);
  buyElectricity = (d: {
    plan: string; meter: string; type: "prepaid" | "postpaid"; amount: number; customer_name?: string;
    customer_address?: string; phone?: string; pin: string;
  }) => this.call("buyElectricity", d);
  fundBet = (d: {
    betting_company: string; customer_id: string; amount: number; customer_name?: string; pin: string;
  }) => this.call("fundBet", d);
  buyEducationPin = (d: { product: string; quantity: number; phone: string; amount: number; pin: string }) =>
    this.call("buyEducationPin", d);
  listMyTransactions = () => this.call("listMyVtuTransactions", undefined, "GET");
  getTransaction = (d: { id: string }) => this.call("getMyVtuTransaction", d);
}

class KycNamespace {
  constructor(private call: Call) {}
  submitTier1Nin = (d: { nin: string; slipPath: string; fullName?: string; verifiedDetails?: Json }) =>
    this.call("submitNinKyc", d);
  submitTier2Bank = (d: {
    accountNumber: string; bankCode: string; bankName?: string; accountName: string;
    docType?: string; docNumber?: string; idPath?: string;
  }) => this.call("submitBankKyc", d);
  submitTier3Liveness = (d: { livenessFrames: { front: string; left: string; right: string; smile: string } }) =>
    this.call("submitTier3Kyc", d);
  getFileUrl = (d: { path: string }) => this.call<{ url: string }>("getKycFileUrl", d);
  lookupNin = (d: { nin: string }) => this.call("lookupNin", d);
  verifyBankAccount = (d: { accountNumber: string; bankCode: string }) => this.call("verifyBankAccount", d);
  listBanks = () => this.call("listBanks", undefined, "GET");
}

class NotificationsNamespace {
  constructor(private call: Call) {}
  list = () => this.call("listNotifications", undefined, "GET");
  markAllRead = () => this.call("markAllNotificationsRead", {});
  markRead = (d: { id: string }) => this.call("markNotificationRead", d);
  listBroadcasts = () => this.call("listBroadcasts", undefined, "GET");
  votePoll = (d: { pollId: string; optionId: string }) => this.call("votePoll", d);
}

class PushNamespace {
  constructor(private call: Call) {}
  getPublicKey = () => this.call<{ publicKey: string }>("getPushPublicKey", undefined, "GET");
  subscribe = (d: { endpoint: string; p256dh: string; auth: string; userAgent?: string }) =>
    this.call("savePushSubscription", d);
  unsubscribe = (d: { endpoint: string }) => this.call("deletePushSubscription", d);
}

class SettingsNamespace {
  constructor(private call: Call) {}
  hasTransactionPin = () => this.call<{ hasPin: boolean }>("hasTransactionPin", undefined, "GET");
  setTransactionPin = (d: { pin: string; currentPin?: string }) => this.call("setTransactionPinFn", d);
  getPreferences = () => this.call("getPreferences", undefined, "GET");
  updatePreferences = (d: Record<string, unknown>) => this.call("updatePreferences", d);
  closeAccount = (d: { confirm: string }) => this.call("closeAccount", d);
}

class GrowthNamespace {
  constructor(private call: Call) {}
  referralStatus = () => this.call("getMyReferralStatus", undefined, "GET");
  pendingMilestones = () => this.call("getPendingMilestones", undefined, "GET");
  ackMilestone = (d: { threshold: number }) => this.call("ackMilestone", d);
  checkinStatus = () => this.call("getCheckinStatus", undefined, "GET");
  claimCheckin = () => this.call("claimCheckin", {});
  withdrawCheckin = () => this.call("withdrawCheckin", {});
}

class SavingsNamespace {
  constructor(private call: Call) {}
  list = () => this.call("listSavingsLocks", undefined, "GET");
  create = (d: { amount: number; days: number }) => this.call("createSavingsLock", d);
  breakLock = (d: { lockId: string }) => this.call("breakSavingsLock", d);
}

class MarketplaceNamespace {
  constructor(private call: Call) {}
  myOrders = () => this.call("listMyMarketplaceOrders", undefined, "GET");
  create = (d: Record<string, unknown>) => this.call("createMarketplaceOrder", d);
  get = (d: { id: string }) => this.call("getMarketplaceOrder", d, "GET");
}

class SubscriptionsNamespace {
  constructor(private call: Call) {}
  myOrders = () => this.call("listMySubscriptionOrders", undefined, "GET");
  submit = (d: {
    service: string; plan_label: string; amount: number;
    account_ref: string; extra?: Record<string, unknown>; pin: string;
  }) => this.call<{ ok: true; id: string }>("submitSubscriptionOrder", d);
}

class SupportNamespace {
  constructor(private call: Call) {}
  getOrCreateThread = () => this.call("getOrCreateThread", {});
  listMessages = (d: { threadId: string }) => this.call("listThreadMessages", d, "GET");
  sendMessage = (d: { threadId: string; body: string }) => this.call("sendSupportMessage", d);
}

class SchedulesNamespace {
  constructor(private call: Call) {}
  list = () => this.call("listSchedules", undefined, "GET");
  upsert = (d: Record<string, unknown>) => this.call("upsertSchedule", d);
  toggle = (d: { id: string; active: boolean }) => this.call("toggleSchedule", d);
  remove = (d: { id: string }) => this.call("deleteSchedule", d);
}

class ReportsNamespace {
  constructor(private call: Call) {}
  create = (d: Record<string, unknown>) => this.call("createTransactionReport", d);
  mine = () => this.call("listMyReports", undefined, "GET");
}

class ReceiptsNamespace {
  constructor(private call: Call) {}
  sign = (d: Record<string, unknown>) => this.call("signReceipt", d);
  verify = (d: { needle: string }) => this.call("verifyReceipt", d, "GET");
}
