/**
 * routes/webhook.js
 * Stripe webhook — raw body, signature verification.
 * Mounted at /webhook/stripe in app.js.
 */

'use strict';

const express = require('express');
const stripe  = require('../config/stripe');
const User    = require('../models/user');
const Audit   = require('../models/auditLog');
const { getStripePriceId, PLANS } = require('../config/plans');
const email   = require('../services/email');
const { db }  = require('../config/db');
const sec     = require('../config/security');

const router = express.Router();

// Build a reverse-lookup: for every Stripe price id we care about,
// figure out which {plan, interval} it corresponds to. Covers both
// monthly and annual prices across all 3 paid plans (6 entries).
function priceIdToPlan(priceId) {
  for (const k of Object.keys(PLANS)) {
    for (const iv of ['month', 'year']) {
      if (getStripePriceId(k, iv) === priceId) {
        return { plan: k, interval: iv };
      }
    }
  }
  return { plan: 'free', interval: 'month' };
}

const insertEvent = db.prepare(`INSERT OR IGNORE INTO webhook_events (id, source, received_at) VALUES (?, ?, ?)`);
const cleanupEvents = db.prepare(`DELETE FROM webhook_events WHERE received_at < ?`);

router.post('/', (req, res) => {
  if (!stripe) return res.status(500).send('Stripe not configured');
  const sig = req.headers['stripe-signature'];
  let event;
  try {
    event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
  } catch (err) {
    console.error('[stripe webhook] signature verify failed:', err.message);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Replay protection: reject if event is too old OR we've already processed it
  const eventAgeS = (Date.now() / 1000) - event.created;
  if (eventAgeS > sec.WEBHOOK_REPLAY_MAX_AGE_S) {
    console.warn('[stripe webhook] rejecting event too old:', event.id, `(${eventAgeS}s)`);
    return res.status(400).send('Event too old (replay protection)');
  }
  cleanupEvents.run(Date.now() - 24 * 60 * 60 * 1000);
  const inserted = insertEvent.run(event.id, 'stripe', Date.now());
  if (inserted.changes === 0) {
    console.log('[stripe webhook] duplicate event ignored:', event.id);
    return res.json({ received: true, duplicate: true });
  }

  try {
    switch (event.type) {
      case 'checkout.session.completed': {
        const s = event.data.object;
        const userId  = parseInt(s.metadata && s.metadata.user_id, 10);
        const plan    = s.metadata && s.metadata.plan;
        const interval = s.metadata && s.metadata.interval;
        const quantity = s.metadata && parseInt(s.metadata.quantity, 10);
        const kind    = (s.metadata && s.metadata.kind) || 'subscription';
        if (userId && s.customer) {
          User.setStripeCustomerId(userId, s.customer);
          Audit.log(null, 'stripe.checkout.completed', `user:${userId}`,
            { plan, interval, quantity, kind, session_id: s.id, mode: s.mode });
        }
        if (kind === 'credit_pack') {
          // One-time payment → grant the credits defined on the plan.
          const planDef = PLANS[plan];
          if (planDef && planDef.grants && userId) {
            User.grantCredits(userId, planDef.grants.scans || 0, planDef.grants.takedowns || 0);
            Audit.log(null, 'stripe.credits.granted', `user:${userId}`,
              { plan, scans: planDef.grants.scans, takedowns: planDef.grants.takedowns,
                payment_intent: s.payment_intent });
          }
        } else if (userId && s.subscription) {
          User.setStripeSubscriptionId(userId, s.subscription, plan, interval, quantity);
        }
        break;
      }
      // payment_intent.succeeded fires for one-time charges (mode='payment').
      // Some Stripe configurations deliver this in addition to checkout.session.completed;
      // granting credits here is idempotent because we always ADD, not set.
      case 'payment_intent.succeeded': {
        const pi = event.data.object;
        const meta = pi.metadata || {};
        const userId = parseInt(meta.user_id, 10);
        const plan = meta.plan;
        const kind = meta.kind || 'subscription';
        if (kind === 'credit_pack' && userId && plan) {
          const planDef = PLANS[plan];
          if (planDef && planDef.grants) {
            User.grantCredits(userId, planDef.grants.scans || 0, planDef.grants.takedowns || 0);
            Audit.log(null, 'stripe.credits.granted.pi', `user:${userId}`,
              { plan, scans: planDef.grants.scans, takedowns: planDef.grants.takedowns,
                payment_intent: pi.id });
          }
        }
        break;
      }
      case 'customer.subscription.updated': {
        const sub = event.data.object;
        const item  = sub.items.data[0];
        const priceId = item && item.price && item.price.id;
        const quantity = item ? item.quantity : 1;
        // Look up the plan AND the interval (month vs year) by price id.
        // This works for both flat and per-seat plans — the quantity
        // is read from the subscription item, not from metadata.
        const { plan: planKey, interval } = priceIdToPlan(priceId);
        const customerId = typeof sub.customer === 'string' ? sub.customer : sub.customer.id;
        const u = User.getByStripeCustomer(customerId);
        if (u) {
          User.setStripeSubscriptionId(u.id, sub.id, planKey, interval, quantity);
          User.clearFailedPayment(u.id);
          Audit.log(u, 'stripe.subscription.updated', `user:${u.id}`,
            { plan: planKey, interval, quantity, status: sub.status });
        }
        break;
      }
      case 'customer.subscription.deleted': {
        const sub = event.data.object;
        const customerId = typeof sub.customer === 'string' ? sub.customer : sub.customer.id;
        const u = User.getByStripeCustomer(customerId);
        if (u) {
          User.setStripeSubscriptionId(u.id, sub.id, 'free');
          Audit.log(u, 'stripe.subscription.deleted', `user:${u.id}`);
        }
        break;
      }
      case 'invoice.payment_failed': {
        const inv = event.data.object;
        const customerId = typeof inv.customer === 'string' ? inv.customer : inv.customer.id;
        const u = User.getByStripeCustomer(customerId);
        if (u) {
          User.markFailedPayment(u.id);
          Audit.log(u, 'stripe.payment_failed', `user:${u.id}`, { invoice_id: inv.id });
          email.sendRaw(u.email,
            'Payment failed — Shield',
            `Hi ${u.name},\n\nWe were unable to process your latest invoice. Please update your card from your billing portal:\n\n  ${process.env.APP_URL}/billing\n\nIf you believe this is in error, reply to this email.\n\n— Shield`,
            { tag: 'billing' }
          ).catch(err => console.warn('[webhook] payment-failed email:', err.message));
        }
        break;
      }
      default:
        // unhandled event types are fine
        break;
    }
    res.json({ received: true });
  } catch (err) {
    console.error('[stripe webhook] handler error:', err);
    res.status(500).send('Webhook handler failed');
  }
});

module.exports = router;
