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

const TECHNIQUES: MitreTechnique[] = [
  { id: 'T1059', name: 'Command and Scripting Interpreter', tactics: ['execution'] },
  { id: 'T1059.001', name: 'PowerShell', tactics: ['execution'] },
  { id: 'T1021', name: 'Remote Services', tactics: ['lateral-movement'] },
];

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

  beforeEach(() => {
    mock = new MockAdapter(apiClient);
    vi.useFakeTimers({ shouldAdvanceTime: true });
  });

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

  it('renders input with placeholder', () => {
    vi.useRealTimers();
    renderWithProviders(
      <MitreTechniquePicker
        techniqueId={null}
        techniqueName={null}
        onChange={vi.fn()}
      />,
    );
    expect(screen.getByRole('combobox')).toBeInTheDocument();
    expect(screen.getByPlaceholderText(/Search by ID or name/i)).toBeInTheDocument();
  });

  it('shows preselected value when techniqueId and name provided', () => {
    vi.useRealTimers();
    renderWithProviders(
      <MitreTechniquePicker
        techniqueId="T1059"
        techniqueName="Command and Scripting Interpreter"
        onChange={vi.fn()}
      />,
    );
    const input = screen.getByRole('combobox') as HTMLInputElement;
    expect(input.value).toContain('T1059');
    expect(input.value).toContain('Command and Scripting Interpreter');
  });

  it('is disabled when disabled prop is true', () => {
    vi.useRealTimers();
    renderWithProviders(
      <MitreTechniquePicker
        techniqueId={null}
        techniqueName={null}
        onChange={vi.fn()}
        disabled
      />,
    );
    expect(screen.getByRole('combobox')).toBeDisabled();
  });

  it('debounces search: no request fires before 200ms', async () => {
    mock.onGet('/mitre/techniques').reply(200, TECHNIQUES);
    const user = userEvent.setup({ advanceTimers: (ms) => vi.advanceTimersByTime(ms) });

    renderWithProviders(
      <MitreTechniquePicker
        techniqueId={null}
        techniqueName={null}
        onChange={vi.fn()}
      />,
    );

    const input = screen.getByRole('combobox');
    await user.click(input);
    await user.type(input, 'T');

    // Before debounce fires
    expect(mock.history.get.length).toBe(0);

    // Advance past debounce
    act(() => { vi.advanceTimersByTime(300); });

    await waitFor(() => expect(mock.history.get.length).toBeGreaterThan(0));
  });

  it('displays results in dropdown after debounce', async () => {
    mock.onGet('/mitre/techniques').reply(200, TECHNIQUES);
    const user = userEvent.setup({ advanceTimers: (ms) => vi.advanceTimersByTime(ms) });

    renderWithProviders(
      <MitreTechniquePicker
        techniqueId={null}
        techniqueName={null}
        onChange={vi.fn()}
      />,
    );

    const input = screen.getByRole('combobox');
    await user.click(input);
    await user.type(input, 'T1059');
    act(() => { vi.advanceTimersByTime(300); });

    await waitFor(() => {
      expect(screen.getByRole('listbox')).toBeInTheDocument();
    });

    const options = screen.getAllByRole('option');
    expect(options.length).toBeGreaterThan(0);
    expect(options[0].textContent).toContain('T1059');
  });

  it('selecting a result calls onChange with id and name', async () => {
    mock.onGet('/mitre/techniques').reply(200, TECHNIQUES);
    const onChange = vi.fn();
    const user = userEvent.setup({ advanceTimers: (ms) => vi.advanceTimersByTime(ms) });

    renderWithProviders(
      <MitreTechniquePicker
        techniqueId={null}
        techniqueName={null}
        onChange={onChange}
      />,
    );

    const input = screen.getByRole('combobox');
    await user.click(input);
    await user.type(input, 'T1059');
    act(() => { vi.advanceTimersByTime(300); });

    await waitFor(() => screen.getByRole('listbox'));

    const options = screen.getAllByRole('option');
    await user.click(options[0]);

    expect(onChange).toHaveBeenCalledWith('T1059', 'Command and Scripting Interpreter');
  });

  it('populates input display string after selection', async () => {
    mock.onGet('/mitre/techniques').reply(200, TECHNIQUES);
    const user = userEvent.setup({ advanceTimers: (ms) => vi.advanceTimersByTime(ms) });

    renderWithProviders(
      <MitreTechniquePicker
        techniqueId={null}
        techniqueName={null}
        onChange={vi.fn()}
      />,
    );

    const input = screen.getByRole('combobox') as HTMLInputElement;
    await user.click(input);
    await user.type(input, 'T1059');
    act(() => { vi.advanceTimersByTime(300); });

    await waitFor(() => screen.getByRole('listbox'));

    const options = screen.getAllByRole('option');
    await user.click(options[0]);

    expect(input.value).toContain('T1059');
    expect(input.value).toContain('Command and Scripting Interpreter');
  });

  it('keyboard ArrowDown + Enter selects item', async () => {
    mock.onGet('/mitre/techniques').reply(200, TECHNIQUES);
    const onChange = vi.fn();
    const user = userEvent.setup({ advanceTimers: (ms) => vi.advanceTimersByTime(ms) });

    renderWithProviders(
      <MitreTechniquePicker
        techniqueId={null}
        techniqueName={null}
        onChange={onChange}
      />,
    );

    const input = screen.getByRole('combobox');
    await user.click(input);
    await user.type(input, 'T105');
    act(() => { vi.advanceTimersByTime(300); });

    await waitFor(() => screen.getByRole('listbox'));

    await user.keyboard('{ArrowDown}');
    await user.keyboard('{Enter}');

    expect(onChange).toHaveBeenCalled();
  });

  it('Escape closes the dropdown', async () => {
    mock.onGet('/mitre/techniques').reply(200, TECHNIQUES);
    const user = userEvent.setup({ advanceTimers: (ms) => vi.advanceTimersByTime(ms) });

    renderWithProviders(
      <MitreTechniquePicker
        techniqueId={null}
        techniqueName={null}
        onChange={vi.fn()}
      />,
    );

    const input = screen.getByRole('combobox');
    await user.click(input);
    await user.type(input, 'T1059');
    act(() => { vi.advanceTimersByTime(300); });

    await waitFor(() => screen.getByRole('listbox'));

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

    expect(screen.queryByRole('listbox')).toBeNull();
  });

  it('typing while techniqueId is null does not reset inputValue between keystrokes', async () => {
    mock.onGet('/mitre/techniques').reply(200, []);
    const user = userEvent.setup({ advanceTimers: (ms) => vi.advanceTimersByTime(ms) });

    renderWithProviders(
      <MitreTechniquePicker
        techniqueId={null}
        techniqueName={null}
        onChange={vi.fn()}
      />,
    );

    const input = screen.getByRole('combobox') as HTMLInputElement;
    await user.click(input);
    await user.type(input, 'T10');

    // Input must retain the full typed value — no mid-stroke reset
    expect(input.value).toBe('T10');
  });

  it('shows inline error when API returns 503', async () => {
    mock.onGet('/mitre/techniques').reply(503, { error: 'mitre bundle not loaded' });
    const user = userEvent.setup({ advanceTimers: (ms) => vi.advanceTimersByTime(ms) });

    renderWithProviders(
      <MitreTechniquePicker
        techniqueId={null}
        techniqueName={null}
        onChange={vi.fn()}
      />,
    );

    const input = screen.getByRole('combobox');
    await user.click(input);
    await user.type(input, 'T1059');
    act(() => { vi.advanceTimersByTime(300); });

    await waitFor(() => {
      expect(screen.getByRole('alert')).toBeInTheDocument();
    });
  });
});
