/**
 * Ohm Streaming - Automated E2E Test Suite
 * Run: node tests/auto/run_tests.mjs
 * Output: tests/auto/results/report.md + screenshots/
 */
import { chromium } from 'playwright';
import fs from 'fs';
import path from 'path';

const BASE = 'http://127.0.0.1:3000';
const RESULTS_DIR = path.join(import.meta.dirname, 'results');
const SCREENSHOT_DIR = path.join(RESULTS_DIR, 'screenshots');

const CREDS = { username: 'roman', password: 'roman123' };

// ── Helpers ──
const results = { passed: 0, failed: 0, errors: [], duration: 0 };
const startTime = Date.now();

function screenshot(page, name) {
  const p = path.join(SCREENSHOT_DIR, `${name}.png`);
  return page.screenshot({ path: p, fullPage: true }).then(() => p);
}

async function test(name, fn) {
  try {
    await fn();
    results.passed++;
    console.log(`  ✅ ${name}`);
  } catch (err) {
    results.failed++;
    const msg = `❌ ${name}: ${err.message}`;
    results.errors.push(msg);
    console.error(`  ❌ ${name}: ${err.message}`);
  }
}

function assert(condition, message) {
  if (!condition) throw new Error(message || 'Assertion failed');
}

// ── Main ──
(async () => {
  // Ensure output dirs
  fs.mkdirSync(SCREENSHOT_DIR, { recursive: true });

  const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
  const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });

  // Collect console errors
  const consoleErrors = [];
  page.on('console', msg => {
    if (msg.type() === 'error') consoleErrors.push(msg.text());
  });

  // Network error tracking
  const networkErrors = [];
  page.on('requestfailed', req => {
    networkErrors.push(`${req.method()} ${req.url()}: ${req.failure()?.errorText}`);
  });

  console.log('\n🧪 Ohm Streaming - Automated Test Suite\n');
  console.log('═══ Phase 1: API Health ═══');

  // ── Phase 1: API Health Checks ──
  await page.goto(`${BASE}/health`, { waitUntil: 'domcontentloaded', timeout: 10000 });
  await page.waitForTimeout(1000);

  await test('GET /health returns 200', async () => {
    const text = await page.textContent('body');
    const json = JSON.parse(text);
    assert(json.status === 'healthy' || json.status === 'ok', `Unexpected status: ${json.status}`);
  });

  await test('GET / returns landing page', async () => {
    const resp = await page.goto(`${BASE}/`, { waitUntil: 'domcontentloaded', timeout: 10000 });
    assert(resp.status() === 200, `Status ${resp.status()}`);
    await page.waitForTimeout(1500);
    const screenshotPath = await screenshot(page, '01_landing_page');
    console.log(`    📸 ${screenshotPath}`);
  });

  await test('GET /login returns login page', async () => {
    const resp = await page.goto(`${BASE}/login`, { waitUntil: 'domcontentloaded', timeout: 10000 });
    assert(resp.status() === 200);
    await page.waitForTimeout(1500);
    const screenshotPath = await screenshot(page, '02_login_page');
    console.log(`    📸 ${screenshotPath}`);
  });

  // ── Phase 2: Authentication ──
  console.log('\n═══ Phase 2: Authentication ═══');

  await test('Login with valid credentials (roman/roman123)', async () => {
    await page.goto(`${BASE}/login`, { waitUntil: 'domcontentloaded', timeout: 10000 });
    await page.waitForTimeout(2000);

    // Use API to login (SPA approach)
    await page.goto(`${BASE}/login`, { waitUntil: 'domcontentloaded', timeout: 10000 });
    await page.waitForTimeout(2000);

    const token = await page.evaluate(async (creds) => {
      const res = await fetch('/api/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(creds)
      });
      if (!res.ok) throw new Error(`Login failed: ${res.status} ${await res.text()}`);
      return (await res.json()).access_token;
    }, CREDS);

    assert(token && token.length > 10, 'No valid token received');

    // Inject token into localStorage
    await page.evaluate((t) => {
      localStorage.setItem('auth_token', t);
    }, token);
    console.log(`    🔑 Token received (${token.substring(0, 20)}...)`);
  });

  await test('GET /api/auth/me returns user info', async () => {
    const user = await page.evaluate(async () => {
      const token = localStorage.getItem('auth_token');
      const res = await fetch('/api/auth/me', {
        headers: { 'Authorization': `Bearer ${token}` }
      });
      return res.json();
    });
    // Response may be { username, ... } or { user: { username, ... } }
    const name = user.username || user.user?.username || user.id;
    assert(name, `No username found in /me response: ${JSON.stringify(user).substring(0, 200)}`);
    console.log(`    👤 User: ${name} (admin: ${user.is_admin || user.user?.is_admin || false})`);
  });

  // ── Phase 3: SPA Navigation ──
  console.log('\n═══ Phase 3: SPA Navigation (/web) ═══');

  const tabs = ['home', 'anime', 'series', 'providers', 'downloads', 'watchlist', 'settings'];

  for (const tab of tabs) {
    await test(`Navigate to tab: ${tab}`, async () => {
      await page.goto(`${BASE}/web`, { waitUntil: 'domcontentloaded', timeout: 10000 });
      await page.waitForTimeout(2000);

      // Inject auth
      await page.evaluate(() => {
        // Token should already be in localStorage from login test
        // but let's verify
        const token = localStorage.getItem('auth_token');
        if (!token) throw new Error('No auth token in localStorage');
      });

      // Switch tab using the app's own mechanism
      await page.evaluate((tabName) => {
        window.location.hash = tabName;
        window.dispatchEvent(new CustomEvent('set-tab', { detail: { tab: tabName } }));
      }, tab);
      await page.waitForTimeout(3000);

      // Check no JS errors during navigation
      const currentErrors = consoleErrors.length;
      // Just verify page didn't crash
      const content = await page.textContent('body');
      assert(content && content.length > 10, `Tab ${tab} rendered empty content`);

      const screenshotPath = await screenshot(page, `03_tab_${tab}`);
      console.log(`    📸 ${screenshotPath}`);
    });
  }

  // ── Phase 4: API Endpoints ──
  console.log('\n═══ Phase 4: API Endpoints ═══');

  const apiTests = [
    { name: 'GET /api/settings', endpoint: '/api/settings', method: 'GET' },
    { name: 'GET /api/favorites', endpoint: '/api/favorites', method: 'GET' },
    { name: 'GET /api/watchlist', endpoint: '/api/watchlist', method: 'GET' },
    { name: 'GET /api/downloads', endpoint: '/api/downloads', method: 'GET' },
    { name: 'GET /api/watchlist/settings', endpoint: '/api/watchlist/settings', method: 'GET' },
    { name: 'GET /api/watchlist/stats/summary', endpoint: '/api/watchlist/stats/summary', method: 'GET' },
    { name: 'GET /api/providers/health', endpoint: '/api/providers/health', method: 'GET' },
    { name: 'GET /api/recommendations', endpoint: '/api/recommendations', method: 'GET' },
    { name: 'GET /api/releases/latest', endpoint: '/api/releases/latest', method: 'GET' },
    { name: 'GET /api/favorites/stats', endpoint: '/api/favorites/stats', method: 'GET' },
  ];

  for (const apiTest of apiTests) {
    await test(`${apiTest.name} returns 200`, async () => {
      const result = await page.evaluate(async ({ endpoint, method }) => {
        const token = localStorage.getItem('auth_token');
        const res = await fetch(endpoint, {
          method,
          headers: { 'Authorization': `Bearer ${token}` }
        });
        let body = null;
        try { body = await res.json(); } catch(e) { /* body stays null */ }
        return { status: res.status, body };
      }, apiTest);

      assert(result.status === 200, `${apiTest.name} returned ${result.status}: ${JSON.stringify(result.body).substring(0, 200)}`);
      // Verify it's valid JSON
      assert(typeof result.body === 'object', `${apiTest.name} returned non-JSON`);
    });
  }

  // ── Phase 5: Content Validation ──
  console.log('\n═══ Phase 5: Content Validation ═══');

  await test('Home tab renders content (not blank)', async () => {
    await page.goto(`${BASE}/web#home`, { waitUntil: 'domcontentloaded', timeout: 10000 });
    await page.waitForTimeout(3000);
    const content = await page.textContent('body');
    assert(content.length > 100, 'Home tab content too short - may be blank');
    console.log(`    📝 Content length: ${content.length} chars`);
  });

  await test('Alpine.js loaded correctly', async () => {
    const alpineLoaded = await page.evaluate(() => typeof window.Alpine !== 'undefined');
    assert(alpineLoaded, 'Alpine.js not loaded - x-* directives are dead');
    console.log(`    ⚡ Alpine.js: loaded`);
  });

  await test('HTMX loaded correctly', async () => {
    const htmxLoaded = await page.evaluate(() => typeof window.htmx !== 'undefined');
    assert(htmxLoaded, 'HTMX not loaded');
    console.log(`    ⚡ HTMX: loaded`);
  });

  await test('No critical JS errors in console', async () => {
    // Filter out non-critical errors (network, extensions)
    const critical = consoleErrors.filter(e =>
      !e.includes('favicon') &&
      !e.includes('net::ERR_CONNECTION') &&
      !e.includes('404') &&
      !e.includes('DevTools')
    );
    assert(critical.length === 0, `${critical.length} critical JS errors: ${critical.slice(0, 3).join('; ')}`);
    console.log(`    ✨ Console clean (${consoleErrors.length} total, 0 critical)`);
  });

  // ── Phase 6: Search Functionality ──
  console.log('\n═══ Phase 6: Search Functionality ═══');

  await test('Anime search API works', async () => {
    const result = await page.evaluate(async () => {
      const token = localStorage.getItem('auth_token');
      const res = await fetch('/api/anime/search?q=naruto&limit=3', {
        headers: { 'Authorization': `Bearer ${token}` }
      });
      return { status: res.status, body: await res.json() };
    });
    // Search may return empty if providers are down, but should not error
    assert(result.status === 200, `Search returned ${result.status}`);
    console.log(`    🔍 Search results: ${JSON.stringify(result.body).substring(0, 100)}`);
  });

  // ── Phase 7: Responsive Design ──
  console.log('\n═══ Phase 7: Responsive Design ═══');

  await test('Mobile viewport rendering', async () => {
    const context = await browser.newContext({
      viewport: { width: 390, height: 844 },
      isMobile: true,
      hasTouch: true,
      userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15'
    });
    const mobilePage = await context.newPage();

    // Re-auth on mobile
    await mobilePage.goto(`${BASE}/login`, { waitUntil: 'domcontentloaded', timeout: 10000 });
    await mobilePage.waitForTimeout(2000);

    const token = await mobilePage.evaluate(async (creds) => {
      const res = await fetch('/api/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(creds)
      });
      return (await res.json()).access_token;
    }, CREDS);

    await mobilePage.evaluate((t) => localStorage.setItem('auth_token', t), token);

    await mobilePage.goto(`${BASE}/web#home`, { waitUntil: 'domcontentloaded', timeout: 10000 });
    await mobilePage.waitForTimeout(3000);

    const screenshotPath = await mobilePage.screenshot({ path: path.join(SCREENSHOT_DIR, '07_mobile_home.png'), fullPage: true });
    console.log(`    📸 ${screenshotPath}`);

    // Check for horizontal overflow
    const overflow = await mobilePage.evaluate(() => {
      const w = window.innerWidth;
      return Array.from(document.querySelectorAll('*'))
        .filter(el => el.getBoundingClientRect().width > w)
        .length;
    });
    assert(overflow === 0, `${overflow} elements overflow horizontally on mobile`);

    await context.close();
    console.log(`    📱 Mobile: no horizontal overflow`);
  });

  // ── Phase 8: Settings API ──
  console.log('\n═══ Phase 8: Settings & Providers ═══');

  await test('GET /api/settings returns valid config', async () => {
    const settings = await page.evaluate(async () => {
      const token = localStorage.getItem('auth_token');
      const res = await fetch('/api/settings', {
        headers: { 'Authorization': `Bearer ${token}` }
      });
      return res.json();
    });
    assert(settings && typeof settings === 'object', 'Settings not an object');
    console.log(`    ⚙️ Settings keys: ${Object.keys(settings).join(', ')}`);
  });

  await test('GET /api/providers/health check', async () => {
    const health = await page.evaluate(async () => {
      const token = localStorage.getItem('auth_token');
      const res = await fetch('/api/providers/health', {
        headers: { 'Authorization': `Bearer ${token}` }
      });
      return res.json();
    });
    assert(health !== null, 'Provider health returned null');
    const providerCount = Array.isArray(health) ? health.length : Object.keys(health).length;
    console.log(`    🏥 Providers checked: ${providerCount}`);
  });

  await browser.close();

  // ── Generate Report ──
  results.duration = ((Date.now() - startTime) / 1000).toFixed(1);
  consoleErrors.length = 0;

  const report = `# Ohm Streaming - Automated Test Report
**Date:** ${new Date().toISOString()}
**Duration:** ${results.duration}s
**Base URL:** ${BASE}

## Summary

| Metric | Value |
|--------|-------|
| ✅ Passed | ${results.passed} |
| ❌ Failed | ${results.failed} |
| 📊 Total | ${results.passed + results.failed} |
| 📊 Pass Rate | ${((results.passed / (results.passed + results.failed)) * 100).toFixed(1)}% |

${results.errors.length > 0 ? `## Failed Tests\n\n${results.errors.map((e, i) => `${i + 1}. ${e}`).join('\n')}` : '## All tests passed!'}

## Screenshots

${fs.readdirSync(SCREENSHOT_DIR).map(f => `- ![](screenshots/${f})`).join('\n')}
`;

  fs.writeFileSync(path.join(RESULTS_DIR, 'report.md'), report);

  console.log('\n═══════════════════════════════════');
  console.log(`  Results: ${results.passed}/${results.passed + results.failed} passed (${results.duration}s)`);
  console.log(`  Report: ${path.join(RESULTS_DIR, 'report.md')}`);
  console.log(`  Screenshots: ${SCREENSHOT_DIR}`);
  if (results.errors.length > 0) {
    console.log(`\n  Failed tests:`);
    results.errors.forEach(e => console.log(`    ${e}`));
  }
  console.log('═══════════════════════════════════\n');

  process.exit(results.failed > 0 ? 1 : 0);
})();
