import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { MemoryRouter } from 'react-router';
import { ThemeProvider, ToastProvider } from '@cameleer/design-system';
import InboxPage from './InboxPage';
import type { AlertDto } from '../../api/queries/alerts';
import { useAuthStore } from '../../auth/auth-store';
import { useEnvironmentStore } from '../../api/environment-store';

// ── hook mocks ──────────────────────────────────────────────────────────────

// Capture the args passed to useAlerts so we can assert on filter params.
const alertsCallArgs = vi.fn();
const deleteMock = vi.fn().mockResolvedValue(undefined);
const bulkDeleteMock = vi.fn().mockResolvedValue(undefined);
const ackMock = vi.fn().mockResolvedValue(undefined);
const markReadMutateAsync = vi.fn().mockResolvedValue(undefined);
const markReadMutate = vi.fn();
const bulkAckMock = vi.fn().mockResolvedValue(undefined);
const bulkReadMock = vi.fn().mockResolvedValue(undefined);
const restoreMock = vi.fn().mockResolvedValue(undefined);
const createSilenceMock = vi.fn().mockResolvedValue(undefined);

// alertsMock is a factory — each call records its args and returns the current
// rows so we can change the fixture per test.
let currentRows: AlertDto[] = [];

vi.mock('../../api/queries/alerts', () => ({
  useAlerts: (filter: unknown) => {
    alertsCallArgs(filter);
    return { data: currentRows, isLoading: false, error: null };
  },
  useAckAlert: () => ({ mutateAsync: ackMock, isPending: false }),
  useMarkAlertRead: () => ({ mutateAsync: markReadMutateAsync, mutate: markReadMutate, isPending: false }),
  useBulkReadAlerts: () => ({ mutateAsync: bulkReadMock, isPending: false }),
  useBulkAckAlerts: () => ({ mutateAsync: bulkAckMock, isPending: false }),
  useDeleteAlert: () => ({ mutateAsync: deleteMock, isPending: false }),
  useBulkDeleteAlerts: () => ({ mutateAsync: bulkDeleteMock, isPending: false }),
  useRestoreAlert: () => ({ mutateAsync: restoreMock }),
}));

vi.mock('../../api/queries/alertSilences', () => ({
  useCreateSilence: () => ({ mutateAsync: createSilenceMock, isPending: false }),
}));

// ── fixture rows ────────────────────────────────────────────────────────────

const ROW_FIRING: AlertDto = {
  id: '11111111-1111-1111-1111-111111111111',
  ruleId: 'rrrrrrrr-rrrr-rrrr-rrrr-rrrrrrrrrrrr',
  state: 'FIRING',
  severity: 'CRITICAL',
  title: 'Order pipeline down',
  message: 'msg',
  firedAt: '2026-04-21T10:00:00Z',
  ackedAt: undefined,
  ackedBy: undefined,
  resolvedAt: undefined,
  readAt: undefined,
  silenced: false,
  currentValue: undefined,
  threshold: undefined,
  environmentId: 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee',
  context: {},
};

const ROW_ACKED: AlertDto = {
  ...ROW_FIRING,
  id: '22222222-2222-2222-2222-222222222222',
  ackedAt: '2026-04-21T10:05:00Z',
  ackedBy: 'alice',
};

// ── mount helper ────────────────────────────────────────────────────────────

function mount() {
  const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  return render(
    <ThemeProvider>
      <QueryClientProvider client={qc}>
        <ToastProvider>
          <MemoryRouter initialEntries={['/alerts/inbox']}>
            <InboxPage />
          </MemoryRouter>
        </ToastProvider>
      </QueryClientProvider>
    </ThemeProvider>,
  );
}

// ── setup ───────────────────────────────────────────────────────────────────

beforeEach(() => {
  vi.clearAllMocks();
  // Reset mocks to their default resolved values after clearAllMocks
  deleteMock.mockResolvedValue(undefined);
  bulkDeleteMock.mockResolvedValue(undefined);
  ackMock.mockResolvedValue(undefined);
  markReadMutateAsync.mockResolvedValue(undefined);
  bulkAckMock.mockResolvedValue(undefined);
  bulkReadMock.mockResolvedValue(undefined);
  restoreMock.mockResolvedValue(undefined);
  createSilenceMock.mockResolvedValue(undefined);

  currentRows = [ROW_FIRING];

  // Set OPERATOR role and an env so hooks are enabled
  useAuthStore.setState({ roles: ['OPERATOR'] });
  useEnvironmentStore.setState({ environment: 'dev' });
});

// ── tests ───────────────────────────────────────────────────────────────────

describe('InboxPage', () => {
  it('calls useAlerts with default filters: state=[FIRING], acked=false, read=false', () => {
    mount();

    // useAlerts is called during render; check the first call's filter arg
    expect(alertsCallArgs).toHaveBeenCalledWith(
      expect.objectContaining({
        state: ['FIRING'],
        acked: false,
        read: false,
      }),
    );
  });

  it('unchecking "Hide acked" removes the acked filter', () => {
    mount();

    // Initial call should include acked: false
    expect(alertsCallArgs).toHaveBeenCalledWith(
      expect.objectContaining({ acked: false }),
    );

    // Find and uncheck the "Hide acked" toggle
    const hideAckedToggle = screen.getByRole('checkbox', { name: /hide acked/i });
    fireEvent.click(hideAckedToggle);

    // After unchecking, useAlerts should be called without acked: false
    // (the component passes `undefined` when the toggle is off)
    const lastCall = alertsCallArgs.mock.calls[alertsCallArgs.mock.calls.length - 1][0];
    expect(lastCall.acked).toBeUndefined();
  });

  it('shows Acknowledge button only on rows where ackedAt is null', () => {
    currentRows = [ROW_FIRING, ROW_ACKED];
    mount();

    // ROW_FIRING has ackedAt=undefined → Ack button should appear
    // ROW_ACKED has ackedAt set → Ack button should NOT appear
    // The row-level Ack button label is "Ack"
    const ackButtons = screen.getAllByRole('button', { name: /^ack$/i });
    expect(ackButtons).toHaveLength(1);
  });

  it('opens bulk-delete confirmation with the correct count', async () => {
    currentRows = [ROW_FIRING, ROW_ACKED];
    mount();

    // Use the "Select all" checkbox in the filter bar to select all 2 rows.
    // It is labelled "Select all (2)" when nothing is selected.
    const selectAllCb = screen.getByRole('checkbox', { name: /select all/i });
    // fireEvent.click toggles checkboxes and triggers React's onChange
    fireEvent.click(selectAllCb);

    // After selection the bulk toolbar should show a "Delete N" button
    const deleteButton = await screen.findByRole('button', { name: /^delete 2$/i });
    fireEvent.click(deleteButton);

    // The ConfirmDialog should now be open with the count in the message
    await waitFor(() => {
      expect(screen.getByText(/delete 2 alerts/i)).toBeInTheDocument();
    });
  });

  it('hides Delete buttons when user lacks OPERATOR role', () => {
    useAuthStore.setState({ roles: ['VIEWER'] });
    mount();

    // Neither the row-level "Delete alert" button nor the bulk "Delete N" button should appear
    expect(screen.queryByRole('button', { name: /delete alert/i })).toBeNull();
    // No selection is active so "Delete N" wouldn't appear anyway, but confirm
    // there's also no element with "Delete" that would open the confirm dialog
    const deleteButtons = screen
      .queryAllByRole('button')
      .filter((btn) => /^delete\b/i.test(btn.textContent ?? ''));
    expect(deleteButtons).toHaveLength(0);
  });

  it('clicking row Delete invokes useDeleteAlert and shows an Undo toast', async () => {
    mount();

    const deleteAlertButton = screen.getByRole('button', { name: /delete alert/i });
    fireEvent.click(deleteAlertButton);

    // Verify deleteMock was called with the row's id
    await waitFor(() => {
      expect(deleteMock).toHaveBeenCalledWith(ROW_FIRING.id);
    });

    // After deletion a toast appears with "Deleted" title and an "Undo" button
    await waitFor(() => {
      expect(screen.getByText(/deleted/i)).toBeInTheDocument();
    });
    expect(screen.getByRole('button', { name: /undo/i })).toBeInTheDocument();
  });
});
