const { get, run } = require('./postgres');

const FREE_MONTHLY_CREDITS = 15;
const PRO_MONTHLY_CREDITS = 250;
const TOPUP_DEFAULT_CREDITS = 60;
const REVENUECAT_PRO_ENTITLEMENT_ID = (process.env.REVENUECAT_PRO_ENTITLEMENT_ID || 'pro').trim() || 'pro';
const SUPPORTED_SUBSCRIPTION_PRODUCTS = new Set(['monthly_pro', 'yearly_pro']);

const TOPUP_CREDITS_BY_PRODUCT = {
  monthly_pro: 0,
  yearly_pro: 0,
  topup_small: 25,
  topup_medium: 120,
  topup_large: 300,
};

const AVAILABLE_PRODUCTS = ['monthly_pro', 'yearly_pro', 'topup_small', 'topup_medium', 'topup_large'];

const nowIso = () => new Date().toISOString();

const asIsoDate = (value) => {
  if (value == null || value === '') return null;
  if (value instanceof Date) {
    return Number.isNaN(value.getTime()) ? null : value.toISOString();
  }
  if (typeof value === 'number' && Number.isFinite(value)) {
    return new Date(value).toISOString();
  }
  if (typeof value === 'string') {
    const trimmed = value.trim();
    if (!trimmed) return null;
    if (/^\d+$/.test(trimmed)) {
      return new Date(Number(trimmed)).toISOString();
    }
    const parsed = new Date(trimmed);
    if (!Number.isNaN(parsed.getTime())) {
      return parsed.toISOString();
    }
  }
  return null;
};

const startOfUtcMonth = (date) => {
  return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1, 0, 0, 0, 0));
};

const addUtcMonths = (date, months) => {
  return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + months, 1, 0, 0, 0, 0));
};

const addDays = (date, days) => {
  const result = new Date(date.getTime());
  result.setUTCDate(result.getUTCDate() + days);
  return result;
};

const getCycleBounds = (now) => {
  const cycleStartedAt = startOfUtcMonth(now);
  const cycleEndsAt = addUtcMonths(cycleStartedAt, 1);
  return { cycleStartedAt, cycleEndsAt };
};

const getMonthlyAllowanceForPlan = (plan) => {
  return plan === 'pro' ? PRO_MONTHLY_CREDITS : FREE_MONTHLY_CREDITS;
};

const createInsufficientCreditsError = (required, available) => {
  const error = new Error(`Insufficient credits. Required ${required}, available ${available}.`);
  error.code = 'INSUFFICIENT_CREDITS';
  error.status = 402;
  error.metadata = { required, available };
  return error;
};

const runInTransaction = async (db, worker) => {
  const client = typeof db.connect === 'function' ? await db.connect() : db;
  const release = typeof client.release === 'function' ? () => client.release() : () => {};

  await run(client, 'BEGIN');
  try {
    const result = await worker(client);
    await run(client, 'COMMIT');
    return result;
  } catch (error) {
    try {
      await run(client, 'ROLLBACK');
    } catch (rollbackError) {
      console.error('Failed to rollback billing transaction.', rollbackError);
    }
    throw error;
  } finally {
    release();
  }
};

const normalizeAccountRow = (row) => {
  if (!row) return null;
  const now = new Date();
  const { cycleStartedAt: defaultCycleStartedAt, cycleEndsAt: defaultCycleEndsAt } = getCycleBounds(now);
  return {
    userId: String(row.userId),
    plan: row.plan === 'pro' ? 'pro' : 'free',
    provider: typeof row.provider === 'string' && row.provider ? row.provider : 'revenuecat',
    cycleStartedAt: asIsoDate(row.cycleStartedAt) || defaultCycleStartedAt.toISOString(),
    cycleEndsAt: asIsoDate(row.cycleEndsAt) || defaultCycleEndsAt.toISOString(),
    monthlyAllowance: Number(row.monthlyAllowance) || FREE_MONTHLY_CREDITS,
    usedThisCycle: Number(row.usedThisCycle) || 0,
    topupBalance: Number(row.topupBalance) || 0,
    renewsAt: asIsoDate(row.renewsAt),
    updatedAt: asIsoDate(row.updatedAt) || now.toISOString(),
  };
};

const buildDefaultAccount = (userId, now) => {
  const { cycleStartedAt, cycleEndsAt } = getCycleBounds(now);
  return {
    userId,
    plan: 'free',
    provider: 'revenuecat',
    cycleStartedAt: cycleStartedAt.toISOString(),
    cycleEndsAt: cycleEndsAt.toISOString(),
    monthlyAllowance: FREE_MONTHLY_CREDITS,
    usedThisCycle: 0,
    topupBalance: 0,
    renewsAt: null,
    updatedAt: nowIso(),
  };
};

const alignAccountToCurrentCycle = (account, now) => {
  const next = { ...account };
  const expectedMonthlyAllowance = getMonthlyAllowanceForPlan(next.plan);
  if (next.monthlyAllowance !== expectedMonthlyAllowance) {
    next.monthlyAllowance = expectedMonthlyAllowance;
  }

  if (!next.renewsAt && next.plan === 'pro') {
    next.renewsAt = addDays(now, 30).toISOString();
  }

  const cycleEndsAtMs = new Date(next.cycleEndsAt).getTime();
  if (Number.isNaN(cycleEndsAtMs) || now.getTime() >= cycleEndsAtMs) {
    const { cycleStartedAt, cycleEndsAt } = getCycleBounds(now);
    next.cycleStartedAt = cycleStartedAt.toISOString();
    next.cycleEndsAt = cycleEndsAt.toISOString();
    next.usedThisCycle = 0;
    next.monthlyAllowance = expectedMonthlyAllowance;
  }

  return next;
};

const accountChanged = (a, b) => {
  return a.userId !== b.userId
    || a.plan !== b.plan
    || a.provider !== b.provider
    || a.cycleStartedAt !== b.cycleStartedAt
    || a.cycleEndsAt !== b.cycleEndsAt
    || a.monthlyAllowance !== b.monthlyAllowance
    || a.usedThisCycle !== b.usedThisCycle
    || a.topupBalance !== b.topupBalance
    || a.renewsAt !== b.renewsAt;
};

const upsertAccount = async (db, account) => {
  await run(
    db,
    `INSERT INTO billing_accounts (
      user_id,
      plan,
      provider,
      cycle_started_at,
      cycle_ends_at,
      monthly_allowance,
      used_this_cycle,
      topup_balance,
      renews_at,
      updated_at
    ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
    ON CONFLICT (user_id) DO UPDATE SET
      plan = EXCLUDED.plan,
      provider = EXCLUDED.provider,
      cycle_started_at = EXCLUDED.cycle_started_at,
      cycle_ends_at = EXCLUDED.cycle_ends_at,
      monthly_allowance = EXCLUDED.monthly_allowance,
      used_this_cycle = EXCLUDED.used_this_cycle,
      topup_balance = EXCLUDED.topup_balance,
      renews_at = EXCLUDED.renews_at,
      updated_at = EXCLUDED.updated_at`,
    [
      account.userId,
      account.plan,
      account.provider,
      account.cycleStartedAt,
      account.cycleEndsAt,
      account.monthlyAllowance,
      account.usedThisCycle,
      account.topupBalance,
      account.renewsAt,
      account.updatedAt,
    ],
  );
};

const getOrCreateAccount = async (db, userId) => {
  const row = await get(
    db,
    `SELECT
      user_id AS "userId",
      plan,
      provider,
      cycle_started_at AS "cycleStartedAt",
      cycle_ends_at AS "cycleEndsAt",
      monthly_allowance AS "monthlyAllowance",
      used_this_cycle AS "usedThisCycle",
      topup_balance AS "topupBalance",
      renews_at AS "renewsAt",
      updated_at AS "updatedAt"
    FROM billing_accounts
    WHERE user_id = $1`,
    [userId],
  );

  const now = new Date();
  if (!row) {
    const created = buildDefaultAccount(userId, now);
    await upsertAccount(db, created);
    return created;
  }

  const existing = normalizeAccountRow(row);
  const aligned = alignAccountToCurrentCycle(existing, now);
  if (accountChanged(existing, aligned)) {
    aligned.updatedAt = nowIso();
    await upsertAccount(db, aligned);
  }
  return aligned;
};

const getAvailableCredits = (account) => {
  const monthlyRemaining = Math.max(0, account.monthlyAllowance - account.usedThisCycle);
  return monthlyRemaining + Math.max(0, account.topupBalance);
};

const buildBillingSummary = (account) => {
  return {
    entitlement: {
      plan: account.plan,
      provider: account.provider,
      status: account.plan === 'pro' ? 'active' : 'inactive',
      renewsAt: account.renewsAt,
    },
    credits: {
      monthlyAllowance: account.monthlyAllowance,
      usedThisCycle: account.usedThisCycle,
      topupBalance: account.topupBalance,
      available: getAvailableCredits(account),
      cycleStartedAt: account.cycleStartedAt,
      cycleEndsAt: account.cycleEndsAt,
    },
    availableProducts: AVAILABLE_PRODUCTS,
  };
};

const isSupportedTopupProduct = (productId) => {
  return typeof productId === 'string'
    && productId.startsWith('topup_')
    && typeof TOPUP_CREDITS_BY_PRODUCT[productId] === 'number';
};

const normalizeRevenueCatTransactions = (customerInfo) => {
  const nonSubscriptions = customerInfo?.nonSubscriptions;
  if (!nonSubscriptions || typeof nonSubscriptions !== 'object') return [];
  return Object.values(nonSubscriptions).flatMap((entries) => (Array.isArray(entries) ? entries : []));
};

const summarizeRevenueCatCustomerInfo = (customerInfo) => {
  const activeEntitlements = customerInfo?.entitlements?.active || {};
  return {
    appUserId: customerInfo?.appUserId || customerInfo?.app_user_id || null,
    originalAppUserId: customerInfo?.originalAppUserId || customerInfo?.original_app_user_id || null,
    activeEntitlements: Object.entries(activeEntitlements).map(([id, entitlement]) => ({
      id,
      productIdentifier: entitlement?.productIdentifier || null,
      expirationDate: entitlement?.expirationDate || entitlement?.expiresDate || null,
    })),
    allPurchasedProductIdentifiers: Array.isArray(customerInfo?.allPurchasedProductIdentifiers)
      ? customerInfo.allPurchasedProductIdentifiers
      : [],
    nonSubscriptionTransactions: normalizeRevenueCatTransactions(customerInfo).map((transaction) => ({
      productIdentifier: transaction?.productIdentifier || null,
      transactionIdentifier: transaction?.transactionIdentifier || transaction?.transactionId || null,
    })),
  };
};

const getValidProEntitlement = (customerInfo) => {
  const activeEntitlements = customerInfo?.entitlements?.active || {};
  const proEntitlement = activeEntitlements[REVENUECAT_PRO_ENTITLEMENT_ID];
  if (!proEntitlement) {
    return null;
  }

  if (
    typeof proEntitlement?.productIdentifier === 'string'
    && SUPPORTED_SUBSCRIPTION_PRODUCTS.has(proEntitlement.productIdentifier)
  ) {
    return proEntitlement;
  }

  // Fallback: entitlement is active but backed by a non-subscription product (e.g. a topup
  // that was previously misconfigured to grant the pro entitlement). If the user also has a
  // supported subscription product in their purchase history, honour the entitlement anyway.
  const purchased = Array.isArray(customerInfo?.allPurchasedProductIdentifiers)
    ? customerInfo.allPurchasedProductIdentifiers
    : [];
  const hasSubscription = purchased.some((id) => SUPPORTED_SUBSCRIPTION_PRODUCTS.has(id));
  if (hasSubscription) {
    console.warn('[Billing] Pro entitlement backed by unsupported product but subscription found — honouring entitlement', summarizeRevenueCatCustomerInfo(customerInfo));
    return proEntitlement;
  }

  console.warn('[Billing] Ignoring unsupported RevenueCat pro entitlement', summarizeRevenueCatCustomerInfo(customerInfo));
  return null;
};

const applyRevenueCatEntitlementState = (account, options) => {
  const now = new Date();
  const nextPlan = options.active ? 'pro' : 'free';
  const planChanged = account.plan !== nextPlan;
  account.plan = nextPlan;
  account.provider = 'revenuecat';
  account.monthlyAllowance = getMonthlyAllowanceForPlan(account.plan);
  account.renewsAt = options.active ? options.renewsAt || account.renewsAt || addDays(now, 30).toISOString() : null;

  if (planChanged) {
    const { cycleStartedAt, cycleEndsAt } = getCycleBounds(now);
    account.cycleStartedAt = cycleStartedAt.toISOString();
    account.cycleEndsAt = cycleEndsAt.toISOString();
    account.usedThisCycle = 0;
  }
};

const parseStoredJson = (raw) => {
  if (raw == null) return null;
  if (typeof raw === 'object') return raw;
  if (typeof raw !== 'string') return null;
  try {
    return JSON.parse(raw);
  } catch {
    return null;
  }
};

const readIdempotentValue = async (db, key) => {
  const row = await get(
    db,
    'SELECT response_json AS "responseJson" FROM billing_idempotency WHERE id = $1',
    [key],
  );
  if (!row) return null;
  return parseStoredJson(row.responseJson);
};

const writeIdempotentValue = async (db, key, value) => {
  await run(
    db,
    `INSERT INTO billing_idempotency (id, response_json, created_at)
     VALUES ($1, CAST($2 AS jsonb), $3)
     ON CONFLICT (id) DO UPDATE SET
       response_json = EXCLUDED.response_json,
       created_at = EXCLUDED.created_at`,
    [key, JSON.stringify(value), nowIso()],
  );
};

const grantRevenueCatTopupIfNeeded = async (db, account, transactionId, productId) => {
  if (!transactionId || !isSupportedTopupProduct(productId)) {
    return false;
  }
  const idempotencyId = `revenuecat-topup:${transactionId}`;
  const existing = await readIdempotentValue(db, idempotencyId);
  if (existing) return false;
  account.topupBalance += TOPUP_CREDITS_BY_PRODUCT[productId];
  await writeIdempotentValue(db, idempotencyId, { transactionId, productId, creditedAt: nowIso() });
  return true;
};

const syncRevenueCatCustomerInfo = async (db, userId, customerInfo, options = {}) => {
  return runInTransaction(db, async (tx) => {
    const account = await getOrCreateAccount(tx, userId);
    const proEntitlement = getValidProEntitlement(customerInfo);
    const source = typeof options.source === 'string' ? options.source : 'app_init';

    console.log('[Billing] Syncing RevenueCat customer info', {
      userId,
      source,
      customerInfo: summarizeRevenueCatCustomerInfo(customerInfo),
    });

    if (source !== 'topup_purchase') {
      applyRevenueCatEntitlementState(account, {
        active: Boolean(proEntitlement),
        renewsAt: asIsoDate(proEntitlement?.expirationDate || proEntitlement?.expiresDate),
      });
    }

    const transactions = normalizeRevenueCatTransactions(customerInfo);
    for (const transaction of transactions) {
      await grantRevenueCatTopupIfNeeded(
        tx,
        account,
        transaction?.transactionIdentifier || transaction?.transactionId,
        transaction?.productIdentifier,
      );
    }

    // Fallback: also check active entitlements for topup products.
    // This handles cases where a topup product is misconfigured in RevenueCat
    // to grant an entitlement instead of being treated as a consumable.
    const rawActiveEntitlements = Object.values(customerInfo?.entitlements?.active || {});
    for (const entitlement of rawActiveEntitlements) {
      const productId = entitlement?.productIdentifier;
      if (isSupportedTopupProduct(productId)) {
        const purchaseDate = entitlement?.latestPurchaseDate || entitlement?.originalPurchaseDate;
        const txId = purchaseDate ? `entitlement:${productId}:${purchaseDate}` : null;
        await grantRevenueCatTopupIfNeeded(tx, account, txId, productId);
      }
    }

    account.updatedAt = nowIso();
    await upsertAccount(tx, account);
    return {
      billing: buildBillingSummary(account),
      syncedAt: nowIso(),
    };
  });
};

const shouldGrantRevenueCatSubscription = (eventType) => {
  return new Set([
    'INITIAL_PURCHASE',
    'RENEWAL',
    'PRODUCT_CHANGE',
    'UNCANCELLATION',
    'TEMPORARY_ENTITLEMENT_GRANT',
  ]).has(String(eventType || '').toUpperCase());
};

const shouldRevokeRevenueCatSubscription = (eventType) => {
  return new Set([
    'EXPIRATION',
    'SUBSCRIPTION_PAUSED',
  ]).has(String(eventType || '').toUpperCase());
};

const syncRevenueCatWebhookEvent = async (db, eventPayload) => {
  const appUserId = String(
    eventPayload?.app_user_id
    || eventPayload?.appUserId
    || eventPayload?.original_app_user_id
    || '',
  ).trim();
  if (!appUserId) {
    const error = new Error('RevenueCat webhook is missing app_user_id.');
    error.code = 'BAD_REQUEST';
    error.status = 400;
    throw error;
  }

  return runInTransaction(db, async (tx) => {
    const account = await getOrCreateAccount(tx, appUserId);
    const eventType = String(eventPayload?.type || '').toUpperCase();
    const productId = typeof eventPayload?.product_id === 'string' ? eventPayload.product_id : '';
    const entitlementIds = Array.isArray(eventPayload?.entitlement_ids) ? eventPayload.entitlement_ids : [];
    const hasSubscriptionProduct = SUPPORTED_SUBSCRIPTION_PRODUCTS.has(productId);
    const hasTopupProduct = isSupportedTopupProduct(productId);
    const affectsProEntitlement = hasSubscriptionProduct
      || (entitlementIds.includes(REVENUECAT_PRO_ENTITLEMENT_ID) && !hasTopupProduct);

    if (entitlementIds.includes(REVENUECAT_PRO_ENTITLEMENT_ID) && hasTopupProduct) {
      console.warn('[Billing] Ignoring RevenueCat webhook entitlement for top-up product', {
        appUserId,
        eventType,
        productId,
        entitlementIds,
      });
    }

    if (affectsProEntitlement && shouldGrantRevenueCatSubscription(eventType)) {
      applyRevenueCatEntitlementState(account, {
        active: true,
        renewsAt: asIsoDate(eventPayload?.expiration_at_ms || eventPayload?.expiration_at),
      });
    } else if (affectsProEntitlement && shouldRevokeRevenueCatSubscription(eventType)) {
      applyRevenueCatEntitlementState(account, {
        active: false,
        renewsAt: null,
      });
    }

    if (isSupportedTopupProduct(productId)) {
      await grantRevenueCatTopupIfNeeded(
        tx,
        account,
        eventPayload?.transaction_id || eventPayload?.store_transaction_id || eventPayload?.id,
        productId,
      );
    }

    account.updatedAt = nowIso();
    await upsertAccount(tx, account);
    return {
      billing: buildBillingSummary(account),
      syncedAt: nowIso(),
    };
  });
};

const consumeCredits = (account, cost) => {
  if (cost <= 0) return 0;

  const available = getAvailableCredits(account);
  if (available < cost) {
    throw createInsufficientCreditsError(cost, available);
  }

  let remaining = cost;
  const monthlyRemaining = Math.max(0, account.monthlyAllowance - account.usedThisCycle);
  if (monthlyRemaining > 0) {
    const monthlyUsage = Math.min(monthlyRemaining, remaining);
    account.usedThisCycle += monthlyUsage;
    remaining -= monthlyUsage;
  }

  if (remaining > 0 && account.topupBalance > 0) {
    const topupUsage = Math.min(account.topupBalance, remaining);
    account.topupBalance -= topupUsage;
    remaining -= topupUsage;
  }

  return cost;
};

const endpointKey = (scope, userId, idempotencyKey) => {
  return `endpoint:${scope}:${userId}:${idempotencyKey}`;
};

const chargeKey = (scope, userId, idempotencyKey) => {
  return `charge:${scope}:${userId}:${idempotencyKey}`;
};

const consumeCreditsWithIdempotency = async (db, userId, key, cost) => {
  return runInTransaction(db, async (tx) => {
    const existing = await readIdempotentValue(tx, key);
    if (existing && typeof existing.charged === 'number') return existing.charged;

    const account = await getOrCreateAccount(tx, userId);
    const charged = consumeCredits(account, cost);
    account.updatedAt = nowIso();
    await upsertAccount(tx, account);
    await writeIdempotentValue(tx, key, { charged });
    return charged;
  });
};

const getBillingSummary = async (db, userId) => {
  if (userId === 'guest') {
    return {
      entitlement: { plan: 'free', provider: 'mock', status: 'active', renewsAt: null },
      credits: {
        monthlyAllowance: 5,
        usedThisCycle: 0,
        topupBalance: 0,
        available: 5,
        cycleStartedAt: nowIso(),
        cycleEndsAt: nowIso(),
      },
      availableProducts: AVAILABLE_PRODUCTS,
    };
  }
  return runInTransaction(db, async (tx) => {
    const account = await getOrCreateAccount(tx, userId);
    account.updatedAt = nowIso();
    await upsertAccount(tx, account);
    return buildBillingSummary(account);
  });
};

const getAccountSnapshot = async (db, userId) => {
  if (userId === 'guest') {
    return {
      userId: 'guest',
      plan: 'free',
      provider: 'mock',
      cycleStartedAt: nowIso(),
      cycleEndsAt: nowIso(),
      monthlyAllowance: 5,
      usedThisCycle: 0,
      topupBalance: 0,
      renewsAt: null,
      updatedAt: nowIso(),
    };
  }
  return runInTransaction(db, async (tx) => {
    const account = await getOrCreateAccount(tx, userId);
    account.updatedAt = nowIso();
    await upsertAccount(tx, account);
    return account;
  });
};

const getEndpointResponse = async (db, key) => {
  const cached = await readIdempotentValue(db, key);
  return cached || null;
};

const storeEndpointResponse = async (db, key, response) => {
  await writeIdempotentValue(db, key, response);
};

const simulatePurchase = async (db, userId, idempotencyKey, productId) => {
  const endpointId = endpointKey('simulate-purchase', userId, idempotencyKey);
  const cached = await getEndpointResponse(db, endpointId);
  if (cached) return cached;

  return runInTransaction(db, async (tx) => {
    const existingInsideTx = await readIdempotentValue(tx, endpointId);
    if (existingInsideTx) return existingInsideTx;

    const account = await getOrCreateAccount(tx, userId);

    if (productId === 'monthly_pro' || productId === 'yearly_pro') {
      const now = new Date();
      const { cycleStartedAt, cycleEndsAt } = getCycleBounds(now);
      account.plan = 'pro';
      account.provider = 'revenuecat';
      account.monthlyAllowance = PRO_MONTHLY_CREDITS;
      account.usedThisCycle = 0;
      account.cycleStartedAt = cycleStartedAt.toISOString();
      account.cycleEndsAt = cycleEndsAt.toISOString();
      account.renewsAt = addDays(now, 30).toISOString();
    } else {
      const credits = TOPUP_CREDITS_BY_PRODUCT[productId];
      if (typeof credits !== 'number') {
        const error = new Error(`Unsupported product: ${productId}`);
        error.code = 'BAD_REQUEST';
        error.status = 400;
        throw error;
      }
      account.topupBalance += credits;
    }

    account.updatedAt = nowIso();
    await upsertAccount(tx, account);

    const payload = {
      appliedProduct: productId,
      billing: buildBillingSummary(account),
    };
    await storeEndpointResponse(tx, endpointId, payload);
    return payload;
  });
};

const simulateWebhook = async (db, userId, idempotencyKey, event, payload = {}) => {
  const endpointId = endpointKey('simulate-webhook', userId, idempotencyKey);
  const cached = await getEndpointResponse(db, endpointId);
  if (cached) return cached;

  return runInTransaction(db, async (tx) => {
    const existingInsideTx = await readIdempotentValue(tx, endpointId);
    if (existingInsideTx) return existingInsideTx;

    const account = await getOrCreateAccount(tx, userId);

    if (event === 'entitlement_granted') {
      const now = new Date();
      const { cycleStartedAt, cycleEndsAt } = getCycleBounds(now);
      account.plan = 'pro';
      account.provider = 'revenuecat';
      account.monthlyAllowance = PRO_MONTHLY_CREDITS;
      account.usedThisCycle = 0;
      account.cycleStartedAt = cycleStartedAt.toISOString();
      account.cycleEndsAt = cycleEndsAt.toISOString();
      account.renewsAt = addDays(now, 30).toISOString();
    } else if (event === 'entitlement_revoked') {
      const now = new Date();
      const { cycleStartedAt, cycleEndsAt } = getCycleBounds(now);
      account.plan = 'free';
      account.provider = 'revenuecat';
      account.monthlyAllowance = FREE_MONTHLY_CREDITS;
      account.usedThisCycle = 0;
      account.cycleStartedAt = cycleStartedAt.toISOString();
      account.cycleEndsAt = cycleEndsAt.toISOString();
      account.renewsAt = null;
    } else if (event === 'topup_granted') {
      const credits = Math.max(1, Number(payload.credits) || TOPUP_DEFAULT_CREDITS);
      account.topupBalance += credits;
    } else if (event === 'credits_depleted') {
      account.usedThisCycle = account.monthlyAllowance;
      account.topupBalance = 0;
    } else {
      const error = new Error(`Unsupported webhook event: ${event}`);
      error.code = 'BAD_REQUEST';
      error.status = 400;
      throw error;
    }

    account.updatedAt = nowIso();
    await upsertAccount(tx, account);

    const payloadResponse = {
      event,
      billing: buildBillingSummary(account),
    };
    await storeEndpointResponse(tx, endpointId, payloadResponse);
    return payloadResponse;
  });
};

const ensureBillingSchema = async (db) => {
  await run(
    db,
    `CREATE TABLE IF NOT EXISTS billing_accounts (
      user_id TEXT PRIMARY KEY,
      plan TEXT NOT NULL DEFAULT 'free',
      provider TEXT NOT NULL DEFAULT 'revenuecat',
      cycle_started_at TIMESTAMPTZ NOT NULL,
      cycle_ends_at TIMESTAMPTZ NOT NULL,
      monthly_allowance INTEGER NOT NULL DEFAULT 15,
      used_this_cycle INTEGER NOT NULL DEFAULT 0,
      topup_balance INTEGER NOT NULL DEFAULT 0,
      renews_at TIMESTAMPTZ,
      updated_at TIMESTAMPTZ NOT NULL
    )`,
  );

  await run(
    db,
    `CREATE TABLE IF NOT EXISTS billing_idempotency (
      id TEXT PRIMARY KEY,
      response_json JSONB NOT NULL,
      created_at TIMESTAMPTZ NOT NULL
    )`,
  );

  await run(
    db,
    `CREATE INDEX IF NOT EXISTS idx_billing_idempotency_created_at
     ON billing_idempotency (created_at DESC)`,
  );
};

const isInsufficientCreditsError = (error) => {
  return Boolean(error && typeof error === 'object' && error.code === 'INSUFFICIENT_CREDITS');
};

module.exports = {
  AVAILABLE_PRODUCTS,
  chargeKey,
  consumeCreditsWithIdempotency,
  endpointKey,
  ensureBillingSchema,
  getAccountSnapshot,
  getBillingSummary,
  getEndpointResponse,
  getMonthlyAllowanceForPlan,
  isInsufficientCreditsError,
  runInTransaction,
  simulatePurchase,
  simulateWebhook,
  syncRevenueCatCustomerInfo,
  syncRevenueCatWebhookEvent,
  storeEndpointResponse,
};
