/**
 * US-27 — Redteam instantiates a template into an engagement.
 * Covers AC-27.1 → AC-27.7.
 * AC-27.3 (decoupling) tested via API: modifying instance ≠ modifying template and vice-versa.
 */
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 = 'us27-redteam';
const SOC_USER = 'us27-soc';
const PASS = 'us27-pass-strong';

interface Template { id: number; name: string; techniques: unknown[]; tactics: unknown[]; [k: string]: unknown; }
interface Simulation { id: number; name: string; status: string; techniques: unknown[]; tactic_ids?: unknown[]; [k: string]: unknown; }

async function createTemplate(token: string, payload: Record<string, unknown>): Promise<Template> {
  const r = await makeClient(token).post('/templates', payload);
  if (r.status !== 201) throw new Error(`create template: ${r.status} ${JSON.stringify(r.data)}`);
  return r.data as Template;
}

async function deleteTemplate(token: string, id: number): Promise<void> {
  await makeClient(token).delete(`/templates/${id}`);
}

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

test.describe('US-27 — instantiate from template', () => {
  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-27 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 */ }
  });

  // AC-27.1 — POST with template_id copies all RT fields
  test('AC-27.1 — POST with template_id copies name, description, commands, prerequisites, techniques, tactic_ids', async () => {
    const tmpl = await createTemplate(redteamToken, {
      name: 'US27 full template',
      description: 'template desc',
      commands: 'cmd1\ncmd2',
      prerequisites: 'prereq',
      tactic_ids: ['TA0007'],
    });

    const r = await makeClient(redteamToken).post(`/engagements/${engagementId}/simulations`, {
      template_id: tmpl.id,
    });
    expect(r.status).toBe(201);
    const sim = r.data as Simulation;
    expect(sim.name).toBe('US27 full template');
    expect(sim.description).toBe('template desc');
    expect(sim.commands).toBe('cmd1\ncmd2');
    expect(sim.prerequisites).toBe('prereq');
    expect(sim.status).toBe('pending');
    expect(sim.executed_at).toBeNull();
    expect(Array.isArray(sim.tactics)).toBe(true);
    expect((sim.tactics as { id: string }[])[0]?.id).toBe('TA0007');

    // SOC fields null
    expect(sim.soc_comment).toBeNull();

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

  // AC-27.2 — name override in body wins over template name
  test('AC-27.2 — POST with template_id + name override: body name wins', async () => {
    const tmpl = await createTemplate(redteamToken, { name: 'US27 override base' });
    const r = await makeClient(redteamToken).post(`/engagements/${engagementId}/simulations`, {
      template_id: tmpl.id,
      name: 'Custom override name',
    });
    expect(r.status).toBe(201);
    expect(r.data.name).toBe('Custom override name');

    await deleteSimulation(redteamToken, r.data.id as number);
    await deleteTemplate(redteamToken, tmpl.id);
  });

  // AC-27.2 — POST without template_id keeps original blank-create behavior
  test('AC-27.2 — POST without template_id creates blank simulation (name required)', async () => {
    const r = await makeClient(redteamToken).post(`/engagements/${engagementId}/simulations`, {
      name: 'Blank sim no template',
    });
    expect(r.status).toBe(201);
    expect(r.data.name).toBe('Blank sim no template');
    expect(r.data.techniques).toHaveLength(0);
    await deleteSimulation(redteamToken, r.data.id as number);
  });

  // AC-27.1 — template_id inexistant → 404
  test('AC-27.1 — POST with unknown template_id → 404', async () => {
    const r = await makeClient(redteamToken).post(`/engagements/${engagementId}/simulations`, {
      name: 'orphan',
      template_id: 999999,
    });
    expect(r.status).toBe(404);
    expect(r.data.error).toMatch(/template not found/i);
  });

  // AC-27.3 — decoupling: modifying instance does not touch template
  test('AC-27.3 — modifying instance does NOT affect template', async () => {
    const tmpl = await createTemplate(redteamToken, {
      name: 'US27 decouple template',
      description: 'original desc',
    });
    const r = await makeClient(redteamToken).post(`/engagements/${engagementId}/simulations`, {
      template_id: tmpl.id,
    });
    const sim = r.data as Simulation;

    // Modify the instance
    await makeClient(redteamToken).patch(`/simulations/${sim.id}`, { description: 'changed on instance' });

    // Template unchanged
    const tCheck = await makeClient(redteamToken).get(`/templates/${tmpl.id}`);
    expect(tCheck.data.description).toBe('original desc');

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

  // AC-27.3 — decoupling: modifying template does not touch existing instance
  test('AC-27.3 — modifying template does NOT affect already-created instance', async () => {
    const tmpl = await createTemplate(redteamToken, {
      name: 'US27 decouple template 2',
      commands: 'original cmd',
    });
    const r = await makeClient(redteamToken).post(`/engagements/${engagementId}/simulations`, {
      template_id: tmpl.id,
    });
    const sim = r.data as Simulation;

    // Modify the template
    await makeClient(redteamToken).patch(`/templates/${tmpl.id}`, { commands: 'modified cmd' });

    // Instance unchanged
    const sCheck = await makeClient(redteamToken).get(`/simulations/${sim.id}`);
    expect(sCheck.data.commands).toBe('original cmd');

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

  // AC-27.4 — auto-transition NOT triggered on creation from template
  test('AC-27.4 — creation from template with techniques does NOT auto-transition (stays pending)', async () => {
    const tmpl = await createTemplate(redteamToken, {
      name: 'US27 no auto-transition',
      tactic_ids: ['TA0007'],
    });
    const r = await makeClient(redteamToken).post(`/engagements/${engagementId}/simulations`, {
      template_id: tmpl.id,
    });
    expect(r.status).toBe(201);
    expect(r.data.status).toBe('pending');

    await deleteSimulation(redteamToken, r.data.id as number);
    await deleteTemplate(redteamToken, tmpl.id);
  });

  // AC-27.5 — engagement auto-status NOT triggered by instantiation
  test('AC-27.5 — engagement stays planned after instantiation from template', async () => {
    const eng = await createEngagement(redteamToken, {
      name: 'US27 auto-status check',
      start_date: '2026-01-01',
    });
    const tmpl = await createTemplate(redteamToken, {
      name: 'US27 auto-status template',
      tactic_ids: ['TA0001'],
    });
    const r = await makeClient(redteamToken).post(`/engagements/${eng.id}/simulations`, {
      template_id: tmpl.id,
    });
    expect(r.status).toBe(201);

    // Engagement status must still be planned
    const engCheck = await makeClient(redteamToken).get(`/engagements/${eng.id}`);
    expect(engCheck.data.status).toBe('planned');

    await deleteSimulation(redteamToken, r.data.id as number);
    await deleteTemplate(redteamToken, tmpl.id);
    const tok = await adminToken();
    await deleteEngagement(tok, eng.id);
  });

  // AC-27.6 — UI: dropdown + TemplatePickerModal
  test('AC-27.6 — EngagementDetailPage: dropdown toggle shows Blank + From template… options', async ({
    page,
    context,
  }) => {
    await seedTokenInStorage(context, redteamToken);
    await page.goto(`/engagements/${engagementId}`);

    // Main "New" button visible (direct Blank action)
    const newBtn = page.getByTestId('new-simulation-btn');
    await expect(newBtn).toBeVisible({ timeout: 5_000 });

    // Chevron toggle opens dropdown
    await page.getByTestId('new-simulation-dropdown-toggle').click();
    await expect(page.getByRole('menuitem', { name: /blank/i })).toBeVisible();
    await expect(page.getByRole('menuitem', { name: /from template/i })).toBeVisible();
  });

  test('AC-27.6 — clicking Blank navigates to /engagements/:eid/simulations/new', async ({
    page,
    context,
  }) => {
    await seedTokenInStorage(context, redteamToken);
    await page.goto(`/engagements/${engagementId}`);

    await page.getByTestId('new-simulation-btn').click();
    await expect(page).toHaveURL(/\/engagements\/\d+\/simulations\/new/);
  });

  test('AC-27.6 — "From template…" opens TemplatePickerModal', async ({
    page,
    context,
  }) => {
    await seedTokenInStorage(context, redteamToken);
    await page.goto(`/engagements/${engagementId}`);

    await page.getByTestId('new-simulation-dropdown-toggle').click();
    await page.getByTestId('from-template-btn').click();

    // Modal appears
    const dialog = page.getByRole('dialog');
    await expect(dialog).toBeVisible({ timeout: 5_000 });
    await expect(dialog).toContainText(/from template/i);
  });

  test('AC-27.6 — TemplatePickerModal empty state when no templates exist', async ({
    page,
    context,
  }) => {
    // Delete ALL templates (cross-suite leftovers included) to get a clean empty state
    const tok = await adminToken();
    const allTmpl = await makeClient(tok).get('/templates');
    for (const t of allTmpl.data as Template[]) {
      await deleteTemplate(tok, t.id);
    }

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

    await page.getByTestId('new-simulation-dropdown-toggle').click();
    await page.getByTestId('from-template-btn').click();

    const dialog = page.getByRole('dialog');
    await expect(dialog).toBeVisible({ timeout: 5_000 });
    await expect(dialog).toContainText(/no templates available/i);
  });

  test('AC-27.6 — selecting template from picker creates simulation and navigates to edit page', async ({
    page,
    context,
  }) => {
    const tmpl = await createTemplate(redteamToken, { name: 'US27 picker select template' });

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

    await page.getByTestId('new-simulation-dropdown-toggle').click();
    await page.getByTestId('from-template-btn').click();

    const dialog = page.getByRole('dialog');
    await expect(dialog).toBeVisible({ timeout: 5_000 });

    // Click the template row
    await dialog.getByTestId(`template-row-${tmpl.id}`).click();

    // Modal closes, redirect to /engagements/:eid/simulations/:sid/edit
    await expect(dialog).not.toBeVisible({ timeout: 5_000 });
    await expect(page).toHaveURL(
      new RegExp(`/engagements/${engagementId}/simulations/\\d+/edit`),
      { timeout: 8_000 },
    );

    // Simulation name matches template name (use heading to avoid strict mode with toast)
    await expect(page.getByRole('heading', { name: 'US27 picker select template' })).toBeVisible();

    // Clean up: extract sim id from URL
    const url = page.url();
    const m = url.match(/\/simulations\/(\d+)\/edit/);
    if (m) await deleteSimulation(redteamToken, parseInt(m[1]));
    await deleteTemplate(redteamToken, tmpl.id);
  });

  // AC-27.7 — SOC has no access to the new simulation button
  test('AC-27.7 — SOC does not see the New simulation dropdown on EngagementDetailPage', async ({
    page,
    context,
  }) => {
    // Advance a sim to review_required so SOC can access the engagement page
    const sim = await makeClient(redteamToken).post(`/engagements/${engagementId}/simulations`, {
      name: 'US27 soc visibility sim',
    });
    const simId = sim.data.id as number;
    await makeClient(redteamToken).patch(`/simulations/${simId}`, { name: 'US27 soc vis' });
    await makeClient(redteamToken).post(`/simulations/${simId}/transition`, { to: 'review_required' });

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

    // No new-simulation button for SOC
    await expect(page.getByTestId('new-simulation-btn')).not.toBeVisible();
    await expect(page.getByTestId('new-simulation-dropdown-toggle')).not.toBeVisible();

    await deleteSimulation(redteamToken, simId);
  });

  // NIT 1 — Dropdown closes on Escape key and on outside click
  test('NIT-1 — dropdown closes on Escape key press', async ({
    page,
    context,
  }) => {
    await seedTokenInStorage(context, redteamToken);
    await page.goto(`/engagements/${engagementId}`);

    await page.getByTestId('new-simulation-dropdown-toggle').click();
    // Menu is open
    await expect(page.getByTestId('from-template-btn')).toBeVisible({ timeout: 3_000 });

    // Press Escape
    await page.keyboard.press('Escape');
    await expect(page.getByTestId('from-template-btn')).not.toBeVisible({ timeout: 3_000 });
  });

  test('NIT-1 — dropdown closes when clicking outside', async ({
    page,
    context,
  }) => {
    await seedTokenInStorage(context, redteamToken);
    await page.goto(`/engagements/${engagementId}`);

    await page.getByTestId('new-simulation-dropdown-toggle').click();
    await expect(page.getByTestId('from-template-btn')).toBeVisible({ timeout: 3_000 });

    // Click somewhere outside the dropdown (page heading)
    await page.getByRole('heading').first().click({ force: true });
    await expect(page.getByTestId('from-template-btn')).not.toBeVisible({ timeout: 3_000 });
  });

  // NIT 2 — Empty-engagement SimulationList still shows dropdown
  test('NIT-2 — engagement with 0 simulations still shows New simulation dropdown', async ({
    page,
    context,
  }) => {
    // Create a fresh engagement with no simulations
    const eng = await createEngagement(redteamToken, {
      name: 'US27 empty eng dropdown',
      start_date: '2026-01-01',
    });

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

    // Primary button visible even in empty state
    await expect(page.getByTestId('new-simulation-btn')).toBeVisible({ timeout: 5_000 });

    // Chevron also visible and functional
    await page.getByTestId('new-simulation-dropdown-toggle').click();
    await expect(page.getByTestId('from-template-btn')).toBeVisible({ timeout: 3_000 });

    const tok = await adminToken();
    await deleteEngagement(tok, eng.id);
  });
});
