import { expect, test } from '@playwright/test';
import type { Locator, Page } from '@playwright/test';
import { expectNoA11yViolations } from './support/axe';
import { signIn } from './support/cms';
import { uploadMedia } from './support/media';

/**
 * História 29 — Destaques no painel (P5.S4.1): o operador cataloga uma imagem, cria a peça com
 * `alt`/destino/slot, desativa/ativa (o registro é preservado) e arquiva com confirmação nativa.
 * O caminho sem JS prova a validação server-side (imagem inexistente + destino `javascript:`).
 *
 * Pré-condições: `make up` + `php artisan db:seed` (o perfil demo recebe `destaques.*`) +
 * `php artisan migrate` (tabela `destaques.destaques`) + assets compilados + storage/fila no ar
 * (o upload do catálogo usa o service Media).
 *
 * Dados próprios: cada execução envia a **própria** imagem (alt com timestamp) e cria a própria
 * peça. O id da mídia é lido do formulário de arquivamento do card (`…/galeria/{id}/arquivar`) —
 * o catálogo não mostra o id em outro lugar. A peça fica arquivada (arquivar é terminal nesta
 * fatia) e não colide com nenhuma outra spec.
 *
 * Ações são acionadas por **foco + Enter** (submit/implicit submission nativos): o mesmo caminho em
 * desktop e no viewport móvel, onde o formulário longo pode sobrepor o ponteiro (mesma decisão do
 * spec 27) — nunca `force` nem espera fixa (ADR-001).
 */

/** Aciona um controle por teclado e espera a navegação (sem depender do ponteiro). */
async function pressAndWait(page: Page, control: Locator, url: RegExp): Promise<void> {
  await control.focus();
  await Promise.all([page.waitForURL(url), control.press('Enter')]);
}

test.describe('CMS — destaques', () => {
  test('cria a peça com mídia do catálogo e administra ativar/desativar/arquivar', async ({
    page,
  }) => {
    test.setTimeout(90_000);

    const stamp = Date.now();
    const alt = `Imagem de destaque ${stamp}`;
    const title = `Destaque e2e ${stamp}`;

    await signIn(page);

    const mediaId = await uploadMedia(page, alt);

    await page.goto('/destaques');
    await expect(page.getByRole('heading', { level: 1, name: 'Destaques do site' })).toBeVisible();
    await expect(page.getByRole('link', { name: 'Novo destaque' })).toBeVisible();
    await expectNoA11yViolations(page);

    await page.goto('/destaques/criar');
    await expect(page.getByRole('heading', { level: 1, name: 'Novo destaque' })).toBeVisible();

    await page.getByRole('textbox', { name: 'Português (Brasil)', exact: true }).fill(title);
    await page.getByRole('textbox', { name: 'Imagem (id do catálogo de mídia)' }).fill(mediaId);
    await page.getByRole('textbox', { name: 'Texto alternativo' }).fill(alt);
    await page.getByRole('textbox', { name: 'Destino do link' }).fill('/noticias');

    // Envio por teclado (submissão implícita do formulário): mesmo caminho em desktop e no mobile.
    await page.getByRole('textbox', { name: 'Destino do link' }).press('Enter');

    await expect(page.getByText('Destaque criado.')).toBeVisible();
    await expect(
      page.getByRole('textbox', { name: 'Português (Brasil)', exact: true }),
    ).toHaveValue(title);
    await expect(page.getByRole('textbox', { name: 'Texto alternativo' })).toHaveValue(alt);
    await expectNoA11yViolations(page);

    // Desativar preserva o registro: a peça sai do site e continua na lista como inativa.
    await pressAndWait(page, page.getByRole('button', { name: 'Desativar' }), /\/destaques$/);
    await expect(page.getByText('Destaque desativado (registro preservado).')).toBeVisible();

    const row = page.locator('tbody tr', { hasText: title });
    await expect(row).toHaveCount(1);
    await expect(row).toContainText('Inativo');
    await expectNoA11yViolations(page);

    // Reativar devolve o estado (mesmo registro).
    await pressAndWait(page, row.getByRole('button', { name: 'Ativar' }), /\/destaques$/);
    await expect(page.getByText('Destaque ativado.')).toBeVisible();

    const activeRow = page.locator('tbody tr', { hasText: title });
    await expect(activeRow).toContainText('Vigente');

    // Arquivar é terminal: confirmação nativa (Popover), sem `<form>` aninhado.
    const editHref = await activeRow.getByRole('link').first().getAttribute('href');
    expect(editHref).toBeTruthy();
    await page.goto(editHref as string);
    await expect(page.getByRole('heading', { level: 1, name: 'Editar destaque' })).toBeVisible();

    await pressAndWait(
      page,
      page.getByRole('button', { name: 'Arquivar destaque' }),
      /\/destaques\/[0-9a-f-]{36}\/editar$/,
    );
    await pressAndWait(
      page,
      page.locator('#destaque-archive-form button[type="submit"]'),
      /\/destaques$/,
    );
    await expect(page.getByText('Destaque arquivado.')).toBeVisible();

    // A lista padrão não mostra arquivados; o filtro dedicado mostra o registro preservado.
    // A busca por título isola o registro desta execução: arquivar é **terminal** e cada rodada
    // deixa uma peça arquivada, então a lista cresce e a mais nova cairia além da 1ª página
    // (ordem por slot/posição/id — determinismo, ADR-001). Sem a busca o spec quebrava na 16ª.
    await expect(page.locator('tbody tr', { hasText: title })).toHaveCount(0);
    await page.goto(`/destaques?state=arquivados&q=${encodeURIComponent(title)}`);
    await expect(page.locator('tbody tr', { hasText: title })).toHaveCount(1);
    await expectNoA11yViolations(page);
  });

  test.describe('sem JavaScript', () => {
    test.use({ javaScriptEnabled: false });

    test('a validação server-side recusa imagem inexistente e destino inseguro', async ({
      page,
    }) => {
      const stamp = Date.now();

      await signIn(page);

      await page.goto('/destaques/criar');
      await page
        .getByRole('textbox', { name: 'Português (Brasil)', exact: true })
        .fill(`Inválido ${stamp}`);
      await page
        .getByRole('textbox', { name: 'Imagem (id do catálogo de mídia)' })
        .fill('0199b6a2-0000-7000-8000-00000000dead');
      await page.getByRole('textbox', { name: 'Texto alternativo' }).fill('Imagem inexistente');
      await page.getByRole('textbox', { name: 'Destino do link' }).fill('javascript:alert(1)');
      await page.getByRole('textbox', { name: 'Destino do link' }).press('Enter');

      const summary = page.getByRole('alert').first();
      await expect(summary).toContainText('Não foi possível salvar');
      await expect(
        page.getByText('Não existe imagem ativa com este id no catálogo de mídia.').first(),
      ).toBeVisible();
      await expect(
        page.getByText(/O destino deve ser um caminho do próprio site/).first(),
      ).toBeVisible();

      const imageField = page.getByRole('textbox', { name: 'Imagem (id do catálogo de mídia)' });
      await expect(imageField).toHaveAttribute('aria-invalid', 'true');
      await expect(imageField).toHaveValue('0199b6a2-0000-7000-8000-00000000dead');
    });
  });
});
