import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

vi.mock('@/lib/api', () => ({ apiFetch: vi.fn() }));
vi.mock('@/components/ui/toast-store', () => ({ toast: { error: vi.fn(), success: vi.fn() } }));

import { apiFetch } from '@/lib/api';
import { toast } from '@/components/ui/toast-store';
import { VulnerabilityScanSheet } from '../VulnerabilityScanSheet';

const mockedFetch = vi.mocked(apiFetch);
const ok = (body: unknown) => ({ ok: true, json: async () => body }) as Response;

const VULN = {
  id: 1, scan_id: 1, vulnerability_id: 'CVE-2026-1000', pkg_name: 'openssl', installed_version: '1.0',
  fixed_version: null, severity: 'HIGH', title: null, description: null, primary_url: null,
};

const SCAN = {
  id: 1, node_id: 1, image_ref: 'img:1', image_digest: null, scanned_at: Date.now(),
  total_vulnerabilities: 1, critical_count: 0, high_count: 1, medium_count: 0, low_count: 0,
  unknown_count: 0, fixable_count: 0, secret_count: 0, misconfig_count: 0, scanners_used: 'vuln',
  highest_severity: 'HIGH', os_info: null, trivy_version: '0.50.0', scan_duration_ms: 1, triggered_by: 'manual',
  status: 'completed', error: null, stack_context: null, policy_evaluation: null,
};

function postSuppressionCall() {
  return mockedFetch.mock.calls.find(
    ([url, opts]) => /\/security\/suppressions$/.test(String(url)) && (opts as { method?: string } | undefined)?.method === 'POST',
  );
}

beforeEach(() => {
  mockedFetch.mockReset();
  (toast.error as ReturnType<typeof vi.fn>).mockClear();
  mockedFetch.mockImplementation(((url: string, opts?: { method?: string }) => {
    if (opts?.method === 'POST' && /\/security\/suppressions$/.test(url)) {
      return Promise.resolve(ok({}));
    }
    if (/\/fleet\/role/.test(url)) return Promise.resolve(ok({ role: 'control' }));
    if (/\/vulnerabilities\?limit=1000&offset=0/.test(url)) return Promise.resolve(ok({ items: [VULN], total: 1 }));
    if (/\/secrets/.test(url) || /\/misconfigs/.test(url)) return Promise.resolve(ok({ items: [] }));
    if (/\/security\/scans\/1$/.test(url)) return Promise.resolve(ok(SCAN));
    return Promise.resolve(ok({ items: [] }));
  }) as unknown as typeof apiFetch);
});

async function openSuppressDialog() {
  render(<VulnerabilityScanSheet scanId={1} onClose={() => {}} canManageSuppressions />);
  await waitFor(() => expect(screen.getByText('CVE-2026-1000')).toBeInTheDocument());
  await userEvent.click(screen.getByTitle('Triage finding'));
}

async function pickSelect(label: string, optionName: string) {
  await userEvent.click(screen.getByRole('combobox', { name: label }));
  await userEvent.click(await screen.findByRole('option', { name: optionName }));
}

describe('VulnerabilityScanSheet suppress dialog', () => {
  it('titles the dialog Triage finding and prefills the exact image pattern', async () => {
    await openSuppressDialog();
    expect(screen.getByRole('heading', { name: 'Triage finding' })).toBeInTheDocument();
    expect(screen.getByLabelText('Image pattern (optional)')).toHaveValue('img:1');
  });

  it('requires an OpenVEX justification for a not-affected decision and clears it when switching away', async () => {
    await openSuppressDialog();

    await pickSelect('Triage decision', 'Not affected');
    expect(screen.getByRole('combobox', { name: 'OpenVEX justification' })).toBeInTheDocument();

    await userEvent.type(screen.getByLabelText('Reason'), 'Vendor confirmed unreachable code path.');
    await userEvent.click(screen.getByRole('button', { name: 'Save' }));
    expect(toast.error).toHaveBeenCalledWith('An OpenVEX justification is required for this triage decision.');
    expect(postSuppressionCall()).toBeUndefined();

    await pickSelect('Triage decision', 'Accepted risk');
    expect(screen.queryByRole('combobox', { name: 'OpenVEX justification' })).not.toBeInTheDocument();
  });

  it('sends the triage status and justification when suppressing a CVE', async () => {
    await openSuppressDialog();

    await userEvent.type(screen.getByLabelText('Reason'), 'False positive confirmed by vendor.');
    await pickSelect('Triage decision', 'False positive');
    await pickSelect('OpenVEX justification', 'Inline mitigations already exist');
    await userEvent.click(screen.getByRole('button', { name: 'Save' }));

    await waitFor(() => expect(postSuppressionCall()).toBeTruthy());
    const body = JSON.parse((postSuppressionCall()![1] as { body: string }).body);
    expect(body.status).toBe('false_positive');
    expect(body.justification).toBe('inline_mitigations_already_exist');
    expect(body.image_pattern).toBe('img:1');
  });
});
