import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import MockAdapter from 'axios-mock-adapter';
import { apiClient } from '@/api/client';
import { MitreMatrixModal } from '@/components/MitreMatrixModal';
import { renderWithProviders } from './utils';
import type { MitreTactic, MitreTechnique } from '@/api/types';

const MATRIX: MitreTactic[] = [
  {
    tactic_id: 'TA0001',
    tactic_name: 'Initial Access',
    techniques: [
      {
        id: 'T1078',
        name: 'Valid Accounts',
        subtechniques: [
          { id: 'T1078.001', name: 'Default Accounts' },
          { id: 'T1078.002', name: 'Domain Accounts' },
        ],
      },
      {
        id: 'T1190',
        name: 'Exploit Public-Facing Application',
        subtechniques: [],
      },
    ],
  },
  {
    tactic_id: 'TA0002',
    tactic_name: 'Execution',
    techniques: [
      {
        id: 'T1059',
        name: 'Command and Scripting Interpreter',
        subtechniques: [{ id: 'T1059.001', name: 'PowerShell' }],
      },
    ],
  },
];

const SELECTION: MitreTechnique[] = [
  { id: 'T1078', name: 'Valid Accounts', tactics: ['initial-access'] },
];

describe('MitreMatrixModal', () => {
  let mock: MockAdapter;

  beforeEach(() => {
    mock = new MockAdapter(apiClient);
    mock.onGet('/mitre/matrix').reply(200, MATRIX);
  });

  afterEach(() => {
    mock.restore();
  });

  it('returns null when isOpen=false', () => {
    const { container } = renderWithProviders(
      <MitreMatrixModal isOpen={false} initialSelection={[]} onApply={vi.fn()} onCancel={vi.fn()} />,
    );
    expect(container.firstChild).toBeNull();
  });

  it('renders dialog with tactic columns when open', async () => {
    renderWithProviders(
      <MitreMatrixModal isOpen initialSelection={[]} onApply={vi.fn()} onCancel={vi.fn()} />,
    );
    await waitFor(() => {
      expect(screen.getByText('Initial Access')).toBeInTheDocument();
      expect(screen.getByText('Execution')).toBeInTheDocument();
    });
  });

  it('renders techniques for each tactic', async () => {
    renderWithProviders(
      <MitreMatrixModal isOpen initialSelection={[]} onApply={vi.fn()} onCancel={vi.fn()} />,
    );
    await waitFor(() => {
      expect(screen.getByText('T1078')).toBeInTheDocument();
      expect(screen.getByText('T1059')).toBeInTheDocument();
    });
  });

  it('Apply button calls onApply with selected techniques', async () => {
    const onApply = vi.fn();
    const user = userEvent.setup();

    renderWithProviders(
      <MitreMatrixModal isOpen initialSelection={[]} onApply={onApply} onCancel={vi.fn()} />,
    );

    await waitFor(() => screen.getByText('T1078'));

    // Click the label button for T1078 to select it
    const t1078Btn = screen.getAllByRole('button').find(
      (btn) => btn.textContent?.includes('T1078') && !btn.getAttribute('aria-label'),
    );
    await user.click(t1078Btn!);

    await user.click(screen.getByRole('button', { name: /Apply/i }));

    expect(onApply).toHaveBeenCalledWith(
      expect.arrayContaining([expect.objectContaining({ id: 'T1078' })]),
    );
  });

  it('Cancel button calls onCancel without onApply', async () => {
    const onApply = vi.fn();
    const onCancel = vi.fn();
    const user = userEvent.setup();

    renderWithProviders(
      <MitreMatrixModal isOpen initialSelection={[]} onApply={onApply} onCancel={onCancel} />,
    );

    await user.click(screen.getByRole('button', { name: /Cancel/i }));

    expect(onCancel).toHaveBeenCalled();
    expect(onApply).not.toHaveBeenCalled();
  });

  it('Escape key calls onCancel', async () => {
    const onCancel = vi.fn();
    const user = userEvent.setup();

    renderWithProviders(
      <MitreMatrixModal isOpen initialSelection={[]} onApply={vi.fn()} onCancel={onCancel} />,
    );

    await user.keyboard('{Escape}');

    expect(onCancel).toHaveBeenCalled();
  });

  it('shows initial selection as selected', async () => {
    renderWithProviders(
      <MitreMatrixModal isOpen initialSelection={SELECTION} onApply={vi.fn()} onCancel={vi.fn()} />,
    );

    await waitFor(() => screen.getByText('T1078'));

    // T1078 should show selected count in tactic header
    expect(screen.getByText('1 selected')).toBeInTheDocument();
  });

  it('search filter narrows visible techniques', async () => {
    const user = userEvent.setup();
    renderWithProviders(
      <MitreMatrixModal isOpen initialSelection={[]} onApply={vi.fn()} onCancel={vi.fn()} />,
    );

    await waitFor(() => screen.getByText('T1078'));

    const searchInput = screen.getByPlaceholderText(/Filter techniques/i);
    await user.type(searchInput, 'T1059');

    // T1059 column should be visible, T1078 should not
    expect(screen.queryByText('T1078')).toBeNull();
    expect(screen.getByText('T1059')).toBeInTheDocument();
  });

  it('chevron expands subtechniques', async () => {
    const user = userEvent.setup();
    renderWithProviders(
      <MitreMatrixModal isOpen initialSelection={[]} onApply={vi.fn()} onCancel={vi.fn()} />,
    );

    await waitFor(() => screen.getByText('T1078'));

    // Subtechniques should not be visible initially
    expect(screen.queryByText(/Default Accounts/)).toBeNull();

    // Click the expand chevron for T1078
    const expandBtn = screen.getByRole('button', { name: /Expand T1078/i });
    await user.click(expandBtn);

    expect(screen.getByText(/Default Accounts/)).toBeInTheDocument();
  });

  it('Apply button shows technique count', async () => {
    renderWithProviders(
      <MitreMatrixModal isOpen initialSelection={SELECTION} onApply={vi.fn()} onCancel={vi.fn()} />,
    );

    await waitFor(() => {
      expect(screen.getByRole('button', { name: /Apply 1 technique/i })).toBeInTheDocument();
    });
  });

  it('Apply button is disabled when no techniques selected and no initial selection', async () => {
    renderWithProviders(
      <MitreMatrixModal isOpen initialSelection={[]} onApply={vi.fn()} onCancel={vi.fn()} />,
    );

    await waitFor(() => screen.getByText('T1078'));

    // Label is "Clear all" when totalSelected === 0, but it's disabled when initialSelection is also empty
    const applyBtn = screen.getByRole('button', { name: /Clear all/i });
    expect(applyBtn).toBeDisabled();
  });

  it('Apply button shows "Clear all" and stays enabled when initial selection is deselected', async () => {
    const onApply = vi.fn();
    const user = userEvent.setup();

    renderWithProviders(
      <MitreMatrixModal isOpen initialSelection={SELECTION} onApply={onApply} onCancel={vi.fn()} />,
    );

    await waitFor(() => screen.getByText('T1078'));

    // Deselect T1078 (it was pre-selected)
    const t1078Btn = screen.getAllByRole('button').find(
      (btn) => btn.textContent?.includes('T1078') && !btn.getAttribute('aria-label'),
    );
    await user.click(t1078Btn!);

    // Button should show "Clear all" and be enabled (user explicitly clearing the list)
    const applyBtn = screen.getByRole('button', { name: /Clear all/i });
    expect(applyBtn).not.toBeDisabled();
    await user.click(applyBtn);
    expect(onApply).toHaveBeenCalledWith([]);
  });

  it('backdrop click calls onCancel', async () => {
    const onCancel = vi.fn();
    const user = userEvent.setup();

    renderWithProviders(
      <MitreMatrixModal isOpen initialSelection={[]} onApply={vi.fn()} onCancel={onCancel} />,
    );

    // Click the backdrop (the fixed inset div behind the modal)
    const backdrop = document.querySelector('.bg-ink\\/60') as HTMLElement;
    if (backdrop) await user.click(backdrop);

    expect(onCancel).toHaveBeenCalled();
  });
});
