/**
 * US-10 — MITRE ATT&CK autocomplete.
 * Covers AC-10.1 → AC-10.5.
 *
 * AC-10.1 (make update-mitre CLI target) is not exercised from Playwright;
 * the bundle is assumed present in the container image.
 */
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 = 'us10-redteam';
const PASS = 'us10-pass-strong';

interface Simulation {
  id: number;
  [key: string]: unknown;
}

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

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

test.describe('US-10 — MITRE autocomplete', () => {
  let redteamToken: string;
  let engagementId: number;

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

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

  test('AC-10.1 — bundle present in container (skipped: CLI-only, bundle assumed committed)', async () => {
    // AC-10.1 is a Makefile target test. We verify the bundle is loaded
    // indirectly by checking the API returns results (not 503).
    const client = makeClient(redteamToken);
    const r = await client.get('/mitre/techniques?q=T1059');
    // If bundle not loaded, we'd get 503 — this confirms it loaded OK
    expect(r.status).not.toBe(503);
  });

  test('AC-10.2 — GET /api/mitre/techniques?q= returns max 20 results with id/name/tactics', async () => {
    const client = makeClient(redteamToken);

    // Search by id prefix
    const rId = await client.get('/mitre/techniques?q=T1059');
    expect(rId.status).toBe(200);
    expect(Array.isArray(rId.data)).toBe(true);
    expect(rId.data.length).toBeGreaterThan(0);
    expect(rId.data.length).toBeLessThanOrEqual(20);

    const first = rId.data[0];
    expect(first).toHaveProperty('id');
    expect(first).toHaveProperty('name');
    expect(first).toHaveProperty('tactics');
    expect(Array.isArray(first.tactics)).toBe(true);

    // Exact id match comes first
    const exactMatch = rId.data.find((t: { id: string }) => t.id === 'T1059');
    expect(exactMatch).toBeTruthy();
    expect(rId.data[0].id).toBe('T1059');

    // Search by name (case-insensitive)
    const rName = await client.get(
      '/mitre/techniques?q=command%20and%20scripting%20interpreter',
    );
    expect(rName.status).toBe(200);
    expect(rName.data.length).toBeGreaterThan(0);
    const nameMatch = rName.data.find((t: { name: string }) =>
      t.name.toLowerCase().includes('command and scripting'),
    );
    expect(nameMatch).toBeTruthy();
  });

  test('AC-10.3 — 503 if bundle not loaded (verified by absence: bundle IS loaded)', async () => {
    const client = makeClient(redteamToken);
    // We can only test the happy path from e2e; 503 requires a container
    // without the bundle. We verify the endpoint does NOT return 503,
    // confirming the bundle is loaded.
    const r = await client.get('/mitre/techniques?q=T1');
    expect(r.status).toBe(200);
  });

  test('AC-10.4 — sub-techniques (T1059.001) included in search results', async () => {
    const client = makeClient(redteamToken);
    const r = await client.get('/mitre/techniques?q=T1059.001');
    expect(r.status).toBe(200);
    expect(r.data.length).toBeGreaterThan(0);
    const subtech = r.data.find((t: { id: string }) => t.id === 'T1059.001');
    expect(subtech).toBeTruthy();
    expect(subtech.name).toBeTruthy();
  });

  test('AC-10.5 — MitreTechniquePicker: input, dropdown, keyboard nav, selection fills both fields', async ({
    page,
    context,
  }) => {
    const sim = await createSimulation(redteamToken, engagementId, 'AC-10.5 sim');

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

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

    // Type a query — after debounce (200ms) the dropdown opens with results
    await picker.fill('T1059');
    // Wait for dropdown to appear (debounce + network)
    const listbox = page.getByRole('listbox', { name: /mitre techniques/i });
    await expect(listbox).toBeVisible({ timeout: 5_000 });

    // Options visible in expected format: "T1059 — Command and Scripting Interpreter (...)"
    const options = listbox.getByRole('option');
    await expect(options.first()).toBeVisible();
    const firstText = await options.first().textContent();
    expect(firstText).toMatch(/T1059/);
    expect(firstText).toMatch(/—/);

    // Keyboard navigation: ArrowDown selects item, Enter confirms
    await picker.press('ArrowDown');
    await picker.press('Enter');

    // After selection the dropdown closes and input shows the selected value
    await expect(listbox).not.toBeVisible();
    const inputValue = await picker.inputValue();
    expect(inputValue).toMatch(/T1059/);
    expect(inputValue).toMatch(/—/);

    // Escape closes the dropdown
    await picker.fill('T1');
    await expect(listbox).toBeVisible({ timeout: 5_000 });
    await picker.press('Escape');
    await expect(listbox).not.toBeVisible();

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