// RoadPay — BlackRoad's Own Payment & Billing System
// pay.blackroad.io
// v1.0.0
//
// Own your customers, subscriptions, invoices, and payments.
// Stripe is just a dumb card charger underneath.

const VERSION = '1.0.0';

// ─── Schema ──────────────────────────────────────────────────────────────
const SCHEMA = [
  `CREATE TABLE IF NOT EXISTS customers (
    id TEXT PRIMARY KEY,
    email TEXT UNIQUE NOT NULL,
    name TEXT,
    stripe_customer_id TEXT,
    metadata TEXT DEFAULT '{}',
    status TEXT DEFAULT 'active',
    created_at TEXT DEFAULT (datetime('now')),
    updated_at TEXT DEFAULT (datetime('now'))
  )`,
  `CREATE TABLE IF NOT EXISTS plans (
    id TEXT PRIMARY KEY,
    name TEXT NOT NULL,
    slug TEXT UNIQUE NOT NULL,
    description TEXT,
    amount INTEGER NOT NULL,
    currency TEXT DEFAULT 'usd',
    interval TEXT DEFAULT 'month',
    interval_count INTEGER DEFAULT 1,
    features TEXT DEFAULT '[]',
    stripe_price_id TEXT,
    tier INTEGER DEFAULT 0,
    active INTEGER DEFAULT 1,
    metadata TEXT DEFAULT '{}',
    created_at TEXT DEFAULT (datetime('now'))
  )`,
  `CREATE TABLE IF NOT EXISTS addons (
    id TEXT PRIMARY KEY,
    name TEXT NOT NULL,
    slug TEXT UNIQUE NOT NULL,
    description TEXT,
    amount INTEGER NOT NULL,
    currency TEXT DEFAULT 'usd',
    interval TEXT DEFAULT 'month',
    stripe_price_id TEXT,
    active INTEGER DEFAULT 1,
    metadata TEXT DEFAULT '{}',
    created_at TEXT DEFAULT (datetime('now'))
  )`,
  `CREATE TABLE IF NOT EXISTS subscriptions (
    id TEXT PRIMARY KEY,
    customer_id TEXT NOT NULL REFERENCES customers(id),
    plan_id TEXT NOT NULL REFERENCES plans(id),
    stripe_subscription_id TEXT,
    status TEXT DEFAULT 'active',
    current_period_start TEXT,
    current_period_end TEXT,
    cancel_at TEXT,
    canceled_at TEXT,
    metadata TEXT DEFAULT '{}',
    created_at TEXT DEFAULT (datetime('now')),
    updated_at TEXT DEFAULT (datetime('now'))
  )`,
  `CREATE TABLE IF NOT EXISTS subscription_addons (
    id TEXT PRIMARY KEY,
    subscription_id TEXT NOT NULL REFERENCES subscriptions(id),
    addon_id TEXT NOT NULL REFERENCES addons(id),
    stripe_subscription_item_id TEXT,
    status TEXT DEFAULT 'active',
    created_at TEXT DEFAULT (datetime('now'))
  )`,
  `CREATE TABLE IF NOT EXISTS invoices (
    id TEXT PRIMARY KEY,
    customer_id TEXT NOT NULL REFERENCES customers(id),
    subscription_id TEXT REFERENCES subscriptions(id),
    stripe_invoice_id TEXT,
    amount INTEGER NOT NULL,
    currency TEXT DEFAULT 'usd',
    status TEXT DEFAULT 'draft',
    description TEXT,
    line_items TEXT DEFAULT '[]',
    due_date TEXT,
    paid_at TEXT,
    metadata TEXT DEFAULT '{}',
    created_at TEXT DEFAULT (datetime('now'))
  )`,
  `CREATE TABLE IF NOT EXISTS payments (
    id TEXT PRIMARY KEY,
    customer_id TEXT NOT NULL REFERENCES customers(id),
    invoice_id TEXT REFERENCES invoices(id),
    stripe_payment_id TEXT,
    amount INTEGER NOT NULL,
    currency TEXT DEFAULT 'usd',
    method TEXT DEFAULT 'card',
    status TEXT DEFAULT 'pending',
    metadata TEXT DEFAULT '{}',
    created_at TEXT DEFAULT (datetime('now'))
  )`,
  `CREATE TABLE IF NOT EXISTS events (
    id TEXT PRIMARY KEY,
    type TEXT NOT NULL,
    customer_id TEXT,
    entity_type TEXT,
    entity_id TEXT,
    data TEXT DEFAULT '{}',
    created_at TEXT DEFAULT (datetime('now'))
  )`,
  `CREATE INDEX IF NOT EXISTS idx_customers_email ON customers(email)`,
  `CREATE INDEX IF NOT EXISTS idx_customers_stripe ON customers(stripe_customer_id)`,
  `CREATE INDEX IF NOT EXISTS idx_subscriptions_customer ON subscriptions(customer_id)`,
  `CREATE INDEX IF NOT EXISTS idx_subscriptions_status ON subscriptions(status)`,
  `CREATE INDEX IF NOT EXISTS idx_invoices_customer ON invoices(customer_id)`,
  `CREATE INDEX IF NOT EXISTS idx_payments_customer ON payments(customer_id)`,
  `CREATE INDEX IF NOT EXISTS idx_events_type ON events(type)`,
  `CREATE INDEX IF NOT EXISTS idx_events_customer ON events(customer_id)`,
];

// ─── Seed Plans ──────────────────────────────────────────────────────────
const SEED_PLANS = [
  {
    id: 'plan_operator',
    name: 'Operator',
    slug: 'operator',
    description: 'Free forever. 1 agent, community access, basic tools.',
    amount: 0,
    interval: 'month',
    tier: 0,
    features: JSON.stringify([
      '1 AI agent',
      'Community support',
      'Basic RoadSearch',
      'Public dashboard',
    ]),
    stripe_price_id: 'price_1TAjzB3e5FMFdlFwJrJaGhtK',
  },
  {
    id: 'plan_rider',
    name: 'Rider',
    slug: 'rider',
    description: 'For builders. 5 agents, priority support, full API access.',
    amount: 2900,
    interval: 'month',
    tier: 1,
    features: JSON.stringify([
      '5 AI agents',
      'Priority support',
      'Full API access',
      'RoadSearch Pro',
      'Custom dashboards',
      'Webhook integrations',
    ]),
    stripe_price_id: 'price_1TAjzC3e5FMFdlFwPFUMCzSI',
  },
  {
    id: 'plan_paver',
    name: 'Paver',
    slug: 'paver',
    description: 'For teams. 25 agents, dedicated support, fleet management.',
    amount: 9900,
    interval: 'month',
    tier: 2,
    features: JSON.stringify([
      '25 AI agents',
      'Dedicated support',
      'Fleet management',
      'Team workspaces',
      'Advanced analytics',
      'Priority inference',
      'Custom models',
    ]),
    stripe_price_id: 'price_1TAjzD3e5FMFdlFwVNBkEJcU',
  },
  {
    id: 'plan_sovereign',
    name: 'Sovereign',
    slug: 'sovereign',
    description: 'Unlimited. Your own BlackRoad instance.',
    amount: 0,
    interval: 'month',
    tier: 3,
    features: JSON.stringify([
      'Unlimited agents',
      'Dedicated infrastructure',
      'Custom SLAs',
      'White-label option',
      'On-prem deployment',
      'Direct engineering support',
      'Custom integrations',
    ]),
    stripe_price_id: null,
  },
];

const SEED_ADDONS = [
  {
    id: 'addon_lucidia',
    name: 'Lucidia Enhanced',
    slug: 'lucidia-enhanced',
    description: 'Advanced AI companion with memory, personality, and learning.',
    amount: 999,
    interval: 'month',
    stripe_price_id: 'price_1TAk3e3e5FMFdlFwGi46sMNf',
  },
  {
    id: 'addon_roadauth',
    name: 'RoadAuth',
    slug: 'roadauth',
    description: 'Decentralized identity and auth for your apps.',
    amount: 499,
    interval: 'month',
    stripe_price_id: 'price_1TAk3f3e5FMFdlFwfYwLHZVk',
  },
  {
    id: 'addon_context_bridge',
    name: 'Context Bridge',
    slug: 'context-bridge',
    description: 'Cross-agent memory sharing and context persistence.',
    amount: 799,
    interval: 'month',
    stripe_price_id: 'price_1TAk3g3e5FMFdlFwNkgWv2tU',
  },
  {
    id: 'addon_knowledge_hub',
    name: 'Knowledge Hub',
    slug: 'knowledge-hub',
    description: 'RAG pipeline with vector search across your data.',
    amount: 1499,
    interval: 'month',
    stripe_price_id: 'price_1TAk3i3e5FMFdlFwlGXxMWFJ',
  },
];

// ─── Helpers ─────────────────────────────────────────────────────────────
function uid() {
  return crypto.randomUUID().replace(/-/g, '').slice(0, 20);
}

function json(data, status = 200) {
  return Response.json(data, { status });
}

function err(message, status = 400) {
  return Response.json({ error: message }, { status });
}

const SECURITY_HEADERS = {
  'X-Content-Type-Options': 'nosniff',
  'X-Frame-Options': 'SAMEORIGIN',
  'X-XSS-Protection': '1; mode=block',
  'Referrer-Policy': 'strict-origin-when-cross-origin',
  'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload',
};

function corsHeaders(origin) {
  return {
    'Access-Control-Allow-Origin': origin || '*',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-RoadPay-Key',
    'Access-Control-Max-Age': '86400',
  };
}

async function logEvent(db, type, customerId, entityType, entityId, data = {}) {
  await db.prepare(
    'INSERT INTO events (id, type, customer_id, entity_type, entity_id, data) VALUES (?, ?, ?, ?, ?, ?)'
  ).bind(`evt_${uid()}`, type, customerId, entityType, entityId, JSON.stringify(data)).run();
}

async function stripeRequest(env, method, path, body = null) {
  if (!env.STRIPE_SECRET_KEY) return null;
  const url = `https://api.stripe.com/v1${path}`;
  const headers = {
    'Authorization': `Bearer ${env.STRIPE_SECRET_KEY}`,
    'Content-Type': 'application/x-www-form-urlencoded',
  };
  const options = { method, headers };
  if (body) options.body = new URLSearchParams(body).toString();
  const res = await fetch(url, options);
  return res.json();
}

// ─── Auth middleware ─────────────────────────────────────────────────────
async function authenticate(request, env) {
  // Admin key for server-to-server
  const apiKey = request.headers.get('X-RoadPay-Key');
  if (apiKey && env.ROADPAY_ADMIN_KEY && apiKey === env.ROADPAY_ADMIN_KEY) {
    return { role: 'admin', email: 'admin' };
  }

  // Bearer token — verify with auth.blackroad.io
  const auth = request.headers.get('Authorization');
  if (auth?.startsWith('Bearer ')) {
    const token = auth.slice(7);
    try {
      const res = await fetch(`${env.AUTH_API || 'https://auth.blackroad.io'}/api/me`, {
        headers: { 'Authorization': `Bearer ${token}` },
      });
      if (res.ok) {
        const user = await res.json();
        return { role: 'user', email: user.email, userId: user.id };
      }
    } catch {}
  }

  return null;
}

// ─── Init ────────────────────────────────────────────────────────────────
async function handleInit(db) {
  for (const sql of SCHEMA) {
    await db.prepare(sql).run();
  }

  // Seed plans
  for (const plan of SEED_PLANS) {
    await db.prepare(
      `INSERT OR IGNORE INTO plans (id, name, slug, description, amount, interval, tier, features, stripe_price_id)
       VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
    ).bind(plan.id, plan.name, plan.slug, plan.description, plan.amount, plan.interval, plan.tier, plan.features, plan.stripe_price_id).run();
  }

  // Seed addons
  for (const addon of SEED_ADDONS) {
    await db.prepare(
      `INSERT OR IGNORE INTO addons (id, name, slug, description, amount, interval, stripe_price_id)
       VALUES (?, ?, ?, ?, ?, ?, ?)`
    ).bind(addon.id, addon.name, addon.slug, addon.description, addon.amount, addon.interval, addon.stripe_price_id).run();
  }

  return json({ ok: true, tables: SCHEMA.length, plans: SEED_PLANS.length, addons: SEED_ADDONS.length });
}

// ─── Customers ───────────────────────────────────────────────────────────
async function handleCustomers(request, db, env) {
  const url = new URL(request.url);

  if (request.method === 'GET') {
    const email = url.searchParams.get('email');
    const id = url.searchParams.get('id');

    if (id) {
      const customer = await db.prepare('SELECT * FROM customers WHERE id = ?').bind(id).first();
      if (!customer) return err('Customer not found', 404);
      // Get their subscriptions
      const subs = await db.prepare(
        `SELECT s.*, p.name as plan_name, p.slug as plan_slug, p.amount as plan_amount
         FROM subscriptions s JOIN plans p ON s.plan_id = p.id
         WHERE s.customer_id = ? ORDER BY s.created_at DESC`
      ).bind(id).all();
      return json({ customer, subscriptions: subs.results });
    }

    if (email) {
      const customer = await db.prepare('SELECT * FROM customers WHERE email = ?').bind(email).first();
      if (!customer) return err('Customer not found', 404);
      return json({ customer });
    }

    const limit = parseInt(url.searchParams.get('limit') || '50');
    const offset = parseInt(url.searchParams.get('offset') || '0');
    const customers = await db.prepare(
      'SELECT * FROM customers ORDER BY created_at DESC LIMIT ? OFFSET ?'
    ).bind(limit, offset).all();
    const count = await db.prepare('SELECT COUNT(*) as total FROM customers').first();
    return json({ customers: customers.results, total: count.total });
  }

  if (request.method === 'POST') {
    const body = await request.json();
    const { email, name, metadata } = body;
    if (!email) return err('email is required');

    // Check existing
    const existing = await db.prepare('SELECT id FROM customers WHERE email = ?').bind(email).first();
    if (existing) return err('Customer already exists', 409);

    const id = `cus_${uid()}`;

    // Create Stripe customer if key exists
    let stripeId = null;
    if (env.STRIPE_SECRET_KEY) {
      const stripeCustomer = await stripeRequest(env, 'POST', '/customers', {
        email,
        name: name || '',
        'metadata[roadpay_id]': id,
        'metadata[source]': 'roadpay',
      });
      stripeId = stripeCustomer?.id;
    }

    await db.prepare(
      'INSERT INTO customers (id, email, name, stripe_customer_id, metadata) VALUES (?, ?, ?, ?, ?)'
    ).bind(id, email, name || null, stripeId, JSON.stringify(metadata || {})).run();

    await logEvent(db, 'customer.created', id, 'customer', id, { email });

    return json({ id, email, name, stripe_customer_id: stripeId }, 201);
  }

  return err('Method not allowed', 405);
}

// ─── Plans ───────────────────────────────────────────────────────────────
async function handlePlans(db) {
  const plans = await db.prepare('SELECT * FROM plans WHERE active = 1 ORDER BY tier ASC').all();
  return json({
    plans: plans.results.map(p => ({
      ...p,
      features: JSON.parse(p.features || '[]'),
      metadata: JSON.parse(p.metadata || '{}'),
    })),
  });
}

// ─── Addons ──────────────────────────────────────────────────────────────
async function handleAddons(db) {
  const addons = await db.prepare('SELECT * FROM addons WHERE active = 1 ORDER BY amount ASC').all();
  return json({
    addons: addons.results.map(a => ({
      ...a,
      metadata: JSON.parse(a.metadata || '{}'),
    })),
  });
}

// ─── Subscribe ───────────────────────────────────────────────────────────
async function handleSubscribe(request, db, env) {
  const body = await request.json();
  const { customer_id, plan_slug, addon_slugs = [], success_url, cancel_url } = body;

  if (!customer_id || !plan_slug) return err('customer_id and plan_slug are required');

  const customer = await db.prepare('SELECT * FROM customers WHERE id = ?').bind(customer_id).first();
  if (!customer) return err('Customer not found', 404);

  const plan = await db.prepare('SELECT * FROM plans WHERE slug = ?').bind(plan_slug).first();
  if (!plan) return err('Plan not found', 404);

  // Check existing active subscription
  const existing = await db.prepare(
    "SELECT id FROM subscriptions WHERE customer_id = ? AND status = 'active'"
  ).bind(customer_id).first();
  if (existing) return err('Customer already has an active subscription. Cancel first or upgrade.', 409);

  // Free plan — no payment needed
  if (plan.amount === 0 && !plan.stripe_price_id) {
    const subId = `sub_${uid()}`;
    const now = new Date().toISOString();
    const periodEnd = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString();

    await db.prepare(
      `INSERT INTO subscriptions (id, customer_id, plan_id, status, current_period_start, current_period_end)
       VALUES (?, ?, ?, 'active', ?, ?)`
    ).bind(subId, customer_id, plan.id, now, periodEnd).run();

    // Create invoice at $0
    await db.prepare(
      `INSERT INTO invoices (id, customer_id, subscription_id, amount, status, description, paid_at)
       VALUES (?, ?, ?, 0, 'paid', ?, ?)`
    ).bind(`inv_${uid()}`, customer_id, subId, `${plan.name} — Free`, now).run();

    await logEvent(db, 'subscription.created', customer_id, 'subscription', subId, { plan: plan.slug });

    return json({ subscription_id: subId, plan: plan.slug, status: 'active' });
  }

  // Paid plan — create Stripe checkout
  if (!env.STRIPE_SECRET_KEY) {
    return err('Payment processing not configured', 503);
  }

  // Build line items
  const lineItems = {};
  lineItems['line_items[0][price]'] = plan.stripe_price_id;
  lineItems['line_items[0][quantity]'] = '1';

  // Add addon line items
  if (addon_slugs.length > 0) {
    const addons = await db.prepare(
      `SELECT * FROM addons WHERE slug IN (${addon_slugs.map(() => '?').join(',')}) AND active = 1`
    ).bind(...addon_slugs).all();

    addons.results.forEach((addon, i) => {
      lineItems[`line_items[${i + 1}][price]`] = addon.stripe_price_id;
      lineItems[`line_items[${i + 1}][quantity]`] = '1';
    });
  }

  const origin = new URL(request.url).origin;
  const subId = `sub_${uid()}`;

  const session = await stripeRequest(env, 'POST', '/checkout/sessions', {
    mode: 'subscription',
    ...lineItems,
    customer: customer.stripe_customer_id || undefined,
    customer_email: !customer.stripe_customer_id ? customer.email : undefined,
    success_url: success_url || `${origin}/success?session_id={CHECKOUT_SESSION_ID}&sub=${subId}`,
    cancel_url: cancel_url || `${origin}/pricing`,
    'subscription_data[metadata][roadpay_sub_id]': subId,
    'subscription_data[metadata][roadpay_customer_id]': customer_id,
    'subscription_data[metadata][plan]': plan.slug,
    'automatic_tax[enabled]': 'true',
  });

  if (session.error) {
    return err(session.error.message, 500);
  }

  // Create pending subscription
  await db.prepare(
    `INSERT INTO subscriptions (id, customer_id, plan_id, status, metadata)
     VALUES (?, ?, ?, 'pending', ?)`
  ).bind(subId, customer_id, plan.id, JSON.stringify({ stripe_checkout_session: session.id })).run();

  await logEvent(db, 'checkout.started', customer_id, 'subscription', subId, { plan: plan.slug });

  return json({ checkout_url: session.url, subscription_id: subId, session_id: session.id });
}

// ─── Manage Subscription ─────────────────────────────────────────────────
async function handleSubscription(request, db, env) {
  const url = new URL(request.url);
  const subId = url.pathname.split('/').pop();

  if (request.method === 'GET') {
    const sub = await db.prepare(
      `SELECT s.*, p.name as plan_name, p.slug as plan_slug, p.amount as plan_amount, p.features
       FROM subscriptions s JOIN plans p ON s.plan_id = p.id WHERE s.id = ?`
    ).bind(subId).first();
    if (!sub) return err('Subscription not found', 404);

    // Get addons
    const addons = await db.prepare(
      `SELECT sa.*, a.name as addon_name, a.slug as addon_slug, a.amount as addon_amount
       FROM subscription_addons sa JOIN addons a ON sa.addon_id = a.id
       WHERE sa.subscription_id = ? AND sa.status = 'active'`
    ).bind(subId).all();

    return json({
      subscription: {
        ...sub,
        features: JSON.parse(sub.features || '[]'),
        metadata: JSON.parse(sub.metadata || '{}'),
      },
      addons: addons.results,
    });
  }

  if (request.method === 'DELETE') {
    const sub = await db.prepare('SELECT * FROM subscriptions WHERE id = ?').bind(subId).first();
    if (!sub) return err('Subscription not found', 404);

    // Cancel in Stripe
    if (sub.stripe_subscription_id && env.STRIPE_SECRET_KEY) {
      await stripeRequest(env, 'DELETE', `/subscriptions/${sub.stripe_subscription_id}`);
    }

    const now = new Date().toISOString();
    await db.prepare(
      "UPDATE subscriptions SET status = 'canceled', canceled_at = ?, updated_at = ? WHERE id = ?"
    ).bind(now, now, subId).run();

    await logEvent(db, 'subscription.canceled', sub.customer_id, 'subscription', subId);

    return json({ ok: true, status: 'canceled' });
  }

  return err('Method not allowed', 405);
}

// ─── Invoices ────────────────────────────────────────────────────────────
async function handleInvoices(request, db) {
  const url = new URL(request.url);
  const customerId = url.searchParams.get('customer_id');

  if (!customerId) return err('customer_id is required');

  const invoices = await db.prepare(
    'SELECT * FROM invoices WHERE customer_id = ? ORDER BY created_at DESC LIMIT 50'
  ).bind(customerId).all();

  return json({
    invoices: invoices.results.map(inv => ({
      ...inv,
      line_items: JSON.parse(inv.line_items || '[]'),
      metadata: JSON.parse(inv.metadata || '{}'),
    })),
  });
}

// ─── Payments ────────────────────────────────────────────────────────────
async function handlePayments(request, db) {
  const url = new URL(request.url);
  const customerId = url.searchParams.get('customer_id');

  if (!customerId) return err('customer_id is required');

  const payments = await db.prepare(
    'SELECT * FROM payments WHERE customer_id = ? ORDER BY created_at DESC LIMIT 50'
  ).bind(customerId).all();

  return json({ payments: payments.results });
}

// ─── Billing Portal (own) ────────────────────────────────────────────────
async function handlePortal(request, db) {
  const { customer_id } = await request.json();
  if (!customer_id) return err('customer_id is required');

  const customer = await db.prepare('SELECT * FROM customers WHERE id = ?').bind(customer_id).first();
  if (!customer) return err('Customer not found', 404);

  const subs = await db.prepare(
    `SELECT s.*, p.name as plan_name, p.slug as plan_slug, p.amount as plan_amount, p.features
     FROM subscriptions s JOIN plans p ON s.plan_id = p.id
     WHERE s.customer_id = ? ORDER BY s.created_at DESC`
  ).bind(customer_id).all();

  const invoices = await db.prepare(
    'SELECT * FROM invoices WHERE customer_id = ? ORDER BY created_at DESC LIMIT 10'
  ).bind(customer_id).all();

  const payments = await db.prepare(
    'SELECT * FROM payments WHERE customer_id = ? ORDER BY created_at DESC LIMIT 10'
  ).bind(customer_id).all();

  return json({
    customer: { ...customer, metadata: JSON.parse(customer.metadata || '{}') },
    subscriptions: subs.results.map(s => ({
      ...s,
      features: JSON.parse(s.features || '[]'),
      metadata: JSON.parse(s.metadata || '{}'),
    })),
    invoices: invoices.results.map(i => ({
      ...i,
      line_items: JSON.parse(i.line_items || '[]'),
    })),
    payments: payments.results,
  });
}

// ─── Webhook (Stripe → RoadPay sync) ────────────────────────────────────
async function handleWebhook(request, db, env) {
  const signature = request.headers.get('stripe-signature');
  const body = await request.text();

  if (env.STRIPE_WEBHOOK_SECRET && signature) {
    try {
      await verifySignature(body, signature, env.STRIPE_WEBHOOK_SECRET);
    } catch (e) {
      return err(`Webhook verification failed: ${e.message}`, 400);
    }
  }

  let event;
  try {
    event = JSON.parse(body);
  } catch {
    return err('Invalid JSON', 400);
  }

  const now = new Date().toISOString();

  switch (event.type) {
    case 'checkout.session.completed': {
      const session = event.data.object;
      const subId = session.metadata?.roadpay_sub_id;
      const customerId = session.metadata?.roadpay_customer_id;

      if (subId) {
        await db.prepare(
          `UPDATE subscriptions SET status = 'active', stripe_subscription_id = ?,
           current_period_start = ?, updated_at = ? WHERE id = ?`
        ).bind(session.subscription, now, now, subId).run();
      }

      // Update customer's Stripe ID if we didn't have it
      if (customerId && session.customer) {
        await db.prepare(
          'UPDATE customers SET stripe_customer_id = ?, updated_at = ? WHERE id = ? AND stripe_customer_id IS NULL'
        ).bind(session.customer, now, customerId).run();
      }

      await logEvent(db, 'checkout.completed', customerId, 'subscription', subId, {
        stripe_session: session.id,
      });
      break;
    }

    case 'invoice.payment_succeeded': {
      const invoice = event.data.object;
      const stripeSubId = invoice.subscription;

      // Find our subscription
      const sub = await db.prepare(
        'SELECT * FROM subscriptions WHERE stripe_subscription_id = ?'
      ).bind(stripeSubId).first();

      if (sub) {
        // Create invoice record
        await db.prepare(
          `INSERT INTO invoices (id, customer_id, subscription_id, stripe_invoice_id, amount, status, description, paid_at)
           VALUES (?, ?, ?, ?, ?, 'paid', ?, ?)`
        ).bind(
          `inv_${uid()}`, sub.customer_id, sub.id, invoice.id,
          invoice.amount_paid, `Payment for ${invoice.lines?.data?.[0]?.description || 'subscription'}`, now
        ).run();

        // Create payment record
        await db.prepare(
          `INSERT INTO payments (id, customer_id, invoice_id, stripe_payment_id, amount, status)
           VALUES (?, ?, ?, ?, ?, 'succeeded')`
        ).bind(`pay_${uid()}`, sub.customer_id, invoice.id, invoice.payment_intent, invoice.amount_paid).run();

        // Update subscription period
        const periodEnd = invoice.lines?.data?.[0]?.period?.end;
        if (periodEnd) {
          await db.prepare(
            'UPDATE subscriptions SET current_period_end = ?, updated_at = ? WHERE id = ?'
          ).bind(new Date(periodEnd * 1000).toISOString(), now, sub.id).run();
        }

        await logEvent(db, 'payment.succeeded', sub.customer_id, 'payment', invoice.payment_intent, {
          amount: invoice.amount_paid,
        });
      }
      break;
    }

    case 'invoice.payment_failed': {
      const invoice = event.data.object;
      const sub = await db.prepare(
        'SELECT * FROM subscriptions WHERE stripe_subscription_id = ?'
      ).bind(invoice.subscription).first();

      if (sub) {
        await db.prepare(
          "UPDATE subscriptions SET status = 'past_due', updated_at = ? WHERE id = ?"
        ).bind(now, sub.id).run();

        await logEvent(db, 'payment.failed', sub.customer_id, 'subscription', sub.id, {
          amount: invoice.amount_due,
        });
      }
      break;
    }

    case 'customer.subscription.deleted': {
      const stripeSub = event.data.object;
      const sub = await db.prepare(
        'SELECT * FROM subscriptions WHERE stripe_subscription_id = ?'
      ).bind(stripeSub.id).first();

      if (sub) {
        await db.prepare(
          "UPDATE subscriptions SET status = 'canceled', canceled_at = ?, updated_at = ? WHERE id = ?"
        ).bind(now, now, sub.id).run();

        await logEvent(db, 'subscription.canceled', sub.customer_id, 'subscription', sub.id);
      }
      break;
    }

    case 'customer.subscription.updated': {
      const stripeSub = event.data.object;
      const sub = await db.prepare(
        'SELECT * FROM subscriptions WHERE stripe_subscription_id = ?'
      ).bind(stripeSub.id).first();

      if (sub) {
        await db.prepare(
          'UPDATE subscriptions SET status = ?, updated_at = ? WHERE id = ?'
        ).bind(stripeSub.status, now, sub.id).run();
      }
      break;
    }

    default:
      console.log(`Unhandled webhook: ${event.type}`);
  }

  return json({ received: true });
}

async function verifySignature(payload, sigHeader, secret) {
  if (!sigHeader) throw new Error('Missing signature');
  const parts = sigHeader.split(',').reduce((acc, part) => {
    const [k, v] = part.split('=');
    acc[k] = v;
    return acc;
  }, {});

  const timestamp = parts.t;
  const sigs = Object.entries(parts).filter(([k]) => k === 'v1').map(([, v]) => v);
  if (!timestamp || !sigs.length) throw new Error('Invalid signature format');

  const tolerance = 300;
  if (Math.abs(Math.floor(Date.now() / 1000) - parseInt(timestamp)) > tolerance) {
    throw new Error('Timestamp outside tolerance');
  }

  const enc = new TextEncoder();
  const key = await crypto.subtle.importKey('raw', enc.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
  const sig = await crypto.subtle.sign('HMAC', key, enc.encode(`${timestamp}.${payload}`));
  const computed = Array.from(new Uint8Array(sig)).map(b => b.toString(16).padStart(2, '0')).join('');
  if (!sigs.includes(computed)) throw new Error('Signature mismatch');
}

// ─── Stats / Dashboard ──────────────────────────────────────────────────
async function handleStats(db) {
  const customers = await db.prepare('SELECT COUNT(*) as total FROM customers').first();
  const activeSubs = await db.prepare("SELECT COUNT(*) as total FROM subscriptions WHERE status = 'active'").first();
  const totalRevenue = await db.prepare("SELECT COALESCE(SUM(amount), 0) as total FROM payments WHERE status = 'succeeded'").first();
  const invoicesPaid = await db.prepare("SELECT COUNT(*) as total FROM invoices WHERE status = 'paid'").first();
  const recentEvents = await db.prepare('SELECT * FROM events ORDER BY created_at DESC LIMIT 20').all();

  // MRR calculation
  const mrr = await db.prepare(
    `SELECT COALESCE(SUM(p.amount), 0) as mrr
     FROM subscriptions s JOIN plans p ON s.plan_id = p.id
     WHERE s.status = 'active'`
  ).first();

  // Plan breakdown
  const planBreakdown = await db.prepare(
    `SELECT p.name, p.slug, COUNT(s.id) as subscribers, p.amount
     FROM plans p LEFT JOIN subscriptions s ON s.plan_id = p.id AND s.status = 'active'
     GROUP BY p.id ORDER BY p.tier ASC`
  ).all();

  return json({
    stats: {
      customers: customers.total,
      active_subscriptions: activeSubs.total,
      total_revenue_cents: totalRevenue.total,
      total_revenue: `$${(totalRevenue.total / 100).toFixed(2)}`,
      mrr_cents: mrr.mrr,
      mrr: `$${(mrr.mrr / 100).toFixed(2)}`,
      invoices_paid: invoicesPaid.total,
    },
    plan_breakdown: planBreakdown.results,
    recent_events: recentEvents.results.map(e => ({
      ...e,
      data: JSON.parse(e.data || '{}'),
    })),
  });
}

// ─── Lookup (by email — public-ish) ──────────────────────────────────────
async function handleLookup(request, db) {
  const url = new URL(request.url);
  const email = url.searchParams.get('email');
  if (!email) return err('email is required');

  const customer = await db.prepare('SELECT id, email, name, status, created_at FROM customers WHERE email = ?').bind(email).first();
  if (!customer) return json({ found: false });

  const sub = await db.prepare(
    `SELECT s.id, s.status, s.current_period_end, p.name as plan_name, p.slug as plan_slug, p.tier
     FROM subscriptions s JOIN plans p ON s.plan_id = p.id
     WHERE s.customer_id = ? AND s.status = 'active'
     ORDER BY s.created_at DESC LIMIT 1`
  ).bind(customer.id).first();

  return json({
    found: true,
    customer_id: customer.id,
    email: customer.email,
    name: customer.name,
    plan: sub ? { name: sub.plan_name, slug: sub.plan_slug, tier: sub.tier, status: sub.status, period_end: sub.current_period_end } : null,
  });
}

// ─── Upgrade / Downgrade ─────────────────────────────────────────────────
async function handleUpgrade(request, db, env) {
  const { customer_id, new_plan_slug } = await request.json();
  if (!customer_id || !new_plan_slug) return err('customer_id and new_plan_slug required');

  const customer = await db.prepare('SELECT * FROM customers WHERE id = ?').bind(customer_id).first();
  if (!customer) return err('Customer not found', 404);

  const newPlan = await db.prepare('SELECT * FROM plans WHERE slug = ?').bind(new_plan_slug).first();
  if (!newPlan) return err('Plan not found', 404);

  const currentSub = await db.prepare(
    "SELECT * FROM subscriptions WHERE customer_id = ? AND status = 'active'"
  ).bind(customer_id).first();

  if (!currentSub) return err('No active subscription to upgrade', 404);

  const now = new Date().toISOString();

  // If upgrading to enterprise/free (contact only) or downgrading to free
  if (!newPlan.stripe_price_id || newPlan.amount === 0) {
    // Cancel stripe sub
    if (currentSub.stripe_subscription_id && env.STRIPE_SECRET_KEY) {
      await stripeRequest(env, 'DELETE', `/subscriptions/${currentSub.stripe_subscription_id}`);
    }
    await db.prepare(
      "UPDATE subscriptions SET status = 'canceled', canceled_at = ?, updated_at = ? WHERE id = ?"
    ).bind(now, now, currentSub.id).run();

    // Create new free sub
    const newSubId = `sub_${uid()}`;
    await db.prepare(
      `INSERT INTO subscriptions (id, customer_id, plan_id, status, current_period_start, current_period_end)
       VALUES (?, ?, ?, 'active', ?, ?)`
    ).bind(newSubId, customer_id, newPlan.id, now, new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString()).run();

    await logEvent(db, 'subscription.changed', customer_id, 'subscription', newSubId, {
      from: currentSub.plan_id, to: newPlan.id,
    });

    return json({ subscription_id: newSubId, plan: newPlan.slug, status: 'active' });
  }

  // Stripe subscription update (proration)
  if (currentSub.stripe_subscription_id && env.STRIPE_SECRET_KEY) {
    // Get current subscription items
    const stripeSub = await stripeRequest(env, 'GET', `/subscriptions/${currentSub.stripe_subscription_id}`);
    if (stripeSub?.items?.data?.[0]) {
      await stripeRequest(env, 'POST', `/subscriptions/${currentSub.stripe_subscription_id}`, {
        'items[0][id]': stripeSub.items.data[0].id,
        'items[0][price]': newPlan.stripe_price_id,
        proration_behavior: 'create_prorations',
      });
    }

    await db.prepare(
      'UPDATE subscriptions SET plan_id = ?, updated_at = ? WHERE id = ?'
    ).bind(newPlan.id, now, currentSub.id).run();

    await logEvent(db, 'subscription.upgraded', customer_id, 'subscription', currentSub.id, {
      from: currentSub.plan_id, to: newPlan.id,
    });

    return json({ subscription_id: currentSub.id, plan: newPlan.slug, status: 'active', prorated: true });
  }

  return err('Cannot upgrade — no active Stripe subscription', 400);
}

// ─── Health ──────────────────────────────────────────────────────────────
function handleHealth() {
  return json({
    status: 'ok',
    service: 'roadpay',
    version: VERSION,
    time: new Date().toISOString(),
    endpoints: [
      'GET  /health',
      'GET  /init',
      'GET  /plans',
      'GET  /addons',
      'GET  /stats',
      'GET  /lookup?email=',
      'GET  /customers?email=|id=',
      'POST /customers',
      'POST /subscribe',
      'GET  /subscriptions/:id',
      'DELETE /subscriptions/:id',
      'POST /upgrade',
      'POST /portal',
      'GET  /invoices?customer_id=',
      'GET  /payments?customer_id=',
      'POST /webhook',
    ],
  });
}

// ─── Router ──────────────────────────────────────────────────────────────
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    const origin = request.headers.get('Origin') || '*';
    const cors = corsHeaders(origin);

    if (request.method === 'OPTIONS') {
      return new Response(null, { status: 204, headers: { ...cors, ...SECURITY_HEADERS } });
    }

    const db = env.DB;
    let response;

    try {
      const path = url.pathname;

      // Public endpoints (no auth)
      if (path === '/health') return addHeaders(handleHealth(), cors);
      if (path === '/init') return addHeaders(await handleInit(db), cors);
      if (path === '/plans') return addHeaders(await handlePlans(db), cors);
      if (path === '/addons') return addHeaders(await handleAddons(db), cors);
      if (path === '/webhook' && request.method === 'POST') return addHeaders(await handleWebhook(request, db, env), cors);
      if (path === '/lookup') return addHeaders(await handleLookup(request, db), cors);

      // Authenticated endpoints
      const user = await authenticate(request, env);
      if (!user) {
        return addHeaders(err('Unauthorized — provide Bearer token or X-RoadPay-Key', 401), cors);
      }

      switch (true) {
        case path === '/customers':
          response = await handleCustomers(request, db, env);
          break;
        case path === '/subscribe' && request.method === 'POST':
          response = await handleSubscribe(request, db, env);
          break;
        case path.startsWith('/subscriptions/'):
          response = await handleSubscription(request, db, env);
          break;
        case path === '/upgrade' && request.method === 'POST':
          response = await handleUpgrade(request, db, env);
          break;
        case path === '/portal' && request.method === 'POST':
          response = await handlePortal(request, db);
          break;
        case path === '/invoices':
          response = await handleInvoices(request, db);
          break;
        case path === '/payments':
          response = await handlePayments(request, db);
          break;
        case path === '/stats':
          response = await handleStats(db);
          break;
        default:
          response = err('Not found', 404);
      }
    } catch (e) {
      console.error('RoadPay error:', e);
      response = err(e.message, 500);
    }

    return addHeaders(response, cors);
  },
};

function addHeaders(response, cors) {
  const headers = new Headers(response.headers);
  for (const [k, v] of Object.entries({ ...cors, ...SECURITY_HEADERS })) {
    headers.set(k, v);
  }
  return new Response(response.body, { status: response.status, headers });
}
