import { chromium } from 'playwright';
import { mkdir, writeFile } from 'fs/promises';
import path from 'path';

// Parse CLI args
const args = process.argv.slice(2);
const labelIdx = args.indexOf('--label');
if (labelIdx === -1 || !args[labelIdx + 1]) {
  console.error('Usage: node scripts/test-v14.mjs --label <name> [--headless]');
  process.exit(1);
}
const label = args[labelIdx + 1].replace(/[^a-zA-Z0-9_-]/g, '-');
const headless = args.includes('--headless');

const BASE_URL = 'http://encoder.local:30014';
const ADMIN_PASSWORD = 'slamdek6';
const GM_PASSWORD = 'slamdek6';
const WORLD_NAME = 'test-world';
const SCREENSHOT_DIR = path.resolve(new URL('.', import.meta.url).pathname, 'screenshots', label);

const consoleMessages = [];

async function ensureDir(dir) {
  await mkdir(dir, { recursive: true });
}

async function screenshot(page, name) {
  const filePath = path.join(SCREENSHOT_DIR, name);
  await page.screenshot({ path: filePath, fullPage: true });
  console.log(`  screenshot: ${filePath}`);
  return filePath;
}

async function tryStep(stepLabel, fn) {
  try {
    await fn();
  } catch (err) {
    console.warn(`  [warn] step "${stepLabel}" failed: ${err.message}`);
  }
}

/**
 * Handle admin authentication on the /setup page.
 * Only called when we are actually on the /auth page and need to auth.
 * Foundry v14 uses input[name="adminPassword"] on the /auth page.
 */
async function handleAdminAuth(page) {
  const adminInput = page.locator('input[name="adminPassword"], input[name="adminKey"]').first();
  const isVisible = await adminInput.isVisible().catch(() => false);
  if (!isVisible) return false;

  console.log('  admin auth form detected, filling credentials...');
  await adminInput.fill(ADMIN_PASSWORD);

  const submitBtn = page.locator('[data-action="adminAuth"], #setup-authentication button[type="submit"], #setup-authentication input[type="submit"]').first();
  const submitVisible = await submitBtn.isVisible().catch(() => false);
  if (submitVisible) {
    await submitBtn.click();
  } else {
    await adminInput.press('Enter');
  }

  await page.waitForURL((url) => !url.toString().includes('/auth'), { timeout: 10000 }).catch(() => {});
  console.log('  admin auth submitted');
  return true;
}

/**
 * Navigate to setup page and launch the world if it is not already running.
 * If the world is already running, /setup redirects to /join and we handle that.
 */
async function ensureOnJoinPage(page) {
  const currentUrl = page.url();

  if (currentUrl.includes('/join')) {
    console.log('  world already running, already on /join');
    return;
  }

  if (currentUrl.includes('/setup') || currentUrl.includes('/auth')) {
    // May need admin auth
    await handleAdminAuth(page);
    await page.waitForTimeout(1000);
  }

  // Try to navigate to setup and look for our world
  if (!page.url().includes('/join')) {
    await page.goto(`${BASE_URL}/setup`, { waitUntil: 'networkidle', timeout: 15000 });

    const afterUrl = page.url();

    if (afterUrl.includes('/join')) {
      console.log('  world already running (redirected to /join from /setup)');
      return;
    }

    if (afterUrl.includes('/auth') || afterUrl.includes('/setup')) {
      await handleAdminAuth(page);
      await page.waitForTimeout(1000);
    }

    if (!page.url().includes('/join')) {
      // On setup page — check if test-world entry exists (presence = compatible system)
      console.log('  on setup page, checking for test-world...');
      await page.waitForSelector(`[data-package-id="${WORLD_NAME}"]`, { timeout: 15000 }).catch(() => {});

      const worldExists = await page.locator(`[data-package-id="${WORLD_NAME}"]`).count().catch(() => 0);
      if (!worldExists) {
        // No world entry — take setup screenshot and exit
        console.warn(`  world "${WORLD_NAME}" not found on setup page. Screenshotting setup.`);
        await ensureDir(SCREENSHOT_DIR);
        await page.screenshot({ path: path.join(SCREENSHOT_DIR, '00-setup-incompatible.png'), fullPage: true });
        console.log(`  screenshot: ${path.join(SCREENSHOT_DIR, '00-setup-incompatible.png')}`);
        return 'incompatible';
      }

      // Check if system is compatible (no error badge) by looking for the launch control
      const launchExists = await page.evaluate((worldName) => {
        const el = document.querySelector(`[data-package-id="${worldName}"] [data-action="worldLaunch"]`);
        return !!el;
      }, WORLD_NAME);

      if (!launchExists) {
        // Launch button absent — system incompatible with this Foundry version
        console.warn(`  no launch button for "${WORLD_NAME}" — system may be incompatible. Screenshotting setup page.`);
        await ensureDir(SCREENSHOT_DIR);
        await page.screenshot({ path: path.join(SCREENSHOT_DIR, '00-setup-incompatible.png'), fullPage: true });
        console.log(`  screenshot: ${path.join(SCREENSHOT_DIR, '00-setup-incompatible.png')}`);
        return 'incompatible';
      }

      // Launch via API: POST /setup {action:'launchWorld', world:'<id>'}
      console.log('  launching world via API...');
      const launchResp = await page.evaluate(async (worldName) => {
        const r = await fetch('/setup', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ action: 'launchWorld', world: worldName }),
          credentials: 'include'
        });
        return { status: r.status, body: await r.text().catch(() => '') };
      }, WORLD_NAME);

      if (launchResp.status !== 200) {
        throw new Error(`Failed to launch world: ${launchResp.status} ${launchResp.body}`);
      }

      // Poll /api/status until the world is active
      console.log('  waiting for world to become active...');
      for (let i = 0; i < 30; i++) {
        await page.waitForTimeout(2000);
        const status = await page.evaluate(async () => {
          const r = await fetch('/api/status', { credentials: 'include' });
          return r.json().catch(() => ({}));
        });
        if (status.active) {
          console.log(`  world active after ${(i + 1) * 2}s`);
          break;
        }
        if (i === 29) throw new Error('World never became active after 60s');
      }

      await page.goto(`${BASE_URL}/join`, { waitUntil: 'networkidle', timeout: 20000 });
      console.log('  navigated to /join');
    }
  }
}

async function joinAsGM(page) {
  console.log('  selecting Gamemaster user...');

  // Select by the dropdown
  const userSelect = page.locator('select[name="userid"]');
  await userSelect.waitFor({ state: 'visible', timeout: 10000 });

  // Find the Gamemaster option
  const options = await userSelect.locator('option').all();
  let gmValue = null;
  for (const opt of options) {
    const text = (await opt.textContent() || '').trim();
    if (/gamemaster/i.test(text)) {
      gmValue = await opt.getAttribute('value');
      break;
    }
  }

  if (gmValue) {
    await userSelect.selectOption(gmValue);
  } else {
    // Fall back to first non-empty option
    for (const opt of options) {
      const val = await opt.getAttribute('value');
      if (val) {
        await userSelect.selectOption(val);
        break;
      }
    }
  }

  await page.waitForTimeout(300);

  // Fill password
  const pwInput = page.locator('input[name="password"]').first();
  const pwVisible = await pwInput.isVisible().catch(() => false);
  if (pwVisible) {
    await pwInput.fill(GM_PASSWORD);
  }

  // Click the join button
  const joinBtn = page.locator('button[name="join"]').first();
  const joinVisible = await joinBtn.isVisible().catch(() => false);
  if (joinVisible) {
    await joinBtn.click();
  } else {
    await page.keyboard.press('Enter');
  }

  console.log('  join submitted, waiting for game to load...');
}

async function waitForGame(page) {
  await page.waitForSelector('#interface', { timeout: 30000 });
  console.log('  game interface loaded');
  await page.waitForTimeout(3000);
}

async function openActorSheets(page, screenshotFiles) {
  // Make sure the actors sidebar tab is active
  const actorsSidebarBtn = page.locator('button[data-tab="actors"][data-action="tab"], [data-tab="actors"]').first();
  const sidebarBtnVisible = await actorsSidebarBtn.isVisible().catch(() => false);
  if (sidebarBtnVisible) {
    await actorsSidebarBtn.click();
    await page.waitForTimeout(800);
  }

  // Find actor entries
  const actorEntries = page.locator('#actors .directory-list .document, #actors .directory-list .directory-item');
  const count = await actorEntries.count();
  console.log(`  found ${count} actor entries in sidebar`);

  if (count === 0) return;

  for (let i = 0; i < count; i++) {
    const entry = actorEntries.nth(i);

    // Get actor name from various possible attributes/elements
    let actorName = await entry.getAttribute('data-document-name').catch(() => null);
    if (!actorName) {
      actorName = await entry.locator('.document-name, .entity-name, h4').first().textContent().catch(() => null);
    }
    if (!actorName) actorName = `actor-${i}`;
    actorName = actorName.trim().replace(/\s+/g, '-').replace(/[^a-zA-Z0-9_-]/g, '').toLowerCase();

    await tryStep(`open actor ${actorName}`, async () => {
      await entry.dblclick();
      await page.waitForSelector('.app.sheet, .application', { timeout: 10000 });
      await page.waitForTimeout(1000);

      screenshotFiles.push(await screenshot(page, `02-actor-${actorName}.png`));

      const sheet = page.locator('.app.sheet, .application').last();
      const tabs = sheet.locator('.sheet-tabs .item, nav.tabs .item, .tabs a[data-tab]');
      const tabCount = await tabs.count();

      for (let t = 0; t < tabCount; t++) {
        await tryStep(`actor ${actorName} tab ${t}`, async () => {
          const tab = tabs.nth(t);
          const tabId = await tab.getAttribute('data-tab') || `${t}`;
          const tabVisible = await tab.isVisible().catch(() => false);
          if (!tabVisible) return;

          await tab.click();
          await page.waitForTimeout(800);

          screenshotFiles.push(await screenshot(page, `02-actor-${actorName}-tab-${tabId}.png`));
        });
      }

      const closeBtn = sheet.locator('.header-button.close, .window-header .close, a.close').first();
      const closeBtnVisible = await closeBtn.isVisible().catch(() => false);
      if (closeBtnVisible) {
        await closeBtn.click();
      } else {
        await page.keyboard.press('Escape');
      }
      await page.waitForTimeout(500);
    });
  }
}

async function openGearItem(page, screenshotFiles) {
  const actorEntries = page.locator('#actors .directory-list .document, #actors .directory-list .directory-item');
  const count = await actorEntries.count();

  for (let i = 0; i < count; i++) {
    const entry = actorEntries.nth(i);
    let found = false;

    await tryStep(`look for gear in actor ${i}`, async () => {
      await entry.dblclick();
      await page.waitForSelector('.app.sheet, .application', { timeout: 10000 });
      await page.waitForTimeout(800);

      const sheet = page.locator('.app.sheet, .application').last();

      // Try gear/inventory tab
      const gearTab = sheet.locator('[data-tab="gear"], [data-tab="inventory"], [data-tab="equipment"]').first();
      const gearTabVisible = await gearTab.isVisible().catch(() => false);
      if (gearTabVisible) {
        await gearTab.click();
        await page.waitForTimeout(500);
      }

      const gearItem = sheet.locator('.item[data-item-id], li.item, .directory-item').first();
      const gearVisible = await gearItem.isVisible().catch(() => false);

      if (gearVisible) {
        await gearItem.dblclick();
        await page.waitForSelector('.app.sheet, .application', { timeout: 8000 });
        await page.waitForTimeout(800);

        screenshotFiles.push(await screenshot(page, '03-item-gear.png'));
        found = true;

        const itemSheet = page.locator('.app.sheet, .application').last();
        const closeItem = itemSheet.locator('.header-button.close, .window-header .close, a.close').first();
        const closeItemVisible = await closeItem.isVisible().catch(() => false);
        if (closeItemVisible) {
          await closeItem.click();
        } else {
          await page.keyboard.press('Escape');
        }
        await page.waitForTimeout(300);
      }

      // Close actor sheet
      const closeBtn = sheet.locator('.header-button.close, .window-header .close, a.close').first();
      const closeBtnVisible = await closeBtn.isVisible().catch(() => false);
      if (closeBtnVisible) {
        await closeBtn.click();
      } else {
        await page.keyboard.press('Escape');
      }
      await page.waitForTimeout(400);
    });

    if (found) break;
  }
}

async function openActiveEffect(page, screenshotFiles) {
  const actorEntries = page.locator('#actors .directory-list .document, #actors .directory-list .directory-item');
  const count = await actorEntries.count();

  for (let i = 0; i < count; i++) {
    const entry = actorEntries.nth(i);
    let found = false;

    await tryStep(`look for active effects in actor ${i}`, async () => {
      await entry.dblclick();
      await page.waitForSelector('.app.sheet, .application', { timeout: 10000 });
      await page.waitForTimeout(800);

      const sheet = page.locator('.app.sheet, .application').last();

      const effectsTab = sheet.locator('[data-tab="effects"], .effects-tab').first();
      const effectsTabVisible = await effectsTab.isVisible().catch(() => false);
      if (effectsTabVisible) {
        await effectsTab.click();
        await page.waitForTimeout(500);
      }

      const effectEntry = sheet.locator('.effect[data-effect-id], .active-effect, li.effect').first();
      const effectVisible = await effectEntry.isVisible().catch(() => false);

      if (effectVisible) {
        await effectEntry.dblclick();
        await page.waitForSelector('.app.sheet, .application', { timeout: 8000 });
        await page.waitForTimeout(600);

        const effectSheet = page.locator('.app.sheet, .application').last();
        const changesTab = effectSheet.locator('[data-tab="changes"]').first();
        const changesVisible = await changesTab.isVisible().catch(() => false);
        if (changesVisible) {
          await changesTab.click();
          await page.waitForTimeout(400);
        }

        screenshotFiles.push(await screenshot(page, '04-active-effect-changes.png'));
        found = true;

        const closeEffect = effectSheet.locator('.header-button.close, .window-header .close, a.close').first();
        const closeEffectVisible = await closeEffect.isVisible().catch(() => false);
        if (closeEffectVisible) {
          await closeEffect.click();
        } else {
          await page.keyboard.press('Escape');
        }
        await page.waitForTimeout(300);
      }

      const closeBtn = sheet.locator('.header-button.close, .window-header .close, a.close').first();
      const closeBtnVisible = await closeBtn.isVisible().catch(() => false);
      if (closeBtnVisible) {
        await closeBtn.click();
      } else {
        await page.keyboard.press('Escape');
      }
      await page.waitForTimeout(400);
    });

    if (found) break;
  }
}

async function main() {
  await ensureDir(SCREENSHOT_DIR);

  console.log(`Starting test-v14.mjs: label="${label}", headless=${headless}`);

  const browser = await chromium.launch({ headless });
  const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });

  page.on('console', (msg) => {
    const type = msg.type();
    if (type === 'error' || type === 'warning' || type === 'warn') {
      const loc = msg.location();
      consoleMessages.push({
        type,
        text: msg.text(),
        url: loc.url || '',
        lineNumber: loc.lineNumber || 0
      });
    }
  });

  const screenshotFiles = [];

  try {
    console.log(`Navigating to ${BASE_URL}...`);
    await page.goto(BASE_URL, { waitUntil: 'networkidle', timeout: 30000 });
    console.log(`  landed on: ${page.url()}`);

    const joinResult = await ensureOnJoinPage(page);
    if (joinResult === 'incompatible') {
      console.log('  system incompatible — skipping game UI screenshots');
      return;
    }
    console.log(`  on join page: ${page.url()}`);

    console.log('Joining as GM...');
    await joinAsGM(page);

    console.log('Waiting for game interface...');
    await waitForGame(page);

    await tryStep('screenshot 01-canvas', async () => {
      screenshotFiles.push(await screenshot(page, '01-canvas.png'));
    });

    await tryStep('actor sheets', async () => {
      await openActorSheets(page, screenshotFiles);
    });

    await tryStep('gear item', async () => {
      await openGearItem(page, screenshotFiles);
    });

    await tryStep('active effect', async () => {
      await openActiveEffect(page, screenshotFiles);
    });

  } finally {
    const consoleJsonPath = path.join(SCREENSHOT_DIR, 'console.json');
    await writeFile(consoleJsonPath, JSON.stringify(consoleMessages, null, 2));
    console.log(`\nconsole.json written: ${consoleJsonPath}`);

    const errors = consoleMessages.filter((m) => m.type === 'error').length;
    const warnings = consoleMessages.filter((m) => m.type === 'warning' || m.type === 'warn').length;
    console.log('\n--- Summary ---');
    console.log(`  label:       ${label}`);
    console.log(`  screenshots: ${screenshotFiles.length}`);
    screenshotFiles.forEach((f) => console.log(`    ${f}`));
    console.log(`  errors:      ${errors}`);
    console.log(`  warnings:    ${warnings}`);

    await browser.close();
  }
}

main().catch((err) => {
  console.error('Fatal error:', err);
  process.exit(1);
});
