// BlackRoad Stripe Worker — checkout, portal, prices, webhooks, admin

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',
  'Permissions-Policy': 'geolocation=(), microphone=(), camera=()',
  'Strict-Transport-Security': 'max-age=31536000; includeSubDomains; preload',
};

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

async function stripeRequest(env, method, path, body = 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);
  const data = await res.json();
  if (!res.ok) {
    throw new Error(data.error?.message || `Stripe error: ${res.status}`);
  }
  return data;
}

// ─── Checkout ─────────────────────────────────────────────────────────
async function handleCheckout(request, env) {
  const body = await request.json();
  const { price_id, success_url, cancel_url, customer_email, metadata = {} } = body;

  if (!price_id) {
    return Response.json({ error: 'price_id is required' }, { status: 400 });
  }

  const origin = request.headers.get('Origin') || '*';
  const successUrl = success_url || `${origin}/billing?success=true&session_id={CHECKOUT_SESSION_ID}`;
  const cancelUrl = cancel_url || `${origin}/pricing`;

  const params = {
    mode: 'subscription',
    'line_items[0][price]': price_id,
    'line_items[0][quantity]': '1',
    success_url: successUrl,
    cancel_url: cancelUrl,
    'automatic_tax[enabled]': 'true',
    'subscription_data[metadata][source]': 'blackroad-os',
    ...Object.fromEntries(
      Object.entries(metadata).map(([k, v]) => [`metadata[${k}]`, v])
    ),
  };

  if (customer_email) {
    params.customer_email = customer_email;
  }

  const session = await stripeRequest(env, 'POST', '/checkout/sessions', params);
  return Response.json({ url: session.url, session_id: session.id });
}

// ─── Billing Portal ───────────────────────────────────────────────────
async function handlePortal(request, env) {
  const { customer_id, return_url } = await request.json();
  if (!customer_id) {
    return Response.json({ error: 'customer_id is required' }, { status: 400 });
  }
  const origin = request.headers.get('Origin') || '*';
  const session = await stripeRequest(env, 'POST', '/billing_portal/sessions', {
    customer: customer_id,
    return_url: return_url || `${origin}/account`,
  });
  return Response.json({ url: session.url });
}

// ─── Prices ───────────────────────────────────────────────────────────
async function handlePrices(env) {
  const prices = await stripeRequest(env, 'GET', '/prices?active=true&expand[]=data.product&limit=100');
  const formatted = prices.data
    .filter((p) => p.product && !p.product.deleted)
    .map((p) => ({
      id: p.id,
      amount: p.unit_amount,
      currency: p.currency,
      interval: p.recurring?.interval,
      interval_count: p.recurring?.interval_count,
      product: {
        id: p.product.id,
        name: p.product.name,
        description: p.product.description,
        metadata: p.product.metadata,
      },
    }))
    .sort((a, b) => (a.amount || 0) - (b.amount || 0));
  return Response.json({ prices: formatted, count: formatted.length });
}

// ─── Products (list all) ─────────────────────────────────────────────
async function handleProducts(env) {
  const products = await stripeRequest(env, 'GET', '/products?active=true&limit=100');
  const formatted = products.data.map((p) => ({
    id: p.id,
    name: p.name,
    description: p.description,
    metadata: p.metadata,
    images: p.images,
    default_price: p.default_price,
    created: p.created,
  }));
  return Response.json({ products: formatted, count: formatted.length });
}

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

  if (env.STRIPE_WEBHOOK_SECRET) {
    try {
      // Standard response headers
      const requestId = crypto.randomUUID().slice(0, 8);
      await verifyStripeSignature(body, signature, env.STRIPE_WEBHOOK_SECRET);
    } catch (err) {
      return new Response(`Webhook signature verification failed: ${err.message}`, { status: 400 });
    }
  }

  let event;
  try {
    event = JSON.parse(body);
  } catch {
    return new Response('Invalid JSON', { status: 400 });
  }

  // Forward to Slack hub
  const slackRelay = fetch('https://blackroad-slack.amundsonalexa.workers.dev/stripe', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(event)
  }).catch(() => {});

  switch (event.type) {
    case 'checkout.session.completed': {
      const session = event.data.object;
      console.log(`✓ Checkout completed: ${session.id} | customer: ${session.customer}`);
      break;
    }
    case 'customer.subscription.created':
    case 'customer.subscription.updated': {
      const sub = event.data.object;
      console.log(`✓ Subscription ${event.type}: ${sub.id} | status: ${sub.status}`);
      break;
    }
    case 'customer.subscription.deleted': {
      const sub = event.data.object;
      console.log(`✓ Subscription cancelled: ${sub.id}`);
      break;
    }
    case 'invoice.payment_failed': {
      const invoice = event.data.object;
      console.log(`✗ Payment failed: ${invoice.id} | customer: ${invoice.customer}`);
      break;
    }
    case 'invoice.payment_succeeded': {
      const invoice = event.data.object;
      console.log(`✓ Payment succeeded: ${invoice.id}`);
      break;
    }
    default:
      console.log(`Unhandled event: ${event.type}`);
  }
  await slackRelay;
  return Response.json({ received: true });
}

async function verifyStripeSignature(payload, sigHeader, secret) {
  if (!sigHeader) throw new Error('Missing stripe-signature header');

  const parts = sigHeader.split(',').reduce((acc, part) => {
    const [k, v] = part.split('=');
    acc[k] = v;
    return acc;
  }, {});

  const timestamp = parts.t;
  const signatures = Object.entries(parts)
    .filter(([k]) => k === 'v1')
    .map(([, v]) => v);

  if (!timestamp || signatures.length === 0) {
    throw new Error('Invalid stripe-signature format');
  }

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

  const signedPayload = `${timestamp}.${payload}`;
  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(signedPayload));
  const computed = Array.from(new Uint8Array(sig))
    .map((b) => b.toString(16).padStart(2, '0'))
    .join('');

  if (!signatures.includes(computed)) {
    throw new Error('Signature mismatch');
  }
}


// ─── 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 } });
    }

    if (!env.STRIPE_SECRET_KEY) {
      return Response.json(
        { error: 'Stripe not configured' },
        { status: 503, headers: { ...cors, ...SECURITY_HEADERS } }
      );
    }

    let response;
    try {
      switch (true) {
        case url.pathname === '/health':
          response = Response.json({
            status: 'ok',
            worker: 'blackroad-stripe',
            version: '2.0.0',
            time: new Date().toISOString(),
          });
          break;

        case request.method === 'POST' && url.pathname === '/checkout':
          response = await handleCheckout(request, env);
          break;

        case request.method === 'POST' && url.pathname === '/portal':
          response = await handlePortal(request, env);
          break;

        case request.method === 'POST' && url.pathname === '/webhook':
          return await handleWebhook(request, env);

        case request.method === 'GET' && url.pathname === '/prices':
          response = await handlePrices(env);
          break;

        case request.method === 'GET' && url.pathname === '/products':
          response = await handleProducts(env);
          break;

        default:
          response = Response.json(
            { error: 'Not found', routes: ['/health', '/checkout', '/portal', '/webhook', '/prices', '/products'] },
            { status: 404 }
          );
      }
    } catch (err) {
      console.error('Worker error:', err);
      response = Response.json({ error: err.message }, { status: 500 });
    }

    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 });
  },
};
