/**
 * US-13 — redteam selects multiple MITRE techniques per simulation.
 * Covers AC-13.1 → AC-13.5 (API / data contract focus).
 */
import { test, expect } from '@playwright/test';
import {
  adminToken,
  createEngagement,
  deleteEngagement,
  deleteUserByUsername,
  ensureUser,
  login,
  makeClient,
} from '../fixtures/api';

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

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

async function createSimulation(
  token: string,
  engagementId: number,
  name = 'US-13 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} ${JSON.stringify(r.data)}`);
  return r.data as Simulation;
}

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

test.describe('US-13 — multi-technique simulations', () => {
  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-13 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-13.1 — simulation serialisation has techniques array, not scalar MITRE fields', async () => {
    const sim = await createSimulation(redteamToken, engagementId, 'AC-13.1 sim');

    expect(Array.isArray(sim.techniques)).toBe(true);
    expect(sim.techniques).toHaveLength(0);
    expect(sim).not.toHaveProperty('mitre_technique_id');
    expect(sim).not.toHaveProperty('mitre_technique_name');

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

  test('AC-13.2 — migration: new simulations start with techniques = []', async () => {
    // Migration is tested implicitly: every new simulation created via POST must
    // return techniques: [] (no scalar columns present).
    const sim = await createSimulation(redteamToken, engagementId, 'AC-13.2 migration sim');
    expect(sim.techniques).toEqual([]);
    await deleteSimulation(redteamToken, sim.id);
  });

  test('AC-13.3 — serialisation enriches each technique entry with tactics from bundle', async () => {
    const sim = await createSimulation(redteamToken, engagementId, 'AC-13.3 sim');
    const client = makeClient(redteamToken);

    const r = await client.patch(`/simulations/${sim.id}`, {
      technique_ids: ['T1059', 'T1078'],
    });
    expect(r.status).toBe(200);

    const techniques: { id: string; name: string; tactics: string[] }[] = r.data.techniques;
    expect(techniques).toHaveLength(2);

    // Each entry has id, name, and tactics (derived from bundle at serialize time)
    for (const t of techniques) {
      expect(t).toHaveProperty('id');
      expect(t).toHaveProperty('name');
      expect(Array.isArray(t.tactics)).toBe(true);
      expect(t.tactics.length).toBeGreaterThan(0);
    }

    const t1059 = techniques.find((t) => t.id === 'T1059');
    expect(t1059).toBeTruthy();
    expect(t1059!.name).toBe('Command and Scripting Interpreter');
    expect(t1059!.tactics).toContain('execution');

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

  test('AC-13.4 — PATCH technique_ids: valid IDs stored, unknown ID → 400', async () => {
    const sim = await createSimulation(redteamToken, engagementId, 'AC-13.4 sim');
    const client = makeClient(redteamToken);

    // Valid IDs
    const rOk = await client.patch(`/simulations/${sim.id}`, {
      technique_ids: ['T1059', 'T1078', 'T1566'],
    });
    expect(rOk.status).toBe(200);
    const ids = (rOk.data.techniques as { id: string }[]).map((t) => t.id);
    expect(ids).toContain('T1059');
    expect(ids).toContain('T1078');
    expect(ids).toContain('T1566');

    // Unknown ID → 400
    const rBad = await client.patch(`/simulations/${sim.id}`, {
      technique_ids: ['T9999'],
    });
    expect(rBad.status).toBe(400);
    expect(rBad.data.error).toMatch(/unknown technique id.*T9999/i);

    // Dedup: sending T1059 twice keeps only one entry in order
    const rDedup = await client.patch(`/simulations/${sim.id}`, {
      technique_ids: ['T1059', 'T1078', 'T1059'],
    });
    expect(rDedup.status).toBe(200);
    const dedupIds = (rDedup.data.techniques as { id: string }[]).map((t) => t.id);
    expect(dedupIds).toEqual(['T1059', 'T1078']);

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

  test('AC-13.4 — SOC PATCH technique_ids → 403', async () => {
    const sim = await createSimulation(redteamToken, engagementId, 'AC-13.4 soc block');
    // Advance to review_required so SOC can attempt a patch
    const rtClient = makeClient(redteamToken);
    await rtClient.patch(`/simulations/${sim.id}`, { name: 'trigger' });
    await rtClient.post(`/simulations/${sim.id}/transition`, { to: 'review_required' });

    const socClient = makeClient(socToken);
    const r = await socClient.patch(`/simulations/${sim.id}`, {
      technique_ids: ['T1059'],
    });
    expect(r.status).toBe(403);
    expect(r.data.error).toMatch(/soc cannot edit redteam fields/i);

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

  test('AC-13.5 — auto-transition pending→in_progress triggered by non-empty technique_ids', async () => {
    const sim = await createSimulation(redteamToken, engagementId, 'AC-13.5 auto-transition');
    expect(sim.status).toBe('pending');
    const client = makeClient(redteamToken);

    // Non-empty technique_ids → triggers auto-transition
    const r = await client.patch(`/simulations/${sim.id}`, {
      technique_ids: ['T1059'],
    });
    expect(r.status).toBe(200);
    expect(r.data.status).toBe('in_progress');

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

  test('AC-13.5 — empty technique_ids does NOT trigger auto-transition', async () => {
    const sim = await createSimulation(redteamToken, engagementId, 'AC-13.5 no-trigger');
    expect(sim.status).toBe('pending');
    const client = makeClient(redteamToken);

    const r = await client.patch(`/simulations/${sim.id}`, {
      technique_ids: [],
    });
    expect(r.status).toBe(200);
    expect(r.data.status).toBe('pending');

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