import { test, expect } from '@playwright/test';
import { Client } from 'pg';
import { hashSource } from '@platform-modules/i18n-translator';

const ADMIN_EMAIL = 'admin@e2e.test';
const ADMIN_PASSWORD = 'e2e-admin-pw-9f3a2b1c';
const ORIGIN = 'http://localhost:4331';
const DATABASE_URL = process.env.DATABASE_URL;

async function withDb<T>(run: (client: Client) => Promise<T>): Promise<T> {
  if (!DATABASE_URL) {
    throw new Error('DATABASE_URL must be set for mod-cms i18n e2e');
  }
  const client = new Client({ connectionString: DATABASE_URL });
  await client.connect();
  try {
    return await run(client);
  } finally {
    await client.end();
  }
}

async function signIn(page: import('@playwright/test').Page) {
  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');
}

async function createViaApi(
  page: import('@playwright/test').Page,
  input: { slug: string; title: string; body: string; type?: string; visibility?: string },
): Promise<{ id: string; slug: string }> {
  const res = await page.request.post('/api/admin/content', {
    headers: { origin: ORIGIN, 'content-type': 'application/json' },
    data: { type: 'post', ...input },
  });
  expect(res.ok(), `create failed: ${res.status()} ${await res.text()}`).toBeTruthy();
  return (await res.json()) as { id: string; slug: string };
}

async function publishViaApi(page: import('@playwright/test').Page, id: string): Promise<void> {
  const res = await page.request.post('/api/admin/publish', {
    headers: { origin: ORIGIN, 'content-type': 'application/json' },
    data: { id },
  });
  expect(res.ok(), `publish failed: ${res.status()} ${await res.text()}`).toBeTruthy();
}

async function translateViaApi(page: import('@playwright/test').Page, id: string): Promise<void> {
  const res = await page.request.post('/api/admin/translate', {
    headers: { origin: ORIGIN, 'content-type': 'application/json' },
    data: { id },
  });
  expect(res.ok(), `translate failed: ${res.status()} ${await res.text()}`).toBeTruthy();
}

async function ensureFrenchLocale(): Promise<void> {
  await withDb(async (client) => {
    await client.query(`
      insert into languages (code, name_native, name_english, direction, search_config, is_active, is_default, sort_order)
      values ('fr', 'Français', 'French', 'ltr', 'french', true, false, 2)
      on conflict (code) do update
      set name_native = excluded.name_native,
          name_english = excluded.name_english,
          direction = excluded.direction,
          search_config = excluded.search_config,
          is_active = excluded.is_active,
          sort_order = excluded.sort_order
    `);
  });
}

async function readTranslationRows(entityId: string): Promise<
  Array<{ field_key: string; locale: string; status: string; value: string }>
> {
  return withDb(async (client) => {
    const { rows } = await client.query(
      `
        select field_key, locale, status, value
        from translation_value
        where entity_type = 'content' and entity_id = $1
        order by field_key, locale
      `,
      [entityId],
    );
    return rows as Array<{ field_key: string; locale: string; status: string; value: string }>;
  });
}

async function seedFrenchTranslations(input: {
  entityId: string;
  title: string;
  body: string;
  status?: 'OK' | 'STALE';
}): Promise<void> {
  const titleHash = await hashSource(input.title);
  const bodyHash = await hashSource(input.body);
  await withDb(async (client) => {
    await client.query(
      `
        insert into translation_value
          (entity_type, entity_id, field_key, locale, value, source_hash, model_id, manual_override, status, translated_at, updated_at)
        values
          ('content', $1, 'title', 'fr', $2, $3, null, false, $4, now(), now()),
          ('content', $1, 'body', 'fr', $5, $6, null, false, $4, now(), now())
        on conflict (entity_type, entity_id, field_key, locale) do update
        set value = excluded.value,
            source_hash = excluded.source_hash,
            model_id = excluded.model_id,
            manual_override = excluded.manual_override,
            status = excluded.status,
            translated_at = excluded.translated_at,
            updated_at = excluded.updated_at
      `,
      [
        input.entityId,
        `[fr] ${input.title}`,
        titleHash,
        input.status ?? 'OK',
        `[fr] ${input.body}`,
        bodyHash,
      ],
    );
  });
}

test('published post is visible in default locale (authz pass)', async ({ page }) => {
  const stamp = Date.now();
  const slug = `e2e-i18n-pub-${stamp}`;
  const title = `Published post ${stamp}`;
  const bodyText = `Default locale body ${stamp}`;

  await signIn(page);
  const { id } = await createViaApi(page, {
    slug,
    title,
    body: `<p>${bodyText}</p>`,
    visibility: 'public',
  });
  await publishViaApi(page, id);

  const res = await page.request.get(`/post/${slug}`);
  expect(res.status(), await res.text()).toBe(200);
  const html = await res.text();
  expect(html).toContain(title);
  expect(html).toContain(bodyText);
});

test('unpublished post is 404 in default locale regardless of translation (authz gates)', async ({
  page,
}) => {
  const stamp = Date.now();
  const slug = `e2e-i18n-draft-${stamp}`;
  const title = `Draft post ${stamp}`;

  await signIn(page);
  const { id } = await createViaApi(page, {
    slug,
    title,
    body: `<p>Draft body ${stamp}</p>`,
    visibility: 'public',
  });
  await ensureFrenchLocale();
  await seedFrenchTranslations({
    entityId: id,
    title,
    body: `Draft body ${stamp}`,
  });

  const res = await page.request.get(`/post/${slug}`);
  expect(res.status(), await res.text()).toBe(404);
  const frRes = await page.request.get(`/post/${slug}`, {
    headers: { 'accept-language': 'fr' },
  });
  expect(frRes.status(), await frRes.text()).toBe(404);
});

test('editor translate action populates store rows via the content translate route', async ({
  page,
}) => {
  const stamp = Date.now();
  const slug = `e2e-i18n-editor-${stamp}`;
  const title = `Editor translated ${stamp}`;
  const bodyText = `Editor translate me ${stamp}`;

  await ensureFrenchLocale();
  await signIn(page);
  const { id } = await createViaApi(page, {
    slug,
    title,
    body: `<p>${bodyText}</p>`,
    visibility: 'public',
  });
  await publishViaApi(page, id);

  await page.goto(`/admin/content/${id}`);
  await page.getByRole('button', { name: 'Translate content' }).click();
  await expect(page.getByText('Translations queued for active locales')).toBeVisible();

  const rows = await readTranslationRows(id);
  expect(rows).toEqual([
    { field_key: 'body', locale: 'fr', status: 'OK', value: `[fr] ${bodyText}` },
    { field_key: 'title', locale: 'fr', status: 'OK', value: `[fr] ${title}` },
  ]);
});

test('admin translate action populates store rows and the list badge reflects the rollup', async ({
  page,
}) => {
  const stamp = Date.now();
  const slug = `e2e-i18n-admin-${stamp}`;
  const title = `Admin translated ${stamp}`;
  const bodyText = `Translate me ${stamp}`;

  await ensureFrenchLocale();
  await signIn(page);
  const { id } = await createViaApi(page, {
    slug,
    title,
    body: `<p>${bodyText}</p>`,
    visibility: 'public',
  });
  await publishViaApi(page, id);

  await page.goto('/admin');
  const row = page.locator('tr', { hasText: title });
  await row.getByRole('button', { name: 'Translate' }).click();
  await expect(row.getByText('fr complete')).toBeVisible();

  const rows = await readTranslationRows(id);
  expect(rows).toEqual([
    { field_key: 'body', locale: 'fr', status: 'OK', value: `[fr] ${bodyText}` },
    { field_key: 'title', locale: 'fr', status: 'OK', value: `[fr] ${title}` },
  ]);
});

test('published post with complete fr translation is 200 with translated content', async ({
  page,
}) => {
  const stamp = Date.now();
  const slug = `e2e-i18n-fr-${stamp}`;
  const title = `Localized post ${stamp}`;
  const bodyText = `Localized body ${stamp}`;

  await ensureFrenchLocale();
  await signIn(page);
  const { id } = await createViaApi(page, {
    slug,
    title,
    body: `<p>${bodyText}</p>`,
    visibility: 'public',
  });
  await publishViaApi(page, id);
  await seedFrenchTranslations({ entityId: id, title, body: bodyText });

  const res = await page.request.get(`/post/${slug}`, {
    headers: { 'accept-language': 'fr' },
  });
  expect(res.status(), await res.text()).toBe(200);
  const html = await res.text();
  expect(html).toContain(`[fr] ${title}`);
  expect(html).toContain(`[fr] ${bodyText}`);
});

test('published post with no fr translation is 404 in non-default locale (translation gates)', async ({
  page,
}) => {
  const stamp = Date.now();
  const slug = `e2e-i18n-missing-${stamp}`;
  const title = `Missing fr ${stamp}`;

  await ensureFrenchLocale();
  await signIn(page);
  const { id } = await createViaApi(page, {
    slug,
    title,
    body: `<p>Missing fr body ${stamp}</p>`,
    visibility: 'public',
  });
  await publishViaApi(page, id);

  const rows = await readTranslationRows(id);
  expect(rows).toEqual([]);

  const res = await page.request.get(`/post/${slug}`, {
    headers: { 'accept-language': 'fr' },
  });
  expect(res.status(), await res.text()).toBe(404);
});

test('markStale round-trips via hashSource when an edited source field changes', async ({ page }) => {
  const stamp = Date.now();
  const slug = `e2e-i18n-stale-${stamp}`;
  const originalTitle = `Original title ${stamp}`;
  const originalBody = `Original body ${stamp}`;
  const updatedTitle = `Updated title ${stamp}`;

  await ensureFrenchLocale();
  await signIn(page);
  const { id } = await createViaApi(page, {
    slug,
    title: originalTitle,
    body: `<p>${originalBody}</p>`,
    visibility: 'public',
  });
  await publishViaApi(page, id);
  await translateViaApi(page, id);

  await page.goto(`/admin/content/${id}`);
  await page.getByLabel('Title').fill(updatedTitle);
  await page.getByRole('button', { name: 'Save changes' }).click();
  await page.waitForURL('**/admin');

  const row = page.locator('tr', { hasText: updatedTitle });
  await expect(row.getByText('fr stale')).toBeVisible();
  const rows = await readTranslationRows(id);
  expect(rows.map(({ field_key, status }) => ({ field_key, status }))).toEqual([
    { field_key: 'body', status: 'OK' },
    { field_key: 'title', status: 'STALE' },
  ]);
});
