/**
 * US-14 — redteam views and removes techniques as tags.
 * Covers AC-14.1 → AC-14.5 (UI tags + auto-save).
 */
import { test, expect } from '@playwright/test';
import {
  adminToken,
  createEngagement,
  deleteEngagement,
  deleteUserByUsername,
  ensureUser,
  login,
  makeClient,
} from '../fixtures/api';
import { seedTokenInStorage } from '../fixtures/auth';

const REDTEAM_USER = 'us14-redteam';
const SOC_USER = 'us14-soc';
const PASS = 'us14-pass-strong';

interface Simulation {
  id: number;
  status: string;
  techniques: { id: string; name: string; tactics: string[] }[];
}

async function createSimulation(
  token: string,
  engagementId: number,
  name = 'US-14 sim',
): Promise<Simulation> {
  const client = makeClient(token);
  const r = await client.post(`/engagements/${engagementId}/simulations`, { name });
  if (r.status !== 201) throw new Error(`create sim: ${r.status}`);
  return r.data as Simulation;
}

async function patchTechniques(
  token: string,
  simId: number,
  techniqueIds: string[],
): Promise<void> {
  const client = makeClient(token);
  const r = await client.patch(`/simulations/${simId}`, { technique_ids: techniqueIds });
  if (r.status !== 200) throw new Error(`patch techniques: ${r.status}`);
}

async function deleteSimulation(token: string, simId: number): Promise<void> {
  await makeClient(token).delete(`/simulations/${simId}`);
}

test.describe('US-14 — technique tags UI', () => {
  let redteamToken: string;
  let socToken: string;
  let engagementId: number;

  test.beforeAll(async () => {
    await ensureUser(REDTEAM_USER, PASS, 'redteam');
    await ensureUser(SOC_USER, PASS, 'soc');
    redteamToken = (await login(REDTEAM_USER, PASS)).token;
    socToken = (await login(SOC_USER, PASS)).token;
    const eng = await createEngagement(redteamToken, {
      name: 'US-14 Engagement',
      start_date: '2026-01-01',
    });
    engagementId = eng.id;
  });

  test.afterAll(async () => {
    try {
      const tok = await adminToken();
      await deleteEngagement(tok, engagementId);
      for (const u of [REDTEAM_USER, SOC_USER]) {
        await deleteUserByUsername(tok, u);
      }
    } catch {
      /* noop */
    }
  });

  test('AC-14.1 — MitreTechniquesField shows empty state, matrix icon + inline search input', async ({
    page,
    context,
  }) => {
    const sim = await createSimulation(redteamToken, engagementId, 'AC-14.1 empty');

    await seedTokenInStorage(context, redteamToken);
    await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);

    // Empty state message visible when no techniques
    await expect(page.getByText(/no techniques selected/i)).toBeVisible();

    // Matrix icon button present (sprint 4: replaces "Add technique" text button)
    await expect(page.getByLabel(/open mitre matrix/i)).toBeVisible();

    // Inline search placeholder present (sprint 4: replaces "Quick search" text button)
    await expect(page.getByText(/search technique/i)).toBeVisible();

    await deleteSimulation(redteamToken, sim.id);
  });

  test('AC-14.1 — tags show id + name + × button', async ({
    page,
    context,
  }) => {
    const sim = await createSimulation(redteamToken, engagementId, 'AC-14.1 tags');
    await patchTechniques(redteamToken, sim.id, ['T1059', 'T1078']);

    await seedTokenInStorage(context, redteamToken);
    await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);

    const tagList = page.getByTestId('techniques-tag-list');
    await expect(tagList).toBeVisible();

    // Both techniques appear as tags
    const tags = tagList.getByTestId('mitre-technique-tag');
    await expect(tags).toHaveCount(2);

    // First tag contains T1059
    await expect(tagList).toContainText('T1059');
    await expect(tagList).toContainText('T1078');

    // × buttons present for redteam
    await expect(page.getByRole('button', { name: /remove T1059/i })).toBeVisible();
    await expect(page.getByRole('button', { name: /remove T1078/i })).toBeVisible();

    await deleteSimulation(redteamToken, sim.id);
  });

  test('AC-14.2 — removing a tag triggers auto-save PATCH (toast + tag disappears)', async ({
    page,
    context,
  }) => {
    const sim = await createSimulation(redteamToken, engagementId, 'AC-14.2 remove tag');
    await patchTechniques(redteamToken, sim.id, ['T1059', 'T1078']);

    await seedTokenInStorage(context, redteamToken);
    await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);

    // Wait for tags to render
    await expect(page.getByTestId('techniques-tag-list')).toBeVisible();
    await expect(page.getByRole('button', { name: /remove T1059/i })).toBeVisible();

    // Click × on T1059
    await page.getByRole('button', { name: /remove T1059/i }).click();

    // Toast "Techniques updated" should appear
    await expect(page.getByText(/techniques updated/i)).toBeVisible({ timeout: 5_000 });

    // T1059 tag is gone, T1078 remains
    await expect(page.getByTestId('techniques-tag-list')).not.toContainText('T1059');
    await expect(page.getByTestId('techniques-tag-list')).toContainText('T1078');

    await deleteSimulation(redteamToken, sim.id);
  });

  test('AC-14.2 — inline search: selecting technique appends as tag + auto-save', async ({
    page,
    context,
  }) => {
    const sim = await createSimulation(redteamToken, engagementId, 'AC-14.2 quick search');

    await seedTokenInStorage(context, redteamToken);
    await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);

    // Sprint 4: click inline placeholder text to reveal the combobox
    await page.getByText(/search technique/i).click();

    const picker = page.getByRole('combobox', { name: /mitre technique/i });
    await picker.fill('T1059');

    const listbox = page.getByRole('listbox', { name: /mitre techniques/i });
    await expect(listbox).toBeVisible({ timeout: 5_000 });

    await picker.press('ArrowDown');
    await picker.press('Enter');

    // Tag appears and auto-save toast
    await expect(page.getByText(/techniques updated/i)).toBeVisible({ timeout: 5_000 });
    await expect(page.getByTestId('techniques-tag-list')).toContainText('T1059');

    await deleteSimulation(redteamToken, sim.id);
  });

  test('AC-14.3 — SimulationList MITRE column shows first id + +N counter', async ({
    page,
    context,
  }) => {
    const simEmpty = await createSimulation(redteamToken, engagementId, 'AC-14.3 empty');
    const simOne = await createSimulation(redteamToken, engagementId, 'AC-14.3 one');
    await patchTechniques(redteamToken, simOne.id, ['T1059']);
    const simThree = await createSimulation(redteamToken, engagementId, 'AC-14.3 three');
    await patchTechniques(redteamToken, simThree.id, ['T1059', 'T1078', 'T1566']);

    await seedTokenInStorage(context, redteamToken);
    await page.goto(`/engagements/${engagementId}`);

    // Empty → shows "—"
    const rowEmpty = page.getByRole('row', { name: /AC-14.3 empty/i });
    await expect(rowEmpty).toContainText('—');

    // One technique → shows id only
    const rowOne = page.getByRole('row', { name: /AC-14.3 one/i });
    await expect(rowOne).toContainText('T1059');

    // Three techniques → shows "T1059 +2"
    const rowThree = page.getByRole('row', { name: /AC-14.3 three/i });
    await expect(rowThree).toContainText('T1059');
    await expect(rowThree).toContainText('+2');

    await deleteSimulation(redteamToken, simEmpty.id);
    await deleteSimulation(redteamToken, simOne.id);
    await deleteSimulation(redteamToken, simThree.id);
  });

  test('AC-14.4 — order of tags preserved between read and write', async ({
    page,
    context,
  }) => {
    const sim = await createSimulation(redteamToken, engagementId, 'AC-14.4 order');
    // Patch with specific order T1566 → T1059 → T1078
    await patchTechniques(redteamToken, sim.id, ['T1566', 'T1059', 'T1078']);

    // Verify via API
    const r = await makeClient(redteamToken).get(`/simulations/${sim.id}`);
    const ids = (r.data.techniques as { id: string }[]).map((t) => t.id);
    expect(ids).toEqual(['T1566', 'T1059', 'T1078']);

    // Verify via UI — tags appear in insertion order
    await seedTokenInStorage(context, redteamToken);
    await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);
    const tags = page.getByTestId('mitre-technique-tag');
    await expect(tags).toHaveCount(3);
    await expect(tags.nth(0)).toContainText('T1566');
    await expect(tags.nth(1)).toContainText('T1059');
    await expect(tags.nth(2)).toContainText('T1078');

    await deleteSimulation(redteamToken, sim.id);
  });

  test('AC-14.5 — tags styled with DESIGN.md tokens (bg-primary-soft, rounded-full)', async ({
    page,
    context,
  }) => {
    const sim = await createSimulation(redteamToken, engagementId, 'AC-14.5 style');
    await patchTechniques(redteamToken, sim.id, ['T1059']);

    await seedTokenInStorage(context, redteamToken);
    await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);

    const tag = page.getByTestId('mitre-technique-tag').first();
    await expect(tag).toBeVisible();

    // Verify styling classes are present on the tag element
    const cls = await tag.getAttribute('class');
    expect(cls).toMatch(/bg-primary-soft/);
    expect(cls).toMatch(/rounded-full/);

    await deleteSimulation(redteamToken, sim.id);
  });
});
