import type { Page } from '@playwright/test';
import fs from 'node:fs/promises';

import { dismissConsentBanner, expectFixture as expect, test } from '../helpers/test-fixtures';

const shouldRun = process.env.E2E_PADDLE_SANDBOX === '1';
const baseUrl = process.env.E2E_BASE_URL ?? 'https://test-y0k0.fotospiel.app';
const locale = process.env.E2E_LOCALE ?? 'de';
const checkoutSlug = locale === 'en' ? 'checkout' : 'bestellen';
const tenantEmail = buildTenantEmail();
const tenantPassword = process.env.E2E_TENANT_PASSWORD ?? null;
const sandboxCard = {
  number: process.env.E2E_PADDLE_CARD_NUMBER ?? '4242 4242 4242 4242',
  expiry: process.env.E2E_PADDLE_CARD_EXPIRY ?? '12/34',
  cvc: process.env.E2E_PADDLE_CARD_CVC ?? '123',
  name: process.env.E2E_PADDLE_CARD_NAME ?? 'Playwright Tester',
  postal: process.env.E2E_PADDLE_CARD_POSTAL ?? '10115',
};

test.use({
  channel: process.env.E2E_BROWSER_CHANNEL ?? 'chrome',
  userAgent:
    process.env.E2E_USER_AGENT ??
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
  launchOptions: {
    args: ['--disable-blink-features=AutomationControlled'],
  },
});

test.describe('Paddle sandbox full flow (staging)', () => {
  test.skip(!shouldRun, 'Set E2E_PADDLE_SANDBOX=1 to run live sandbox checkout on staging.');
  test.skip(!tenantEmail || !tenantPassword, 'Set E2E_TENANT_EMAIL and E2E_TENANT_PASSWORD for sandbox flow.');

  test('register, pay via Paddle sandbox, and login to event admin', async ({ page, request }, testInfo) => {
    const paddleNetworkLog: string[] = [];

    page.on('response', async (response) => {
      const url = response.url();
      if (!/paddle/i.test(url)) {
        return;
      }

      const status = response.status();
      let bodySnippet = '';
      if (status >= 400 || /checkout|error/i.test(url)) {
        try {
          const text = await response.text();
          bodySnippet = text.trim().slice(0, 2000);
        } catch {
          bodySnippet = '';
        }
      }

      const entry = [`[${status}] ${url}`, bodySnippet].filter(Boolean).join('\n');
      paddleNetworkLog.push(entry);
      if (paddleNetworkLog.length > 40) {
        paddleNetworkLog.shift();
      }
    });

    await page.addInitScript(() => {
      Object.defineProperty(navigator, 'webdriver', {
        get: () => undefined,
      });
    });

    try {
      // Jump directly into wizard for Standard package (2)
      await page.goto(`${baseUrl}/${locale}/${checkoutSlug}/2`);

      await dismissConsentBanner(page);

      await proceedToAccountStep(page);

      await completeRegistrationOrLogin(page, {
        email: tenantEmail!,
        password: tenantPassword!,
      });

      const termsCheckbox = page.locator('#checkout-terms-hero');
      await expect(termsCheckbox).toBeVisible();
      await termsCheckbox.click();

      const checkoutCta = page.getByRole('button', { name: /Weiter mit Paddle|Continue with Paddle/i }).first();
      await expect(checkoutCta).toBeVisible({ timeout: 20000 });

      const [apiResponse] = await Promise.all([
        page.waitForResponse((resp) => resp.url().includes('/paddle/create-checkout') && resp.status() < 500),
        checkoutCta.click(),
      ]);

      const rawBody = await apiResponse.text();
      let checkoutPayload: Record<string, unknown> | null = null;
      try {
        checkoutPayload = rawBody && rawBody.trim().startsWith('{') ? JSON.parse(rawBody) : null;
      } catch {
        checkoutPayload = null;
      }

      const inlineMode = checkoutPayload?.mode === 'inline' || checkoutPayload?.inline === true;
      const checkoutUrl = extractCheckoutUrl(checkoutPayload, rawBody);

      if (!inlineMode) {
        expect(checkoutUrl).toContain('paddle');
      }

      // Navigate to Paddle hosted checkout and complete payment.
      if (inlineMode) {
        await expect(
          page.getByText(/Checkout geöffnet|Checkout opened|Paddle-Checkout/i).first()
        ).toBeVisible({ timeout: 20_000 });
        await waitForPaddleCardInputs(page, ['input[autocomplete="cc-number"]', 'input[name="cardnumber"]', 'input[name="card_number"]']);
      } else if (checkoutUrl) {
        await page.goto(checkoutUrl);
        await expect(page).toHaveURL(/paddle/);
      } else {
        throw new Error(`Missing Paddle checkout URL. Response: ${rawBody}`);
      }

      await completeHostedPaddleCheckout(page, sandboxCard);

      await expect
        .poll(
          async () => {
            const latestCompleted = await request.get('/api/_testing/checkout/sessions/latest', {
              params: { status: 'completed', email: tenantEmail },
            });
            if (!latestCompleted.ok()) {
              return null;
            }

            const json = await latestCompleted.json();
            return json?.data?.status ?? null;
          },
          { timeout: 120_000 }
        )
        .toBe('completed');

      await page.goto(`${baseUrl}/event-admin/login`);
      await dismissConsentBanner(page);

      await page.locator('input[name="identifier"]').fill(tenantEmail!);
      await page.locator('input[name="password"]').fill(tenantPassword!);
      await page.getByRole('button', { name: /Anmelden|Login/i }).click();

      await expect(page).toHaveURL(/\/event-admin\/mobile\/(dashboard|welcome)/i, { timeout: 30_000 });
      await expect(page.getByText(/Dashboard|Willkommen/i)).toBeVisible();
    } finally {
      if (paddleNetworkLog.length > 0) {
        const logPath = testInfo.outputPath('paddle-network-log.txt');
        await fs.writeFile(logPath, paddleNetworkLog.join('\n\n'), 'utf8');
        await testInfo.attach('paddle-network-log', {
          path: logPath,
          contentType: 'text/plain',
        });
      }
    }
  });
});

async function completeRegistrationOrLogin(page: Page, credentials: { email: string; password: string }): Promise<void> {
  const identifierInput = page.locator('input[name="identifier"]');
  if (await identifierInput.isVisible()) {
    await identifierInput.fill(credentials.email);
    await page.locator('input[name="password"]').fill(credentials.password);
    await page.getByRole('button', { name: /^Anmelden$|Login/i }).last().click();
    await expect(page.getByPlaceholder(/Gutscheincode|Coupon/i)).toBeVisible({ timeout: 20_000 });
    return;
  }

  await page.fill('input[name="first_name"]', 'Play');
  await page.fill('input[name="last_name"]', 'Wright');
  await page.fill('input[name="email"]', credentials.email);
  const addressInput = page.locator('input[name="address"]');
  if (await addressInput.isVisible()) {
    await addressInput.fill('Teststrasse 1, 12345 Berlin');
  }

  const phoneInput = page.locator('input[name="phone"]');
  if (await phoneInput.isVisible()) {
    await phoneInput.fill('+49123456789');
  }

  const usernameInput = page.locator('input[name="username"]');
  if (await usernameInput.isVisible()) {
    await usernameInput.fill(credentials.email);
  }
  await page.fill('input[name="password"]', credentials.password);
  await page.fill('input[name="password_confirmation"]', credentials.password);

  await page.check('input[name="privacy_consent"]');
  await page.getByRole('button', { name: /Registrieren|Register/i }).last().click();

  try {
    await expect(page.getByPlaceholder(/Gutscheincode|Coupon/i)).toBeVisible({ timeout: 20_000 });
    return;
  } catch {
    const loginButton = page.getByRole('button', { name: /^Anmelden$|Login/i }).first();
    if (await loginButton.isVisible()) {
      await loginButton.click();
    }
  }

  await expect(page.locator('input[name="identifier"]')).toBeVisible({ timeout: 10_000 });
  await page.locator('input[name="identifier"]').fill(credentials.email);
  await page.locator('input[name="password"]').fill(credentials.password);
  await page.getByRole('button', { name: /^Anmelden$|Login/i }).last().click();
  await expect(page.getByPlaceholder(/Gutscheincode|Coupon/i)).toBeVisible({ timeout: 20_000 });
}

async function proceedToAccountStep(page: Page, timeoutMs = 30_000): Promise<void> {
  const deadline = Date.now() + timeoutMs;

  while (Date.now() < deadline) {
    const accountForm = page.locator('input[name="first_name"], input[name="identifier"]');
    if (await accountForm.isVisible()) {
      return;
    }

    await dismissConsentBanner(page);

    const continueButton = page.getByRole('button', { name: /Weiter|Continue/i }).first();
    if (await continueButton.isVisible()) {
      if (await continueButton.isEnabled()) {
        await continueButton.click();
      }
    }

    await page.waitForTimeout(500);
  }

  throw new Error('Account step did not load in time.');
}

async function completeHostedPaddleCheckout(
  page: Page,
  card: { number: string; expiry: string; cvc: string; name: string; postal: string }
): Promise<void> {
  const cardNumberSelectors = [
    'input[autocomplete="cc-number"]',
    'input[name="cardnumber"]',
    'input[name="card_number"]',
  ];
  const expirySelectors = [
    'input[autocomplete="cc-exp"]',
    'input[name="exp-date"]',
    'input[name="exp_date"]',
    'input[name="expiry"]',
  ];
  const cvcSelectors = [
    'input[autocomplete="cc-csc"]',
    'input[name="cvc"]',
    'input[name="security-code"]',
    'input[name="cvv"]',
  ];
  const nameSelectors = [
    'input[autocomplete="cc-name"]',
    'input[name="cardholder"]',
    'input[name="cardholder_name"]',
    'input[name="cardholder-name"]',
  ];
  const postalSelectors = [
    'input[autocomplete="postal-code"]',
    'input[name="postal"]',
    'input[name="postal_code"]',
    'input[name="zip"]',
  ];

  await maybeFillInAnyFrame(page, nameSelectors, card.name);
  await fillInAnyFrame(page, cardNumberSelectors, card.number);
  await fillInAnyFrame(page, expirySelectors, card.expiry);
  await fillInAnyFrame(page, cvcSelectors, card.cvc);
  await maybeFillInAnyFrame(page, postalSelectors, card.postal);

  const payButton = page.getByRole('button', {
    name: /Jetzt bezahlen|Pay now|Complete order|Order now|Kaufen|Bezahlen|Zahlung abschließen/i,
  }).first();
  await expect(payButton).toBeVisible({ timeout: 20_000 });
  await payButton.click();
}

async function waitForPaddleCardInputs(page: Page, selectors: string[], timeoutMs = 30_000): Promise<void> {
  const deadline = Date.now() + timeoutMs;

  while (Date.now() < deadline) {
    if (await hasAnySelector(page, selectors)) {
      return;
    }

    if (await hasAnyText(page, /Something went wrong|try again later/i)) {
      throw new Error('Paddle inline checkout returned an error in the iframe.');
    }

    await page.waitForTimeout(500);
  }

  throw new Error('Paddle card inputs did not appear in time.');
}

async function hasAnySelector(page: Page, selectors: string[]): Promise<boolean> {
  if (await hasSelectorInFrame(page, selectors)) {
    return true;
  }

  for (const frame of page.frames()) {
    if (await hasSelectorInFrame(frame, selectors)) {
      return true;
    }
  }

  return false;
}

async function hasSelectorInFrame(scope: Page | import('@playwright/test').Frame, selectors: string[]): Promise<boolean> {
  for (const selector of selectors) {
    if ((await scope.locator(selector).count()) > 0) {
      return true;
    }
  }

  return false;
}

async function hasAnyText(page: Page, matcher: RegExp): Promise<boolean> {
  if ((await page.getByText(matcher).count()) > 0) {
    return true;
  }

  for (const frame of page.frames()) {
    if ((await frame.getByText(matcher).count()) > 0) {
      return true;
    }
  }

  return false;
}

function extractCheckoutUrl(payload: Record<string, unknown> | null, rawBody: string): string | null {
  if (payload) {
    const directUrl = payload.checkout_url ?? payload.url ?? payload.checkoutUrl;
    if (typeof directUrl === 'string') {
      return directUrl;
    }
  }

  const trimmed = rawBody.trim();
  if (/^https?:\/\//i.test(trimmed)) {
    return trimmed;
  }

  if (trimmed.startsWith('<')) {
    const match = trimmed.match(/https?:\/\/["'a-zA-Z0-9._~:/?#@!$&'()*+,;=%-]+/);
    if (match) {
      return match[0];
    }
  }

  return null;
}

function buildTenantEmail(): string | null {
  const rawEmail = process.env.E2E_TENANT_EMAIL ?? process.env.E2E_PADDLE_EMAIL ?? null;
  if (!rawEmail) {
    return null;
  }

  if (!rawEmail.includes('{timestamp}')) {
    return rawEmail;
  }

  const timestamp = Math.floor(Date.now() / 1000).toString();
  return rawEmail.replaceAll('{timestamp}', timestamp);
}

async function fillInAnyFrame(page: Page, selectors: string[], value: string): Promise<void> {
  const filled = await attemptFill(page, selectors, value);
  if (filled) {
    return;
  }

  const frames = page.frames();
  for (const frame of frames) {
    const frameFilled = await attemptFill(frame, selectors, value);
    if (frameFilled) {
      return;
    }
  }

  throw new Error(`Unable to find input for selectors: ${selectors.join(', ')}`);
}

async function maybeFillInAnyFrame(page: Page, selectors: string[], value: string): Promise<void> {
  const filled = await attemptFill(page, selectors, value);
  if (filled) {
    return;
  }

  const frames = page.frames();
  for (const frame of frames) {
    const frameFilled = await attemptFill(frame, selectors, value);
    if (frameFilled) {
      return;
    }
  }
}

async function attemptFill(
  scope: Page | import('@playwright/test').Frame,
  selectors: string[],
  value: string
): Promise<boolean> {
  for (const selector of selectors) {
    const locator = scope.locator(selector).first();
    if ((await locator.count()) === 0) {
      continue;
    }

    try {
      await locator.fill(value);
      return true;
    } catch {
      try {
        await locator.click();
        await locator.type(value, { delay: 25 });
        return true;
      } catch {
        continue;
      }
    }
  }

  return false;
}
