import type { Plan } from '@bmm/llm';
import Stripe from 'stripe';
import { config } from '../config.js';
import { getRedis } from './redis.js';

/**
 * Stripe client (null when no secret key is configured — e.g. in dev/test).
 * `apiVersion` pinned to the current default to avoid silent breakage when
 * Stripe rolls out new defaults.
 */
export const stripe: Stripe | null = config.STRIPE_SECRET_KEY
  ? new Stripe(config.STRIPE_SECRET_KEY, {
      // Must match the version the installed SDK (stripe@22) is built against —
      // its types expose ui_mode: 'embedded_page', which only exists from this
      // version on. Pinning the older '2025-10-29.acacia' made Stripe reject the
      // embedded checkout create call (acacia still used ui_mode: 'embedded').
      apiVersion: '2026-04-22.dahlia',
      typescript: true,
      // Fail fast + visibly. Without a tight timeout, a wedged Stripe call (bad
      // version, egress hiccup) hangs past Cloudflare's ~100s edge limit, and
      // CF returns its own 5xx WITHOUT our CORS headers — which surfaces in the
      // browser as an opaque "No Access-Control-Allow-Origin" error instead of
      // the real failure. 20s keeps us well inside the edge limit so the handler
      // returns a proper 502 (with CORS) the client can actually read.
      timeout: 20_000,
      maxNetworkRetries: 2,
    })
  : null;

export type PriceTier = 'pro_monthly' | 'pro_yearly' | 'team_monthly' | 'team_yearly';

export function priceIdForTier(tier: PriceTier): string | undefined {
  switch (tier) {
    case 'pro_monthly':
      return config.STRIPE_PRICE_PRO_MONTHLY;
    case 'pro_yearly':
      return config.STRIPE_PRICE_PRO_YEARLY;
    case 'team_monthly':
      return config.STRIPE_PRICE_TEAM_MONTHLY;
    case 'team_yearly':
      return config.STRIPE_PRICE_TEAM_YEARLY;
  }
}

/** Reverse map: which plan does a Stripe price id belong to. Unknown → hobby. */
export function planFromPriceId(priceId: string | undefined): Plan {
  if (!priceId) return 'hobby';
  if (
    priceId === config.STRIPE_PRICE_PRO_MONTHLY ||
    priceId === config.STRIPE_PRICE_PRO_YEARLY
  ) {
    return 'pro';
  }
  if (
    priceId === config.STRIPE_PRICE_TEAM_MONTHLY ||
    priceId === config.STRIPE_PRICE_TEAM_YEARLY
  ) {
    return 'team';
  }
  return 'hobby';
}

/**
 * Idempotency for Stripe webhooks. Stripe retries failed deliveries — we must
 * dedupe by event.id or we'd e.g. double-cancel a subscription. SET NX with a
 * 7-day TTL covers Stripe's full retry window.
 *
 * Returns true if this event was already processed (caller should skip).
 */
export async function isDuplicateEvent(eventId: string): Promise<boolean> {
  const redis = getRedis();
  const key = `stripe:event:${eventId}`;
  const set = await redis.set(key, '1', 'EX', 7 * 24 * 60 * 60, 'NX');
  return set === null;
}

/**
 * Roll back the idempotency marker for an event whose handler FAILED, so
 * Stripe's retry re-processes it. Without this, the marker set by the failed
 * first attempt makes every retry look like a duplicate and the event is lost
 * forever (e.g. a paid org that never gets upgraded). (BILL-003)
 */
export async function clearProcessedEvent(eventId: string): Promise<void> {
  const redis = getRedis();
  await redis.del(`stripe:event:${eventId}`);
}

/**
 * Sanity-check that price-id env vars actually contain price ids — a common
 * setup mistake is to paste the product id (prod_…) instead. Logs loudly on
 * boot so we discover misconfiguration before the first checkout attempt.
 */
export function validateStripePriceConfig(log: { warn: (msg: string) => void }): void {
  const checks: Array<[string, string | undefined]> = [
    ['STRIPE_PRICE_PRO_MONTHLY', config.STRIPE_PRICE_PRO_MONTHLY],
    ['STRIPE_PRICE_PRO_YEARLY', config.STRIPE_PRICE_PRO_YEARLY],
    ['STRIPE_PRICE_TEAM_MONTHLY', config.STRIPE_PRICE_TEAM_MONTHLY],
    ['STRIPE_PRICE_TEAM_YEARLY', config.STRIPE_PRICE_TEAM_YEARLY],
  ];
  for (const [name, value] of checks) {
    if (!value) continue;
    if (!value.startsWith('price_')) {
      log.warn(
        `[stripe] ${name} does not start with "price_" (got "${value.slice(0, 6)}…") — ` +
          'Stripe Checkout will reject this. Paste the PRICE id (price_…) from the product page, not the product id (prod_…).',
      );
    }
  }
}
