/**
 * checkout-stripe.spec.ts — Stripe Payment Element + checkout submit flow tests.
 *
 * Covers:
 *   - Stripe.js script presence and NEURON_CFG shape
 *   - submit-btn starts disabled; enabled after Stripe element is ready
 *   - payment-message div for error display
 *   - Founding: attestation checkbox + attest-warn guard
 *   - Professional: charge timing radio buttons (now/later)
 *   - buyer-name + buyer-email validation on submit
 *   - Mocked full payment flow: /api/payment-intent + mock Stripe.js
 *   - Setup mode (professional, timing=later): label switches to "Save my card"
 *   - Decline handling: payment-message shows Stripe error
 *   - /api/payment-intent endpoint contracts
 *   - /api/link-customer endpoint exists and handles requests
 *   - /api/attest endpoint (founding plan)
 *   - Success redirect target is /account?welcome=1
 *
 * Stripe mocking strategy:
 *   addInitScript() injects window.Stripe BEFORE the page loads so checkout-stripe.js
 *   picks it up. We also intercept /api/payment-intent to return a fake client_secret.
 *   This lets us test DOM transitions, validation, and submit flow without real keys.
 *
 *   For real test-card tests (4242...) the page must have a valid pk_test_ key.
 *   Those tests are marked with [stripe-live] and are skipped when STRIPE_LIVE is not set.
 */

import { test, expect, type Page } from '@playwright/test';

const STRIPE_LIVE = process.env.STRIPE_LIVE === '1';

// ─── Mock helpers ─────────────────────────────────────────────────────────────

/** Inject a mock window.Stripe before the page loads */
async function injectMockStripe(page: Page, opts: {
  confirmResult?: { error?: { message: string } };
  declineMessage?: string;
} = {}) {
  // Block the real Stripe CDN so it cannot override the addInitScript mock
  await page.route('https://js.stripe.com/**', (route) => route.abort());
  await page.addInitScript((o) => {
    (window as any).Stripe = function (_key: string) {
      const confirmResult = o.declineMessage
        ? { error: { message: o.declineMessage } }
        : (o.confirmResult ?? {});

      return {
        elements: function () {
          return {
            create: function (_type: string) {
              return {
                mount: function (selector: string) {
                  const container = document.querySelector(selector);
                  if (container) {
                    container.innerHTML =
                      '<div id="stripe-mock-mounted" style="padding:1rem;border:1px solid #ccc;font-size:.875rem">Mock payment element</div>';
                  }
                  // Fire 'ready' via the saved cb
                  setTimeout(() => {
                    const btn = document.getElementById('submit-btn');
                    if (btn) btn.disabled = false;
                    const ld = document.querySelector('.checkout-element-loading');
                    if (ld) ld.remove();
                  }, 100);
                },
                unmount: function () {},
                on: function (event: string, cb: () => void) {
                  if (event === 'ready') setTimeout(cb, 100);
                },
              };
            },
          };
        },
        confirmPayment: function () {
          return Promise.resolve(confirmResult);
        },
        confirmSetup: function () {
          return Promise.resolve(confirmResult);
        },
      };
    };
  }, opts);
}

/** Mock /api/payment-intent to return a fake client_secret */
async function mockPaymentIntent(page: Page, overrides: Record<string, unknown> = {}) {
  await page.route('/api/payment-intent', (route) =>
    route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({
        client_secret: 'pi_test_fake_secret_playwright_123',
        id: 'pi_test_fake_playwright_123',
        plan: 'professional',
        ...overrides,
      }),
    })
  );
}

async function mockPaymentIntentSetupMode(page: Page) {
  await page.route('/api/payment-intent', (route) =>
    route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({
        client_secret: 'seti_test_fake_secret_playwright_123',
        id: 'seti_test_fake_playwright_123',
        plan: 'professional',
        setup_mode: true,
      }),
    })
  );
}

async function mockSupabaseConfig(page: Page) {
  await page.route('/api/supabase-config', (route) =>
    route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ url: 'https://xyzfaketest.supabase.co', anon_key: 'fake-key' }),
    })
  );
  // Supabase getUser() call on no-session returns 401 so the else branch runs:
  // "for paid plans, call window.initStripe('', '')" immediately.
  await page.route('https://xyzfaketest.supabase.co/auth/v1/user', (route) =>
    route.fulfill({
      status: 401,
      contentType: 'application/json',
      body: JSON.stringify({ error: 'not_authenticated' }),
    })
  );
}

// ─── Page structure — Stripe-specific ─────────────────────────────────────────

test('[professional] Stripe.js script tag present in page', async ({ page }) => {
  await page.goto('/checkout?plan=professional');
  const stripeScript = page.locator('script[src*="stripe.com"]');
  await expect(stripeScript).toBeAttached();
});

test('[founding] Stripe.js script tag present in page', async ({ page }) => {
  await page.goto('/checkout?plan=founding');
  const stripeScript = page.locator('script[src*="stripe.com"]');
  await expect(stripeScript).toBeAttached();
});

test('[free] Stripe.js is loaded (used for age verification SetupIntent)', async ({ page }) => {
  // Free plan now creates a SetupIntent for age verification
  await page.goto('/checkout?plan=free');
  const stripeScript = page.locator('script[src*="stripe.com"]');
  await expect(stripeScript).toBeAttached();
});

test('[professional] NEURON_CFG.plan is set to "professional"', async ({ page }) => {
  await page.goto('/checkout?plan=professional');
  const plan = await page.evaluate(() => (window as any).NEURON_CFG?.plan);
  expect(plan).toBe('professional');
});

test('[founding] NEURON_CFG.plan is set to "founding"', async ({ page }) => {
  await page.goto('/checkout?plan=founding');
  const plan = await page.evaluate(() => (window as any).NEURON_CFG?.plan);
  expect(plan).toBe('founding');
});

test('[professional] NEURON_CFG.pub_key is present (may be empty if unconfigured)', async ({ page }) => {
  await page.goto('/checkout?plan=professional');
  const cfg = await page.evaluate(() => (window as any).NEURON_CFG);
  expect(cfg).not.toBeNull();
  expect('pub_key' in cfg).toBeTruthy();
});

test('[professional] submit-btn starts disabled', async ({ page }) => {
  await page.goto('/checkout?plan=professional');
  const btn = page.locator('#submit-btn');
  await expect(btn).toBeAttached();
  // Before Stripe initialises, button is disabled
  const isDisabled = await btn.getAttribute('disabled');
  expect(isDisabled).not.toBeNull();
});

test('[professional] payment-message div starts hidden', async ({ page }) => {
  await page.goto('/checkout?plan=professional');
  await expect(page.locator('#payment-message')).toBeHidden();
});

test('[professional] buyer-name input is present and fillable', async ({ page }) => {
  await page.goto('/checkout?plan=professional');
  await expect(page.locator('#buyer-name')).toBeAttached();
  await page.fill('#buyer-name', 'Test User');
  expect(await page.locator('#buyer-name').inputValue()).toBe('Test User');
});

test('[professional] buyer-email input is present and fillable', async ({ page }) => {
  await page.goto('/checkout?plan=professional');
  await expect(page.locator('#buyer-email')).toBeAttached();
  await page.fill('#buyer-email', 'test@example.com');
  expect(await page.locator('#buyer-email').inputValue()).toBe('test@example.com');
});

// ─── Founding-specific ────────────────────────────────────────────────────────

test('[founding] attestation checkbox is present', async ({ page }) => {
  await page.goto('/checkout?plan=founding');
  await expect(page.locator('#founding-attest-cb')).toBeAttached();
});

test('[founding] attestation checkbox starts unchecked', async ({ page }) => {
  await page.goto('/checkout?plan=founding');
  const checked = await page.locator('#founding-attest-cb').isChecked();
  expect(checked).toBe(false);
});

test('[founding] attest-warn div is present (shown on submit without checking)', async ({ page }) => {
  await page.goto('/checkout?plan=founding');
  await expect(page.locator('#attest-warn')).toBeAttached();
  await expect(page.locator('#attest-warn')).toBeHidden();
});

test('[founding] attestation text contains expected copy', async ({ page }) => {
  await page.goto('/checkout?plan=founding');
  const attestText = (await page.locator('#founding-attestation').textContent()) ?? '';
  expect(attestText).toContain('good faith');
  expect(attestText.toLowerCase()).toContain('founding member');
});

test('[founding] submit without attestation shows attest-warn', async ({ page }) => {
  await mockSupabaseConfig(page);
  await mockPaymentIntent(page, { plan: 'founding' });
  await injectMockStripe(page);
  await page.goto('/checkout?plan=founding');

  // Wait for Stripe mock to enable the submit button
  await expect(page.locator('#submit-btn')).not.toBeDisabled({ timeout: 5000 });

  await page.fill('#buyer-name', 'Test Founder');
  await page.fill('#buyer-email', 'founder@example.com');
  // Do NOT check the attestation checkbox
  await page.locator('#payment-form').dispatchEvent('submit');

  await expect(page.locator('#attest-warn')).toBeVisible({ timeout: 3000 });
});

test('[founding] submit WITH attestation does not show attest-warn', async ({ page }) => {
  await mockSupabaseConfig(page);
  await mockPaymentIntent(page, { plan: 'founding' });
  await injectMockStripe(page);
  // Mock attest endpoint
  await page.route('/api/attest', (route) =>
    route.fulfill({ status: 200, contentType: 'application/json', body: '{"ok":true}' })
  );
  // Mock link-customer
  await page.route('/api/link-customer', (route) =>
    route.fulfill({ status: 200, contentType: 'application/json', body: '{"linked":true}' })
  );
  await page.goto('/checkout?plan=founding');

  await expect(page.locator('#submit-btn')).not.toBeDisabled({ timeout: 5000 });
  await page.fill('#buyer-name', 'Test Founder');
  await page.fill('#buyer-email', 'founder@example.com');
  await page.locator('#founding-attest-cb').check();
  await page.locator('#payment-form').dispatchEvent('submit');

  // attest-warn should NOT appear
  await page.waitForTimeout(500);
  await expect(page.locator('#attest-warn')).toBeHidden();
});

// ─── Professional charge timing ───────────────────────────────────────────────

test('[professional] charge timing section is present', async ({ page }) => {
  await page.goto('/checkout?plan=professional');
  await expect(page.locator('#timing-now')).toBeAttached();
  await expect(page.locator('#timing-later')).toBeAttached();
});

test('[professional] "charge now" radio is selected by default', async ({ page }) => {
  await page.goto('/checkout?plan=professional');
  expect(await page.locator('#timing-now').isChecked()).toBe(true);
  expect(await page.locator('#timing-later').isChecked()).toBe(false);
});

test('[professional] selecting "later" changes radio state', async ({ page }) => {
  await page.goto('/checkout?plan=professional');
  await page.locator('#timing-later').check();
  expect(await page.locator('#timing-later').isChecked()).toBe(true);
  expect(await page.locator('#timing-now').isChecked()).toBe(false);
});

test('[professional] setup_mode label shows "Save my card" text', async ({ page }) => {
  await mockSupabaseConfig(page);
  await mockPaymentIntentSetupMode(page);
  await injectMockStripe(page);
  await page.goto('/checkout?plan=professional');

  // initStripe is called by checkout-auth.el when no session → immediately for paid plans
  // Wait for the submit label to update
  await page.waitForFunction(
    () => {
      const el = document.getElementById('submit-label');
      return el && el.textContent && el.textContent.toLowerCase().includes('save');
    },
    { timeout: 6000 }
  );

  const labelText = (await page.locator('#submit-label').textContent()) ?? '';
  expect(labelText.toLowerCase()).toContain('save');
});

test('[founding] no charge timing section (one-time payment only)', async ({ page }) => {
  await page.goto('/checkout?plan=founding');
  const timingNow = page.locator('#timing-now');
  const count = await timingNow.count();
  expect(count).toBe(0);
});

// ─── Mocked payment flow — full Stripe mock ───────────────────────────────────

test('[professional] Stripe mock: payment element mounts after initStripe', async ({ page }) => {
  await mockSupabaseConfig(page);
  await mockPaymentIntent(page);
  await injectMockStripe(page);
  await page.goto('/checkout?plan=professional');

  // After initStripe() runs (checkout-auth triggers it immediately for paid plans with no session)
  await expect(page.locator('#stripe-mock-mounted')).toBeAttached({ timeout: 8000 });
});

test('[professional] Stripe mock: submit-btn enabled after element ready', async ({ page }) => {
  await mockSupabaseConfig(page);
  await mockPaymentIntent(page);
  await injectMockStripe(page);
  await page.goto('/checkout?plan=professional');

  await expect(page.locator('#submit-btn')).not.toBeDisabled({ timeout: 8000 });
});

test('[professional] submit without name shows error message', async ({ page }) => {
  await mockSupabaseConfig(page);
  await mockPaymentIntent(page);
  await injectMockStripe(page);
  await page.goto('/checkout?plan=professional');

  await expect(page.locator('#submit-btn')).not.toBeDisabled({ timeout: 8000 });

  // Fill email only, no name
  await page.fill('#buyer-email', 'test@example.com');
  await page.locator('#payment-form').dispatchEvent('submit');

  const msg = page.locator('#payment-message');
  await expect(msg).toBeVisible({ timeout: 3000 });
  const text = (await msg.textContent()) ?? '';
  expect(text.toLowerCase()).toMatch(/name|email/);
});

test('[professional] submit without email shows error message', async ({ page }) => {
  await mockSupabaseConfig(page);
  await mockPaymentIntent(page);
  await injectMockStripe(page);
  await page.goto('/checkout?plan=professional');

  await expect(page.locator('#submit-btn')).not.toBeDisabled({ timeout: 8000 });

  // Fill name only, no email
  await page.fill('#buyer-name', 'Test User');
  await page.locator('#payment-form').dispatchEvent('submit');

  const msg = page.locator('#payment-message');
  await expect(msg).toBeVisible({ timeout: 3000 });
});

test('[professional] Stripe decline: payment-message shows decline text', async ({ page }) => {
  await mockSupabaseConfig(page);
  await mockPaymentIntent(page);
  await injectMockStripe(page, { declineMessage: 'Your card was declined.' });
  await page.route('/api/link-customer', (route) =>
    route.fulfill({ status: 200, contentType: 'application/json', body: '{"linked":true}' })
  );
  await page.goto('/checkout?plan=professional');

  await expect(page.locator('#submit-btn')).not.toBeDisabled({ timeout: 8000 });
  await page.fill('#buyer-name', 'Test Buyer');
  await page.fill('#buyer-email', 'buyer@example.com');

  await page.locator('#payment-form').dispatchEvent('submit');

  const msg = page.locator('#payment-message');
  await expect(msg).toBeVisible({ timeout: 5000 });
  const text = (await msg.textContent()) ?? '';
  expect(text.toLowerCase()).toMatch(/declined|failed|error|card/);
});

test('[professional] successful payment: submit-btn shows spinner then loading state', async ({ page }) => {
  await mockSupabaseConfig(page);
  await mockPaymentIntent(page);
  await injectMockStripe(page, { confirmResult: {} }); // no error = success → redirect
  await page.route('/api/link-customer', (route) =>
    route.fulfill({ status: 200, contentType: 'application/json', body: '{"linked":true}' })
  );
  // Intercept the redirect to /account
  await page.route('**/account**', (route) => route.fulfill({ status: 200, body: 'ok' }));

  await page.goto('/checkout?plan=professional');
  await expect(page.locator('#submit-btn')).not.toBeDisabled({ timeout: 8000 });
  await page.fill('#buyer-name', 'Test Buyer');
  await page.fill('#buyer-email', 'buyer@example.com');

  // Verify loading state is triggered on submit
  const submitBtn = page.locator('#submit-btn');
  await page.locator('#payment-form').dispatchEvent('submit');
  // setLoading(true) disables the button — verify it transitions
  await expect(submitBtn).toBeDisabled({ timeout: 2000 }).catch(() => {
    // May redirect before we can check — that's also success
  });
});

// ─── /api/payment-intent endpoint contracts ───────────────────────────────────

test('POST /api/payment-intent free plan returns setup_mode (age verification)', async ({ request }) => {
  const res = await request.post('/api/payment-intent', {
    data: JSON.stringify({ plan: 'free', email: 'test@example.com' }),
    headers: { 'Content-Type': 'application/json' },
  });
  // Free plan creates a SetupIntent for age verification — must not 500
  expect(res.status()).toBeLessThan(500);
  if (res.status() === 200) {
    const body = await res.json();
    expect('setup_mode' in body || 'client_secret' in body || 'error' in body).toBeTruthy();
    expect(body.no_payment_required).toBeFalsy();
  }
});

test('POST /api/payment-intent professional returns client_secret or stripe error (not 500)', async ({ request }) => {
  const res = await request.post('/api/payment-intent', {
    data: JSON.stringify({ plan: 'professional', email: 'test@example.com', name: 'Test', timing: 'now' }),
    headers: { 'Content-Type': 'application/json' },
  });
  expect(res.status()).toBeLessThan(500);
  if (res.status() === 200) {
    const body = await res.json();
    expect('client_secret' in body || 'error' in body || 'setup_mode' in body).toBeTruthy();
  }
});

test('POST /api/payment-intent professional timing=later returns setup_mode flag', async ({ request }) => {
  const res = await request.post('/api/payment-intent', {
    data: JSON.stringify({ plan: 'professional', email: 'test@example.com', name: 'Test', timing: 'later' }),
    headers: { 'Content-Type': 'application/json' },
  });
  expect(res.status()).toBeLessThan(500);
  if (res.status() === 200) {
    const body = await res.json();
    if ('client_secret' in body) {
      // Stripe configured: setup_mode should be true for timing=later
      expect(body.setup_mode).toBeTruthy();
    }
  }
});

test('POST /api/payment-intent founding returns client_secret or error (not 500)', async ({ request }) => {
  const res = await request.post('/api/payment-intent', {
    data: JSON.stringify({ plan: 'founding', email: 'test@example.com', name: 'Founder' }),
    headers: { 'Content-Type': 'application/json' },
  });
  expect(res.status()).toBeLessThan(500);
});

test('POST /api/payment-intent empty body returns 4xx not 500', async ({ request }) => {
  const res = await request.post('/api/payment-intent', { data: {} });
  expect(res.status()).toBeLessThan(500);
});

// ─── /api/link-customer endpoint ─────────────────────────────────────────────

test('POST /api/link-customer exists and handles request (not 404/500)', async ({ request }) => {
  const res = await request.post('/api/link-customer', {
    data: JSON.stringify({
      pi_id: 'pi_test_fake',
      email: 'test@example.com',
      name: 'Test User',
      plan: 'professional',
      timing: 'now',
      mode: 'payment',
      supabase_user_id: '',
    }),
    headers: { 'Content-Type': 'application/json' },
  });
  // Should exist and not 500
  expect(res.status()).not.toBe(404);
  expect(res.status()).toBeLessThan(500);
});

// ─── /api/attest endpoint (founding) ─────────────────────────────────────────

test('POST /api/attest founding exists and handles request (not 500)', async ({ request }) => {
  const res = await request.post('/api/attest', {
    data: JSON.stringify({
      plan: 'founding',
      name: 'Test Founder',
      email: 'founder@example.com',
      timestamp: new Date().toISOString(),
      attestation: 'I am joining as a genuine early user...',
      user_agent: 'Playwright/Test',
    }),
    headers: { 'Content-Type': 'application/json' },
  });
  expect(res.status()).toBeLessThan(500);
});

// ─── /api/founding-count ──────────────────────────────────────────────────────

test('GET /api/founding-count returns remaining + sold + total', async ({ request }) => {
  const res = await request.get('/api/founding-count');
  expect(res.status()).toBe(200);
  const body = await res.json();
  expect(typeof body.remaining === 'number' || 'remaining' in body).toBeTruthy();
});

test('GET /api/founding-count: remaining is <= 1000', async ({ request }) => {
  const res = await request.get('/api/founding-count');
  if (res.status() === 200) {
    const body = await res.json();
    if (typeof body.remaining === 'number') {
      expect(body.remaining).toBeLessThanOrEqual(1000);
      expect(body.remaining).toBeGreaterThanOrEqual(0);
    }
  }
});

// ─── Sold-out guard ───────────────────────────────────────────────────────────

test('[founding] payment-intent sold_out disables submit with sold-out message', async ({ page }) => {
  await mockSupabaseConfig(page);
  await page.route('/api/payment-intent', (route) =>
    route.fulfill({
      status: 200,
      contentType: 'application/json',
      body: JSON.stringify({ error: 'sold_out' }),
    })
  );
  await injectMockStripe(page);
  await page.goto('/checkout?plan=founding');

  // Wait for sold_out message to appear
  await page.waitForFunction(
    () => {
      const msg = document.getElementById('payment-message');
      return msg && msg.style.display !== 'none' && msg.textContent && msg.textContent.includes('spot');
    },
    { timeout: 8000 }
  );

  const msg = page.locator('#payment-message');
  await expect(msg).toBeVisible();
  const text = (await msg.textContent()) ?? '';
  expect(text.toLowerCase()).toMatch(/sold out|spot|founding|professional/);

  // Submit button should be disabled
  const btn = page.locator('#submit-btn');
  const isDisabled = await btn.getAttribute('disabled');
  expect(isDisabled).not.toBeNull();
});

// ─── Live Stripe test-card tests (requires STRIPE_LIVE=1) ─────────────────────
// These only run when the stage has a real pk_test_ key and Stripe is reachable.

test.describe('Stripe live test-card flows', () => {
  test.skip(!STRIPE_LIVE, 'Set STRIPE_LIVE=1 to run these against a configured test-mode stage');

  test('[professional] test card 4242 redirects to /account?welcome=1', async ({ page }) => {
    await page.goto('/checkout?plan=professional');

    // Wait for Stripe payment element iframe to mount
    const stripeFrame = page.frameLocator('iframe[title*="Secure payment"]');
    await expect(stripeFrame.locator('[placeholder*="1234"]')).toBeVisible({ timeout: 15000 });

    await page.fill('#buyer-name', 'Playwright Tester');
    await page.fill('#buyer-email', 'playwright@neurontest.invalid');

    // Fill card details inside Stripe iframe
    await stripeFrame.locator('[placeholder*="1234"]').fill('4242424242424242');
    await stripeFrame.locator('[placeholder="MM / YY"]').fill('12 / 30');
    await stripeFrame.locator('[placeholder="CVC"]').fill('123');
    await stripeFrame.locator('[placeholder="ZIP"]').fill('10001').catch(() => {}); // optional field

    await page.locator('#submit-btn').click();
    await page.waitForURL('**/account**', { timeout: 30000 });
    expect(page.url()).toContain('welcome=1');
  });

  test('[professional] test card 4000 0000 0000 0002 (decline) shows error', async ({ page }) => {
    await page.goto('/checkout?plan=professional');

    const stripeFrame = page.frameLocator('iframe[title*="Secure payment"]');
    await expect(stripeFrame.locator('[placeholder*="1234"]')).toBeVisible({ timeout: 15000 });

    await page.fill('#buyer-name', 'Declined User');
    await page.fill('#buyer-email', 'declined@neurontest.invalid');

    await stripeFrame.locator('[placeholder*="1234"]').fill('4000000000000002');
    await stripeFrame.locator('[placeholder="MM / YY"]').fill('12 / 30');
    await stripeFrame.locator('[placeholder="CVC"]').fill('123');

    await page.locator('#submit-btn').click();

    const msg = page.locator('#payment-message');
    await expect(msg).toBeVisible({ timeout: 15000 });
    const text = (await msg.textContent()) ?? '';
    expect(text.toLowerCase()).toMatch(/declined|failed|card/);
  });

  test('[founding] test card 4242 + attestation → redirect to /account', async ({ page }) => {
    await page.goto('/checkout?plan=founding');

    const stripeFrame = page.frameLocator('iframe[title*="Secure payment"]');
    await expect(stripeFrame.locator('[placeholder*="1234"]')).toBeVisible({ timeout: 15000 });

    await page.fill('#buyer-name', 'Founder Playwright');
    await page.fill('#buyer-email', 'founder@neurontest.invalid');
    await page.locator('#founding-attest-cb').check();

    await stripeFrame.locator('[placeholder*="1234"]').fill('4242424242424242');
    await stripeFrame.locator('[placeholder="MM / YY"]').fill('12 / 30');
    await stripeFrame.locator('[placeholder="CVC"]').fill('123');

    await page.locator('#submit-btn').click();
    await page.waitForURL('**/account**', { timeout: 30000 });
    expect(page.url()).toContain('welcome=1');
  });
});
