import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { render } from '@testing-library/react';
import MockAdapter from 'axios-mock-adapter';
import { MemoryRouter, Routes, Route } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { apiClient } from '@/api/client';
import { TemplateFormPage } from '@/pages/TemplateFormPage';
import { ToastProvider } from '@/hooks/useToast';
import type { SimulationTemplate } from '@/api/types';

vi.mock('@/hooks/useAuth', () => ({
  useAuth: () => ({
    user: { id: 1, username: 'alice', role: 'admin', created_at: '2026-01-01' },
    status: 'authenticated',
    login: vi.fn(),
    logout: vi.fn(),
    isAdmin: true,
    isRedteam: false,
    isSoc: false,
    canEditEngagements: true,
  }),
}));

const TEMPLATE: SimulationTemplate = {
  id: 5,
  name: 'Mimikatz LSASS Dump',
  description: 'Extract NTLM hashes',
  commands: 'mimikatz.exe',
  prerequisites: 'Local admin',
  techniques: [{ id: 'T1003', name: 'OS Credential Dumping', tactics: ['credential-access'] }],
  tactics: [{ id: 'TA0006', name: 'Credential Access' }],
  created_at: '2026-05-28T00:00:00',
  updated_at: null,
  created_by: { id: 1, username: 'alice' },
};

function makeClient() {
  return new QueryClient({
    defaultOptions: {
      queries: { retry: false, gcTime: 0, staleTime: 0 },
      mutations: { retry: false },
    },
  });
}

function renderNew() {
  const client = makeClient();
  return {
    ...render(
      <QueryClientProvider client={client}>
        <MemoryRouter initialEntries={['/admin/templates/new']}>
          <Routes>
            <Route path="/admin/templates/new" element={<ToastProvider><TemplateFormPage /></ToastProvider>} />
            <Route path="/admin/templates/:id/edit" element={<ToastProvider><TemplateFormPage /></ToastProvider>} />
          </Routes>
        </MemoryRouter>
      </QueryClientProvider>
    ),
    client,
  };
}

function renderEdit(id: number) {
  const client = makeClient();
  return {
    ...render(
      <QueryClientProvider client={client}>
        <MemoryRouter initialEntries={[`/admin/templates/${id}/edit`]}>
          <Routes>
            <Route path="/admin/templates/:id/edit" element={<ToastProvider><TemplateFormPage /></ToastProvider>} />
            <Route path="/admin/templates" element={<div data-testid="templates-list" />} />
          </Routes>
        </MemoryRouter>
      </QueryClientProvider>
    ),
    client,
  };
}

describe('TemplateFormPage — new mode', () => {
  let mock: MockAdapter;

  beforeEach(() => {
    mock = new MockAdapter(apiClient);
  });

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

  it('renders the form with name field in empty state', () => {
    renderNew();
    expect(screen.getByLabelText(/^Nom/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/^Description/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/^Commandes/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/^Prérequis/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/^Nom/i)).toHaveValue('');
  });

  it('shows validation error when name is empty on submit', async () => {
    const user = userEvent.setup();
    renderNew();
    const saveBtn = screen.getByRole('button', { name: /Enregistrer/i });
    await user.click(saveBtn);
    await waitFor(() => {
      expect(screen.getByText('Le nom est obligatoire')).toBeInTheDocument();
    });
  });

  it('submits POST when name is filled', async () => {
    mock.onPost('/templates').reply(201, { ...TEMPLATE, id: 99 });
    const user = userEvent.setup();
    renderNew();
    await user.type(screen.getByLabelText(/^Nom/i), 'My Template');
    await user.click(screen.getByRole('button', { name: /Enregistrer/i }));
    await waitFor(() => {
      expect(mock.history.post.length).toBe(1);
    });
    const body = JSON.parse(mock.history.post[0].data as string) as Record<string, unknown>;
    expect(body.name).toBe('My Template');
  });

  it('shows backend error on name conflict (409)', async () => {
    mock.onPost('/templates').reply(409, { error: 'template name already exists' });
    const user = userEvent.setup();
    renderNew();
    await user.type(screen.getByLabelText(/^Nom/i), 'Duplicate');
    await user.click(screen.getByRole('button', { name: /Enregistrer/i }));
    await waitFor(() => {
      expect(screen.getByText('template name already exists')).toBeInTheDocument();
    });
  });

  it('does not show Delete button in new mode', () => {
    renderNew();
    expect(screen.queryByText('Supprimer')).toBeNull();
  });
});

describe('TemplateFormPage — edit mode', () => {
  let mock: MockAdapter;

  beforeEach(() => {
    mock = new MockAdapter(apiClient);
  });

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

  it('loads existing template data into form', async () => {
    mock.onGet('/templates/5').reply(200, TEMPLATE);
    renderEdit(5);
    await waitFor(() => {
      expect(screen.getByDisplayValue('Mimikatz LSASS Dump')).toBeInTheDocument();
    });
    expect(screen.getByDisplayValue('Extract NTLM hashes')).toBeInTheDocument();
  });

  it('shows technique and tactic chips from existing template', async () => {
    mock.onGet('/templates/5').reply(200, TEMPLATE);
    renderEdit(5);
    await waitFor(() => {
      expect(screen.getByTitle('T1003 — OS Credential Dumping')).toBeInTheDocument();
    });
    expect(screen.getByTitle('TA0006 — Credential Access')).toBeInTheDocument();
  });

  it('shows Delete button in edit mode', async () => {
    mock.onGet('/templates/5').reply(200, TEMPLATE);
    renderEdit(5);
    await waitFor(() => {
      expect(screen.getByDisplayValue('Mimikatz LSASS Dump')).toBeInTheDocument();
    });
    expect(screen.getByText('Supprimer')).toBeInTheDocument();
  });

  it('submits PATCH on save', async () => {
    mock.onGet('/templates/5').reply(200, TEMPLATE);
    mock.onPatch('/templates/5').reply(200, { ...TEMPLATE, name: 'Updated' });
    const user = userEvent.setup();
    renderEdit(5);
    await waitFor(() => {
      expect(screen.getByDisplayValue('Mimikatz LSASS Dump')).toBeInTheDocument();
    });
    await user.click(screen.getByRole('button', { name: /Enregistrer/i }));
    await waitFor(() => {
      expect(mock.history.patch.length).toBe(1);
    });
    const body = JSON.parse(mock.history.patch[0].data as string) as Record<string, unknown>;
    expect(body.name).toBe('Mimikatz LSASS Dump');
  });

  it('opens delete confirm dialog and calls DELETE on confirm', async () => {
    mock.onGet('/templates/5').reply(200, TEMPLATE);
    mock.onDelete('/templates/5').reply(204);
    const user = userEvent.setup();
    renderEdit(5);
    await waitFor(() => {
      expect(screen.getByText('Supprimer')).toBeInTheDocument();
    });
    await user.click(screen.getByText('Supprimer'));
    await waitFor(() => {
      expect(screen.getByRole('dialog')).toBeInTheDocument();
    });
    // Click the Supprimer button inside the dialog
    const dialogDeleteBtn = screen.getAllByText('Supprimer').find(
      (el) => el.tagName === 'BUTTON' && el.closest('[role="dialog"]')
    ) as HTMLElement;
    await user.click(dialogDeleteBtn);
    await waitFor(() => {
      expect(mock.history.delete.length).toBe(1);
    });
  });
});
