import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import type { Activity, WorkSession } from '@solelog/shared';
import Stopwatch from './Stopwatch';
import { useActivities } from '../api/activities';
import {
  useActiveSessions,
  useStartSession,
  useStopSession,
  useDiscardSession,
  usePauseSession,
  useResumeSession,
} from '../api/sessions';

vi.mock('../api/activities', () => ({
  useActivities: vi.fn(),
}));

vi.mock('../api/sessions', () => ({
  useActiveSessions: vi.fn(),
  useStartSession: vi.fn(),
  useStopSession: vi.fn(),
  useDiscardSession: vi.fn(),
  usePauseSession: vi.fn(),
  useResumeSession: vi.fn(),
}));

const mockedUseActivities = vi.mocked(useActivities);
const mockedUseActiveSessions = vi.mocked(useActiveSessions);
const mockedUseStartSession = vi.mocked(useStartSession);
const mockedUseStopSession = vi.mocked(useStopSession);
const mockedUseDiscardSession = vi.mocked(useDiscardSession);
const mockedUsePauseSession = vi.mocked(usePauseSession);
const mockedUseResumeSession = vi.mocked(useResumeSession);

const FREZEN: Activity = {
  id: 1,
  name: 'Frezen',
  insole_types: ['Kurk', 'Berk'],
  sort_order: 0,
  created_at: '2026-01-01T00:00:00.000Z',
};
const PRINTEN: Activity = {
  id: 2,
  name: 'Printen',
  insole_types: ['3D'],
  sort_order: 1,
  created_at: '2026-01-01T00:00:00.000Z',
};

function activeSession(overrides: Partial<WorkSession> = {}): WorkSession {
  return {
    id: 99,
    user_id: 'u1',
    activity_id: 1,
    activity_name: 'Frezen',
    insole_type: 'Kurk',
    pair_count: 2,
    start_time: new Date().toISOString(),
    end_time: null,
    duration_seconds: null,
    paused_seconds: 0,
    paused_at: null,
    status: 'active',
    source: 'app',
    notes: null,
    created_at: new Date().toISOString(),
    ...overrides,
  };
}

// Build a fake RQ query/mutation result object the screen can consume.
function query<R>(data: unknown): R {
  return { data, isLoading: false, isError: false } as unknown as R;
}

let startMutate: ReturnType<typeof vi.fn>;
let stopMutate: ReturnType<typeof vi.fn>;
let discardMutate: ReturnType<typeof vi.fn>;
let pauseMutate: ReturnType<typeof vi.fn>;
let resumeMutate: ReturnType<typeof vi.fn>;

function mutation<R>(mutate: ReturnType<typeof vi.fn>): R {
  return { mutate, isPending: false } as unknown as R;
}

function renderStopwatch() {
  const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  return render(
    <QueryClientProvider client={queryClient}>
      <Stopwatch />
    </QueryClientProvider>
  );
}

describe('Stopwatch', () => {
  beforeEach(() => {
    startMutate = vi.fn();
    stopMutate = vi.fn();
    discardMutate = vi.fn();
    pauseMutate = vi.fn();
    resumeMutate = vi.fn();
    mockedUseActivities.mockReturnValue(query<ReturnType<typeof useActivities>>([FREZEN, PRINTEN]));
    mockedUseActiveSessions.mockReturnValue(query<ReturnType<typeof useActiveSessions>>([]));
    mockedUseStartSession.mockReturnValue(
      mutation<ReturnType<typeof useStartSession>>(startMutate)
    );
    mockedUseStopSession.mockReturnValue(mutation<ReturnType<typeof useStopSession>>(stopMutate));
    mockedUseDiscardSession.mockReturnValue(
      mutation<ReturnType<typeof useDiscardSession>>(discardMutate)
    );
    mockedUsePauseSession.mockReturnValue(
      mutation<ReturnType<typeof usePauseSession>>(pauseMutate)
    );
    mockedUseResumeSession.mockReturnValue(
      mutation<ReturnType<typeof useResumeSession>>(resumeMutate)
    );
  });

  afterEach(() => {
    vi.clearAllMocks();
    vi.useRealTimers();
  });

  it('renders the three sections and Start button in Dutch', () => {
    renderStopwatch();
    expect(screen.getByText('Type zool')).toBeInTheDocument();
    expect(screen.getByText('Type handeling')).toBeInTheDocument();
    expect(screen.getByText('Aantal zolen')).toBeInTheDocument();
    expect(screen.getByRole('button', { name: 'Start Stopwatch' })).toBeInTheDocument();
    expect(screen.getByRole('button', { name: 'Kurk' })).toBeInTheDocument();
    expect(screen.getByRole('button', { name: 'Berk' })).toBeInTheDocument();
    expect(screen.getByRole('button', { name: '3D' })).toBeInTheDocument();
    // Default count is 2 (free-typed text mirror, so the value is the string '2').
    expect(screen.getByLabelText('Aantal zolen')).toHaveValue('2');
  });

  it('disables Start until a handling is chosen', async () => {
    const user = userEvent.setup();
    renderStopwatch();

    const startBtn = screen.getByRole('button', { name: 'Start Stopwatch' });
    expect(startBtn).toBeDisabled();

    // Kurk is the default zooltype; pick the Frezen handling.
    await user.selectOptions(screen.getByLabelText('Type handeling'), 'Frezen');
    await waitFor(() => expect(startBtn).toBeEnabled());
  });

  it('filters handlings by the chosen zooltype', async () => {
    const user = userEvent.setup();
    renderStopwatch();

    const select = screen.getByLabelText('Type handeling');
    // Kurk selected by default -> Frezen present, Printen absent.
    expect(screen.getByRole('option', { name: 'Frezen' })).toBeInTheDocument();
    expect(screen.queryByRole('option', { name: 'Printen' })).not.toBeInTheDocument();

    // Switch to 3D -> Printen present, Frezen absent.
    await user.click(screen.getByRole('button', { name: '3D' }));
    expect(screen.getByRole('option', { name: 'Printen' })).toBeInTheDocument();
    expect(screen.queryByRole('option', { name: 'Frezen' })).not.toBeInTheDocument();
    expect(select).toBeInTheDocument();
  });

  it('calls start with the selected values', async () => {
    const user = userEvent.setup();
    renderStopwatch();

    // Pick Berk; Frezen applies to Berk.
    await user.click(screen.getByRole('button', { name: 'Berk' }));
    await user.selectOptions(screen.getByLabelText('Type handeling'), 'Frezen');
    // Bump count to 3.
    await user.click(screen.getByRole('button', { name: 'Meer zolen' }));

    await user.click(screen.getByRole('button', { name: 'Start Stopwatch' }));

    expect(startMutate).toHaveBeenCalledTimes(1);
    expect(startMutate.mock.calls[0][0]).toMatchObject({
      activity_id: 1,
      insole_type: 'Berk',
      pair_count: 3,
    });
  });

  it('arms discard on first Annuleren tap and discards on the second', async () => {
    const user = userEvent.setup();
    // Recover an active session so the screen renders the running UI.
    mockedUseActiveSessions.mockReturnValue(
      query<ReturnType<typeof useActiveSessions>>([activeSession()])
    );
    renderStopwatch();

    const cancel = await screen.findByRole('button', { name: 'Annuleren' });
    await user.click(cancel);

    expect(
      screen.getByRole('button', { name: 'Nogmaals tikken ter bevestiging' })
    ).toBeInTheDocument();

    await user.click(screen.getByRole('button', { name: 'Nogmaals tikken ter bevestiging' }));
    expect(discardMutate).toHaveBeenCalledTimes(1);
    expect(discardMutate.mock.calls[0][0]).toBe(99);
  });

  it('pauses via the server when the display is tapped while running', async () => {
    const user = userEvent.setup();
    mockedUseActiveSessions.mockReturnValue(
      query<ReturnType<typeof useActiveSessions>>([activeSession()])
    );
    renderStopwatch();

    const display = await screen.findByRole('button', { name: 'Stopwatch' });
    await user.click(display);

    expect(pauseMutate).toHaveBeenCalledTimes(1);
    expect(pauseMutate.mock.calls[0][0]).toBe(99);
    expect(resumeMutate).not.toHaveBeenCalled();
  });

  it('resumes via the server when the display is tapped while paused', async () => {
    const user = userEvent.setup();
    mockedUseActiveSessions.mockReturnValue(
      query<ReturnType<typeof useActiveSessions>>([
        activeSession({ paused_at: new Date().toISOString(), paused_seconds: 30 }),
      ])
    );
    renderStopwatch();

    const display = await screen.findByRole('button', { name: 'Stopwatch' });
    await user.click(display);

    expect(resumeMutate).toHaveBeenCalledTimes(1);
    expect(resumeMutate.mock.calls[0][0]).toBe(99);
    expect(pauseMutate).not.toHaveBeenCalled();
  });

  it('recovers a paused session into the paused state on load', async () => {
    mockedUseActiveSessions.mockReturnValue(
      query<ReturnType<typeof useActiveSessions>>([
        activeSession({ paused_at: new Date().toISOString(), paused_seconds: 30 }),
      ])
    );
    renderStopwatch();

    expect(await screen.findByText('Gepauzeerd — tik om te hervatten')).toBeInTheDocument();
  });
});
