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

// Public page theme shell rendering (spec: install-wizard-e2e-design § theme-public.spec.ts).
// Uses the already-installed dev DB — read-only except TH3 settings round-trip (self-cleaning).

// Credentials: prefer the installed DB's real admin (from .dev.vars loaded in playwright.config.ts),
// falling back to the credentials install-appearance.spec.ts seeds on a fresh Neon branch.
const ADMIN_EMAIL = process.env.SEED_ADMIN_EMAIL ?? 'admin@e2e.test';
const ADMIN_PASSWORD = process.env.SEED_ADMIN_PASSWORD ?? '5dd3a429ccbb255dd4e3e8b5Aa1!';
const ORIGIN = 'http://localhost:4331';

type ShellClass = 'site-header' | 'editorial-shell' | 'magazine-shell' | 'blog-header';

const THEME_TO_SHELL: Record<string, ShellClass> = {
  default: 'site-header',
  editorial: 'editorial-shell',
  magazine: 'magazine-shell',
  blog: 'blog-header',
};

const SHELL_TO_THEME: Record<ShellClass, string> = {
  'site-header': 'default',
  'editorial-shell': 'editorial',
  'magazine-shell': 'magazine',
  'blog-header': 'blog',
};

const THEMED_SHELL_IDS = ['default', 'editorial', 'magazine', 'blog'] as const;

let installedThemeClass: ShellClass;

// ID of the post seeded in beforeAll (null = no seeding needed / sign-in unavailable).
let seededPostId: string | null = null;

// beforeAll: seed a published post if the site has no published content.
// Ensures TH2 has a /post/<slug> to navigate to without requiring pre-seeded DB state.
// afterAll: deletes the seeded post to leave the DB clean.
test.beforeAll(async ({ browser }) => {
  const ctx = await browser.newContext();
  const pg = await ctx.newPage();
  try {
    const existing = await findPublishedPostSlugFromHomepage(pg);
    if (existing) return; // existing content — no seeding needed
    try {
      await signIn(pg);
    } catch {
      return; // sign-in unavailable — TH2/TH3 will skip gracefully
    }
    const res = await pg.request.post('/api/admin/content', {
      headers: { origin: ORIGIN, 'content-type': 'application/json' },
      data: { slug: 'e2e-theme-seed-post', type: 'post', title: 'E2E Theme Seed Post' },
    });
    if (!res.ok()) return;
    const entry = (await res.json()) as { id: string };
    seededPostId = entry.id;
    await pg.request.post('/api/admin/publish', {
      headers: { origin: ORIGIN, 'content-type': 'application/json' },
      data: { id: seededPostId },
    });
  } finally {
    await ctx.close();
  }
});

test.afterAll(async ({ browser }) => {
  if (!seededPostId) return;
  const ctx = await browser.newContext();
  const pg = await ctx.newPage();
  try {
    await signIn(pg);
    await pg.request.delete('/api/admin/remove', {
      headers: { origin: ORIGIN, 'content-type': 'application/json' },
      data: { id: seededPostId },
    });
  } finally {
    seededPostId = null;
    await ctx.close();
  }
});

function attachConsoleCapture(page: Page): string[] {
  const consoleProblems: string[] = [];
  page.on('console', (msg) => {
    if (msg.type() === 'error' || msg.type() === 'warning')
      consoleProblems.push(`[${msg.type()}] ${msg.text()}`);
  });
  return consoleProblems;
}

async function detectInstalledShellClass(page: Page): Promise<ShellClass> {
  const hasDefault = (await page.locator('.site-header').count()) > 0;
  const hasEditorial = (await page.locator('.editorial-shell').count()) > 0;
  const hasMagazine = (await page.locator('.magazine-shell').count()) > 0;
  const hasBlog = (await page.locator('.blog-header').count()) > 0;
  expect(
    (hasDefault ? 1 : 0) + (hasEditorial ? 1 : 0) + (hasMagazine ? 1 : 0) + (hasBlog ? 1 : 0),
    'exactly one theme shell class must be present',
  ).toBe(1);
  if (hasDefault) return 'site-header';
  if (hasEditorial) return 'editorial-shell';
  if (hasMagazine) return 'magazine-shell';
  return 'blog-header';
}

async function signIn(page: Page, timeout = 8000): Promise<void> {
  await page.goto('/admin/login');
  await page.getByLabel('Email').fill(ADMIN_EMAIL);
  await page.getByLabel('Password').fill(ADMIN_PASSWORD);
  await page.getByRole('button', { name: 'Sign in' }).click();
  await page.waitForURL('**/admin', { timeout });
}

async function findPublishedPostSlugFromHomepage(page: Page): Promise<string | null> {
  const res = await page.request.get('/');
  if (!res.ok()) return null;
  const html = await res.text();
  const match = html.match(/href="\/post\/([^"?>#/]+)"/);
  return match?.[1] ?? null;
}

async function findPublishedPostSlug(page: Page): Promise<string | null> {
  // Read-only: scrape homepage for a /post/<slug> link. No auth fallback (TH2 is read-only).
  return findPublishedPostSlugFromHomepage(page);
}

type ActiveSelection = { themeId: string; paletteId: string; mode: string };

async function getThemeSelection(page: Page): Promise<ActiveSelection> {
  const res = await page.request.get('/api/admin/settings');
  expect(res.ok(), `settings GET failed: ${res.status()} ${await res.text()}`).toBeTruthy();
  const settings = (await res.json()) as { theme?: unknown };
  const raw = settings.theme;
  if (raw && typeof raw === 'object' && raw !== null) {
    const v = raw as { themeId?: unknown; paletteId?: unknown; mode?: unknown };
    return {
      themeId: typeof v.themeId === 'string' ? v.themeId : 'default',
      paletteId: typeof v.paletteId === 'string' ? v.paletteId : 'default',
      mode: typeof v.mode === 'string' ? v.mode : 'light',
    };
  }
  return { themeId: 'default', paletteId: 'default', mode: 'light' };
}

async function putThemeSetting(page: Page, selection: ActiveSelection): Promise<void> {
  const res = await page.request.put('/api/admin/settings', {
    headers: { origin: ORIGIN, 'content-type': 'application/json' },
    data: { key: 'theme', value: selection },
  });
  expect(res.ok(), `settings PUT failed: ${res.status()} ${await res.text()}`).toBeTruthy();
}

function pickAlternateThemeId(currentThemeId: string): (typeof THEMED_SHELL_IDS)[number] {
  const alternate = THEMED_SHELL_IDS.find((id) => id !== currentThemeId);
  return alternate ?? 'editorial';
}

test('TH1: homepage renders the installed shell', async ({ page }) => {
  const consoleProblems = attachConsoleCapture(page);

  await page.goto('/');
  installedThemeClass = await detectInstalledShellClass(page);
  await expect(page.locator(`.${installedThemeClass}`)).toBeVisible();

  expect(consoleProblems, 'no console errors or warnings').toHaveLength(0);
});

test('TH2: post page renders the same shell', async ({ page }) => {
  const consoleProblems = attachConsoleCapture(page);

  if (!installedThemeClass) {
    await page.goto('/');
    installedThemeClass = await detectInstalledShellClass(page);
  }

  const slug = await findPublishedPostSlug(page);
  if (!slug) {
    test.skip(true, 'no published post found — seed one or connect to an installed DB with content');
    return;
  }
  await page.goto(`/post/${slug}`);
  await expect(page.locator(`.${installedThemeClass}`)).toBeVisible();

  expect(consoleProblems, 'no console errors or warnings').toHaveLength(0);
});

test('TH3: theme change round-trip via admin settings', async ({ page }) => {
  // Settings mutation: PUT /api/admin/settings { key: 'theme', value: ActiveSelection }
  const consoleProblems = attachConsoleCapture(page);

  if (!installedThemeClass) {
    await page.goto('/');
    installedThemeClass = await detectInstalledShellClass(page);
  }

  try {
    await signIn(page);
  } catch {
    test.skip(true, 'admin sign-in failed — DB credentials do not match SEED_ADMIN_EMAIL/PASSWORD in .dev.vars');
    return;
  }
  const original = await getThemeSelection(page);
  const originalShell = installedThemeClass;
  const alternateThemeId = pickAlternateThemeId(SHELL_TO_THEME[originalShell]);
  const alternateShell = THEME_TO_SHELL[alternateThemeId];

  await putThemeSetting(page, {
    themeId: alternateThemeId,
    paletteId: original.paletteId,
    mode: original.mode,
  });

  await page.goto('/');
  await expect(page.locator(`.${alternateShell}`)).toBeVisible();

  await putThemeSetting(page, original);

  await page.goto('/');
  await expect(page.locator(`.${originalShell}`)).toBeVisible();

  expect(consoleProblems, 'no console errors or warnings').toHaveLength(0);
});
