import { chromium } from 'playwright';

const BASE = 'http://localhost:5173';

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

// After a successful password submit, the app shows a 2FA screen with the code
// displayed in the UI. This helper waits for it, reads the code, and submits.
async function complete2FA(page) {
  const codeEl = page.locator('[class*="code-number"]').first();
  try {
    await codeEl.waitFor({ state: 'visible', timeout: 4000 });
  } catch {
    return; // 2FA screen did not appear — user likely already navigated
  }
  const code = await codeEl.innerText().catch(() => '');
  if (!code) return;
  await page.locator('input#code').fill(code.trim());
  await page.locator('button[type="submit"]').click();
  await sleep(500);
}

// Reset seed reservations so the test is repeatable across runs.
async function resetDb() {
  const api = 'http://localhost:3001';
  const patch = (id, body) =>
    fetch(`${api}/reservations/${id}`, {
      method: 'PATCH',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    });
  // Ensure r1 and r2 are pending so admin-confirm test always finds a button.
  await patch('r1', { status: 'pending' });
  await patch('r2', { status: 'pending' });
}

async function run() {
  await resetDb();
  const browser = await chromium.launch({ headless: true });
  const ctx = await browser.newContext();
  const page = await ctx.newPage();

  // Capture browser console errors
  const pageErrors = [];
  page.on('pageerror', (err) => pageErrors.push(err.message));
  page.on('console', (msg) => {
    if (msg.type() === 'error') pageErrors.push(msg.text());
  });

  const results = [];
  const ok   = (l, n='') => results.push(`PASS  ${l}${n ? ' — '+n : ''}`);
  const fail = (l, n='') => results.push(`FAIL  ${l}${n ? ' — '+n : ''}`);
  const warn = (l, n='') => results.push(`WARN  ${l}${n ? ' — '+n : ''}`);

  // ── 1. Login page loads ────────────────────────────────────────────────────
  await page.goto(BASE + '/login');
  await page.waitForLoadState('networkidle');
  const hasEmailInput = await page.locator('input[type="email"]').isVisible().catch(() => false);
  hasEmailInput ? ok('Login page loads (email field visible)') : fail('Login page missing email field');

  // ── 2. Wrong credentials show error ───────────────────────────────────────
  await page.fill('input[type="email"]', 'wrong@example.com');
  await page.fill('input[type="password"]', 'wrongpass');
  await page.click('button[type="submit"]');
  await sleep(1000);
  const errorVisible = await page.locator('text=/invalid|incorrect|wrong/i').isVisible().catch(() => false);
  errorVisible ? ok('Wrong credentials → error message') : fail('Wrong credentials — no error shown');

  // ── 3. Admin login ────────────────────────────────────────────────────────
  await page.fill('input[type="email"]', 'admin@reservations.dev');
  await page.fill('input[type="password"]', 'Admin1234!');
  await page.click('button[type="submit"]');
  await complete2FA(page);
  await page.waitForURL('**/admin', { timeout: 5000 }).catch(() => {});
  page.url().includes('/admin')
    ? ok('Admin login → /admin')
    : fail('Admin redirect failed', page.url());

  // ── 4. Admin table rows ───────────────────────────────────────────────────
  await page.waitForSelector('table tbody tr', { timeout: 5000 }).catch(() => {});
  const rowCount = await page.locator('tbody tr').count().catch(() => 0);
  rowCount > 0 ? ok('Admin table', `${rowCount} rows`) : fail('Admin table empty');

  // ── 5. Confirm button on pending row ─────────────────────────────────────
  const confirmBtn = page.locator('[aria-label^="Confirm reservation"]').first();
  const hasConfirm = await confirmBtn.isVisible().catch(() => false);
  hasConfirm ? ok('Confirm button visible') : fail('Confirm button missing');

  if (hasConfirm) {
    await confirmBtn.click();
    await sleep(1200);
    const confirmedBadge = await page.locator('text=Confirmed').count();
    confirmedBadge >= 1
      ? ok('Confirm click updates row to Confirmed')
      : warn('Confirmed badge not found after click');
  }

  // ── 6. Delete modal ───────────────────────────────────────────────────────
  // Wait for table to re-render after previous action
  await page.waitForSelector('table tbody tr', { timeout: 4000 }).catch(() => {});
  await sleep(500);
  const deleteBtn = page.locator('[aria-label^="Delete reservation"]').first();
  await deleteBtn.click().catch(() => {});
  await sleep(600);
  const modalVisible = await page.locator('[role="dialog"]').isVisible().catch(() => false);
  modalVisible ? ok('Delete confirmation modal opens') : fail('Delete modal did not open');
  // Dismiss via Cancel button
  const cancelModalBtn = page.locator('[class*="modal__btn--cancel"]').first();
  await cancelModalBtn.click().catch(() => {
    page.keyboard.press('Escape').catch(() => {});
  });
  await sleep(300);

  // ── 7. Calendar tab ───────────────────────────────────────────────────────
  const calTab = page.locator('button', { hasText: /Calendar/i });
  const hasCalTab = await calTab.isVisible().catch(() => false);
  hasCalTab ? ok('Calendar tab button visible') : fail('Calendar tab missing');

  if (hasCalTab) {
    await calTab.click();
    await sleep(600);
    const monthTitle = await page.locator('text=/January|February|March|April|May|June|July|August|September|October|November|December/').first().innerText().catch(() => '');
    monthTitle ? ok('Calendar view renders month', monthTitle) : fail('Calendar month title missing');
    const monCell = await page.locator('text=Mon').first().isVisible().catch(() => false);
    monCell ? ok('Calendar weekday headers render') : fail('Calendar weekday headers missing');

    // Click a day cell that has bookings
    const busyCell = page.locator('[class*="cell--busy"]').first();
    const hasBusy = await busyCell.isVisible().catch(() => false);
    if (hasBusy) {
      await busyCell.click();
      await sleep(400);
      const panelVisible = await page.locator('[class*="cal__panel"]').isVisible().catch(() => false);
      panelVisible ? ok('Clicking busy day opens side panel') : fail('Calendar side panel did not open');
    } else {
      warn('No busy day cells visible in current month view');
    }

    // Navigate to next month
    await page.locator('[aria-label="Next month"]').click().catch(() => {});
    await sleep(400);
    const newMonth = await page.locator('text=/January|February|March|April|May|June|July|August|September|October|November|December/').first().innerText().catch(() => '');
    newMonth !== monthTitle
      ? ok('Calendar next-month navigation', newMonth)
      : fail('Calendar did not advance to next month');
  }

  // ── 8. Admin logout → client login ───────────────────────────────────────
  await page.locator('button:has-text("Table")').click().catch(() => {});
  await sleep(200);
  await page.click('button:has-text("Sign out")').catch(() => {});
  await page.waitForURL('**/login', { timeout: 3000 }).catch(() => {});
  page.url().includes('/login') ? ok('Admin logout → /login') : warn('Logout redirect', page.url());

  await page.fill('input[type="email"]', 'anna.kowalski@example.com');
  await page.fill('input[type="password"]', 'Client1234!');
  await page.click('button[type="submit"]');
  await complete2FA(page);
  await page.waitForURL('**/dashboard', { timeout: 5000 }).catch(() => {});
  page.url().includes('/dashboard') ? ok('Client login → /dashboard') : fail('Dashboard redirect', page.url());

  // ── 9. AvailabilitySearch ─────────────────────────────────────────────────
  await page.waitForLoadState('networkidle');
  await sleep(500);
  const availTitle = await page.locator('text=Check Availability').isVisible().catch(() => false);
  availTitle ? ok('AvailabilitySearch section renders') : fail('AvailabilitySearch missing');

  // ── 10. URL sync ──────────────────────────────────────────────────────────
  await page.locator('input[type="date"]').first().fill('2026-07-15');
  await page.locator('select').first().selectOption({ index: 1 });
  await sleep(1200);
  const urlAfterFilter = page.url();
  urlAfterFilter.includes('date=2026-07-15') ? ok('Date syncs to URL') : fail('Date param missing', urlAfterFilter);
  urlAfterFilter.includes('service=') ? ok('Service syncs to URL') : fail('Service param missing', urlAfterFilter);

  // ── 11. Time slot grid ────────────────────────────────────────────────────
  const slotGrid = await page.locator('button:has-text("09:00")').isVisible().catch(() => false);
  slotGrid ? ok('Time slot grid renders (09:00 visible)') : fail('Time slot grid missing');

  // ── 12. Slot click pre-fills wizard ──────────────────────────────────────
  const freeSlot = page.locator('[class*="slot--free"]').first();
  const hasFreeSlot = await freeSlot.isVisible().catch(() => false);
  if (hasFreeSlot) {
    const slotText = await freeSlot.innerText().catch(() => '?');
    await freeSlot.click();
    await sleep(600);
    const step3Active = await page.locator('text=/Special Requirements/i').isVisible().catch(() => false);
    step3Active ? ok(`Slot click (${slotText}) jumps wizard to step 3`) : fail('Slot click did not advance wizard');
  } else {
    warn('No free slots for selected date/service');
  }

  // ── 13. BookingWizard step navigation ────────────────────────────────────
  await page.goto(BASE + '/dashboard');
  await page.waitForLoadState('networkidle');
  await page.waitForSelector('[class*="wizard__step-title"]', { timeout: 5000 }).catch(() => {});
  const wizardTitle = await page.locator('[class*="wizard__step-title"]').first().innerText().catch(() => '');
  wizardTitle.includes('Select a Service')
    ? ok('Wizard renders at step 1 after reload')
    : fail('Wizard step 1 title missing', `got: "${wizardTitle}"`);

  // Pick a service card
  const serviceCard = page.locator('[class*="service-card"]').first();
  const hasCards = await serviceCard.isVisible().catch(() => false);
  hasCards ? ok('Service cards render') : fail('Service cards missing');
  if (hasCards) await serviceCard.click();

  // Next → step 2
  await page.locator('button:has-text("Next")').first().click().catch(() => {});
  await sleep(400);
  const step2Title = await page.locator('[class*="wizard__step-title"]').first().innerText().catch(() => '');
  step2Title.includes('Date') ? ok('Wizard step 2 (Date & Time)') : fail('Step 2 not reached', step2Title);

  // Fill date + time for a date with no conflicts
  await page.locator('input[type="date"]').last().fill('2026-09-01');
  await page.locator('input[type="time"]').last().fill('14:30');
  await page.locator('button:has-text("Next")').first().click().catch(() => {});
  await sleep(400);

  const step3Title = await page.locator('[class*="wizard__step-title"]').first().innerText().catch(() => '');
  step3Title.includes('Requirements') ? ok('Wizard step 3 (Requirements)') : fail('Step 3 not reached', step3Title);

  await page.locator('button:has-text("Next")').first().click().catch(() => {});
  await sleep(400);

  const step4Title = await page.locator('[class*="wizard__step-title"]').first().innerText().catch(() => '');
  step4Title.includes('Summary') ? ok('Wizard step 4 (Summary)') : fail('Step 4 not reached', step4Title);

  // ── 14. Payment modal full flow ───────────────────────────────────────────
  const payBtn = page.locator('button:has-text("Confirm & Pay")');
  const hasPayBtn = await payBtn.isVisible().catch(() => false);
  hasPayBtn ? ok('Confirm & Pay button in step 4') : fail('Confirm & Pay button missing');

  if (hasPayBtn) {
    await payBtn.click();
    await sleep(500);
    const payModalVisible = await page.locator('text=/Secure Payment/i').isVisible().catch(() => false);
    payModalVisible ? ok('Payment modal opens') : fail('Payment modal did not open');

    if (payModalVisible) {
      const depositAmt = await page.locator('[class*="deposit-amt"]').innerText().catch(() => '');
      depositAmt ? ok('Deposit amount displayed', depositAmt) : fail('Deposit amount missing');

      // Fill card form using placeholder selectors
      await page.locator('input[placeholder*="1234"]').fill('4111111111111111');
      await page.locator('input[placeholder*="MM"]').fill('12/28');
      await page.locator('input[type="password"]').fill('123');
      await page.locator('input[placeholder*="John"]').fill('Jan Kowalski');
      await sleep(300);

      // Check no validation errors yet
      const payDepositBtn = page.locator('[class*="pay__pay-btn"]');
      await payDepositBtn.click().catch(() => {});

      // Wait for PaymentModal to reach 'done' phase (~1.5s) then createReservation POST (~200ms)
      // then setPaymentOpen(false) causing overlay to disappear
      await page.locator('[class*="pay__overlay"]').waitFor({ state: 'hidden', timeout: 8000 }).catch(() => {});
      await sleep(400);

      const bookingConfirmed = await page.locator('text=/Booking Confirmed/i').isVisible().catch(() => false);
      bookingConfirmed
        ? ok('Full booking flow — Booking Confirmed shown')
        : fail('Booking Confirmed not shown after payment');

      // Check receipt fields appear
      const receiptVisible = await page.locator('[class*="wizard__receipt"]').isVisible().catch(() => false);
      receiptVisible ? ok('Receipt block rendered') : warn('Receipt block not visible');
    }
  }

  // ── 15. My Reservations sidebar ──────────────────────────────────────────
  // Navigate fresh to see sidebar (not in done state)
  await page.goto(BASE + '/dashboard');
  await page.waitForLoadState('networkidle');
  await sleep(600);
  const myResVisible = await page.locator('text=/My Reservations/i').first().isVisible().catch(() => false);
  myResVisible ? ok('My Reservations sidebar renders') : fail('My Reservations missing');

  // Check it shows at least one reservation for this user (u2 has multiple)
  const resItems = await page.locator('[class*="my-res__item"]').count().catch(() => 0);
  resItems > 0 ? ok('My Reservations lists items', `${resItems} items`) : fail('No reservation items found');

  // ── 16. Cancel from My Reservations ──────────────────────────────────────
  const cancelResBtn = page.locator('[class*="my-res__cancel-btn"]').first();
  const hasCancelRes = await cancelResBtn.isVisible().catch(() => false);
  if (hasCancelRes) {
    const countBefore = await page.locator('[class*="my-res__item"]').count();
    await cancelResBtn.click();
    await sleep(1000);
    const cancelledBadge = await page.locator('text=Cancelled').count();
    cancelledBadge >= 1 ? ok('Cancel from My Reservations updates status') : warn('Cancelled badge not found after cancel');
  } else {
    warn('No cancellable reservations visible in sidebar');
  }

  // ── 17. Dark mode toggle ─────────────────────────────────────────────────
  const themeBtn = page.locator('[aria-label*="dark"],[aria-label*="light"],[aria-label*="theme"]').first();
  const hasTheme = await themeBtn.isVisible().catch(() => false);
  if (hasTheme) {
    await themeBtn.click();
    await sleep(300);
    const theme = await page.evaluate(() => document.documentElement.getAttribute('data-theme'));
    theme === 'dark' ? ok('Dark mode toggle works') : warn('data-theme after toggle', theme ?? 'null');
  } else {
    warn('Theme toggle not found');
  }

  // ── 18. Protected route (client blocked from /admin) ─────────────────────
  await page.goto(BASE + '/admin');
  await sleep(600);
  page.url().includes('/dashboard')
    ? ok('Client blocked from /admin → redirected to /dashboard')
    : fail('Client could access /admin', page.url());

  // ── Errors ────────────────────────────────────────────────────────────────
  if (pageErrors.length > 0) {
    warn('Browser console errors during test', pageErrors.slice(0, 3).join(' | '));
  }

  await browser.close();

  console.log('\n' + '═'.repeat(62));
  console.log('  MANUAL TEST RESULTS  —  ' + new Date().toISOString().slice(0,19));
  console.log('═'.repeat(62));
  results.forEach((r) => console.log('  ' + r));
  const passed = results.filter((r) => r.startsWith('PASS')).length;
  const failed = results.filter((r) => r.startsWith('FAIL')).length;
  const warned = results.filter((r) => r.startsWith('WARN')).length;
  console.log('═'.repeat(62));
  console.log(`  ${passed} passed  ·  ${warned} warnings  ·  ${failed} failed`);
  console.log('═'.repeat(62));
  if (failed > 0) process.exit(1);
}

run().catch((err) => { console.error(err); process.exit(1); });
