'use strict';

/**
 * DashCaddy Stripe client — DC-055 + DC-057.
 *
 * Thin wrapper around the Stripe SDK for the OUTBOUND side of one-time
 * license purchasing: creating Checkout Sessions that drive customers to
 * Stripe's hosted payment page.
 *
 * Inbound (webhook) handling lives in scripts/stripe-license-bridge.js
 * (DC-054 + DC-057) — that runs as its own process so the merchant's
 * Stripe webhook secret doesn't have to be loaded into the DashCaddy API
 * host process.
 *
 * # Pricing contract
 *
 *   One-time payments keyed by `productId` from src/billing/catalog.js:
 *
 *     pro-30d   → $20 USD, 30-day license
 *     pro-90d   → $50 USD, 90-day license
 *     pro-180d  → $70 USD, 180-day license
 *     pro-365d  → $99 USD, 365-day license
 *
 *   `mode: 'payment'` (NOT 'subscription'). The license is generated once
 *   per Checkout completion and the customer pastes it into their host.
 *   No recurring billing, no Stripe Customer object retained beyond the
 *   session.
 *
 * # Configuration (env vars)
 *
 *   Required to create sessions — failures are loud:
 *     STRIPE_SECRET_KEY              — Stripe API secret (sk_live_... | sk_test_...)
 *     STRIPE_PRICE_PRO_30D           — Stripe Price ID for the 30-day product
 *     STRIPE_PRICE_PRO_90D           — Stripe Price ID for the 90-day product
 *     STRIPE_PRICE_PRO_180D          — Stripe Price ID for the 180-day product
 *     STRIPE_PRICE_PRO_365D          — Stripe Price ID for the 365-day product
 *     STRIPE_SUCCESS_URL             — (optional) success page URL override
 *     STRIPE_CANCEL_URL              — (optional) cancel page URL override
 *
 *   The Stripe Price IDs map 1:1 to catalog products. A product whose
 *   Stripe Price ID is unset cannot be purchased (returns
 *   STRIPE_NOT_CONFIGURED).
 *
 * # Metadata contract (DC-057)
 *
 *   The Checkout session carries `metadata.productId` (= one of the
 *   catalog IDs). The webhook bridge reads this field back, maps to the
 *   catalog, and generates the matching license duration.
 *
 *   Why productId and not (e.g.) durationDays: the catalog is the single
 *   source of truth. If pricing changes (e.g. new tier added), only the
 *   catalog and the bridge change — the Checkout metadata stays abstract.
 *
 * Tested in __tests__/billing/stripe-client.test.js with mocked Stripe SDK.
 */

let stripeSdk = null;
function _loadStripeSdk() {
  if (stripeSdk) return stripeSdk;
  // Lazy require so tests can install a mock BEFORE first call.
  stripeSdk = require('stripe');
  return stripeSdk;
}

/**
 * Inject a mock Stripe SDK. Used by tests; never call in production code.
 * @param {Object} mockSdk - Object with `checkout.sessions.create` (and any other surface) the tests want to stub.
 */
function _setStripeSdk(mockSdk) {
  stripeSdk = mockSdk;
}

const catalog = require('./catalog');

/**
 * Read the active configuration. Throws if STRIPE_SECRET_KEY is missing
 * OR if no product has its Stripe Price ID configured — both are loud
 * failures so an operator notices instead of seeing silent 500s.
 *
 * @param {Object} [env] - process.env by default; tests pass custom env.
 * @returns {Object} config snapshot for this invocation
 */
function _readConfig(env = process.env) {
  const secretKey = env.STRIPE_SECRET_KEY;
  if (!secretKey) {
    const err = new Error(
      'Stripe billing is not configured. Missing env var: STRIPE_SECRET_KEY. ' +
      'Set it in /opt/dashcaddy/.env and restart the API.'
    );
    err.code = 'STRIPE_NOT_CONFIGURED';
    err.statusCode = 503;
    err.missing = ['STRIPE_SECRET_KEY'];
    throw err;
  }
  return { secretKey };
}

/**
 * Validate the requested productId and resolve its Stripe Price ID.
 * Throws with a structured error if the productId is unknown OR if the
 * product's Stripe Price ID env var is not configured.
 *
 * @param {string} productId
 * @param {Object} env
 * @returns {Object} catalog product entry
 */
function _resolveProduct(productId, env = process.env) {
  if (!productId || typeof productId !== 'string') {
    const err = new Error('productId is required');
    err.code = 'INVALID_PRODUCT_ID';
    err.statusCode = 400;
    err.field = 'productId';
    throw err;
  }
  const product = catalog.getProduct(productId);
  if (!product) {
    const err = new Error(`Unknown productId: ${productId}. Valid: ${catalog.PRODUCTS.map(p => p.id).join(', ')}`);
    err.code = 'INVALID_PRODUCT_ID';
    err.statusCode = 400;
    err.field = 'productId';
    throw err;
  }
  const priceId = catalog.getConfiguredPrice(product, env);
  if (!priceId) {
    const err = new Error(
      `Product ${productId} is not configured for purchase. Missing env var: ${product.priceEnv}. ` +
      `Create the Stripe Price and set the env var in /opt/dashcaddy/.env, then restart the API.`
    );
    err.code = 'STRIPE_NOT_CONFIGURED';
    err.statusCode = 503;
    err.missing = [product.priceEnv];
    err.productId = productId;
    throw err;
  }
  return { product, priceId };
}

/**
 * Create a Stripe Checkout Session for a one-time DashCaddy Pro purchase.
 *
 * @param {Object} opts
 * @param {string} opts.productId - catalog id: 'pro-30d' | 'pro-90d' | 'pro-180d' | 'pro-365d'
 * @param {string} [opts.customerEmail] - email to prefill on Checkout (optional)
 * @param {string} [opts.origin] - request origin (e.g. 'https://status.sami') used to build success/cancel URLs
 * @returns {Promise<{ id: string, url: string }>}
 * @throws Error with `.code` and `.statusCode` on configuration/validation failure
 */
async function createCheckoutSession({ productId, customerEmail, origin }) {
  const config = _readConfig();
  const { product, priceId } = _resolveProduct(productId);
  const stripe = _loadStripeSdk();
  const api = stripe(config.secretKey);

  const successUrl = process.env.STRIPE_SUCCESS_URL
    || (origin ? `${origin}/billing/success?session_id={CHECKOUT_SESSION_ID}` : '/billing/success?session_id={CHECKOUT_SESSION_ID}');
  const cancelUrl = process.env.STRIPE_CANCEL_URL
    || (origin ? `${origin}/pricing` : '/pricing');

  // mode: 'payment' (one-time, NOT subscription). The license is generated
  // once on `checkout.session.completed` and the customer pastes it into
  // their host. No Customer object, no recurring billing.
  //
  // metadata.productId is the contract with the webhook bridge — it maps
  // back to a catalog entry to get the license duration. If the catalog
  // grows, only the bridge needs to change.
  const params = {
    mode: 'payment',
    line_items: [{ price: priceId, quantity: 1 }],
    success_url: successUrl,
    cancel_url: cancelUrl,
    metadata: {
      productId: product.id,
      product: 'dashcaddy-pro',
    },
    // payment_intent_data carries metadata to the PaymentIntent too, so
    // any downstream Stripe→bridge plumbing that reads PI metadata still
    // gets the productId. (Stripe's webhook includes the PI on
    // checkout.session.completed for retrieval but the canonical metadata
    // field for session-level events is the top-level metadata.)
    payment_intent_data: {
      metadata: { productId: product.id, product: 'dashcaddy-pro' },
    },
    allow_promotion_codes: true,
  };
  if (customerEmail) {
    params.customer_email = customerEmail;
  }

  const session = await api.checkout.sessions.create(params);

  return { id: session.id, url: session.url };
}

module.exports = {
  createCheckoutSession,
  // Test seams
  _setStripeSdk,
  _readConfig,
  _resolveProduct,
};
