/**
 * Tests for CloseShiftModal component
 */

import React from 'react';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import CloseShiftModal from '../CloseShiftModal';
import { useCloseShift } from '../../hooks/useCashDrawer';
import type { CashShift } from '../../types';

// Mock hooks
vi.mock('../../hooks/useCashDrawer');

const mockUseCloseShift = useCloseShift as any;

const mockShift: CashShift = {
  id: 1,
  location: 1,
  status: 'open',
  opening_balance_cents: 10000,
  expected_balance_cents: 15000,
  opened_at: '2024-12-26T09:00:00Z',
  opened_by: 1,
  closed_by: null,
  actual_balance_cents: null,
  variance_cents: null,
  cash_breakdown: {},
  closing_notes: '',
  opening_notes: '',
  closed_at: null,
};

const createWrapper = () => {
  const queryClient = new QueryClient({
    defaultOptions: {
      queries: { retry: false },
      mutations: { retry: false },
    },
  });

  return ({ children }: { children: React.ReactNode }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  );
};

describe('CloseShiftModal', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('should render when open', () => {
    mockUseCloseShift.mockReturnValue({
      mutateAsync: vi.fn(),
      isPending: false,
    });

    render(
      <CloseShiftModal
        isOpen={true}
        onClose={vi.fn()}
        shift={mockShift}
      />,
      { wrapper: createWrapper() }
    );

    expect(screen.getByText(/close cash drawer/i)).toBeInTheDocument();
    expect(screen.getByText(/count cash/i)).toBeInTheDocument();
  });

  it('should display expected balance prominently', () => {
    mockUseCloseShift.mockReturnValue({
      mutateAsync: vi.fn(),
      isPending: false,
    });

    render(
      <CloseShiftModal
        isOpen={true}
        onClose={vi.fn()}
        shift={mockShift}
      />,
      { wrapper: createWrapper() }
    );

    expect(screen.getByText(/expected balance/i)).toBeInTheDocument();
    // Check for the expected balance in the blue box
    const expectedBalanceElements = screen.getAllByText(/\$150\.00/);
    expect(expectedBalanceElements.length).toBeGreaterThan(0);
    expect(expectedBalanceElements[0]).toHaveClass('text-blue-900');
  });

  it('should have denomination counter inputs', () => {
    mockUseCloseShift.mockReturnValue({
      mutateAsync: vi.fn(),
      isPending: false,
    });

    render(
      <CloseShiftModal
        isOpen={true}
        onClose={vi.fn()}
        shift={mockShift}
      />,
      { wrapper: createWrapper() }
    );

    // Bills
    expect(screen.getByLabelText(/\$100 bills/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/\$50 bills/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/\$20 bills/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/\$10 bills/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/\$5 bills/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/\$1 bills/i)).toBeInTheDocument();

    // Coins
    expect(screen.getByLabelText(/quarters/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/dimes/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/nickels/i)).toBeInTheDocument();
    expect(screen.getByLabelText(/pennies/i)).toBeInTheDocument();
  });

  it('should calculate total from denominations', () => {
    mockUseCloseShift.mockReturnValue({
      mutateAsync: vi.fn(),
      isPending: false,
    });

    render(
      <CloseShiftModal
        isOpen={true}
        onClose={vi.fn()}
        shift={mockShift}
      />,
      { wrapper: createWrapper() }
    );

    // Enter: 1x $100, 2x $20, 1x $5
    fireEvent.change(screen.getByLabelText(/\$100 bills/i), { target: { value: '1' } });
    fireEvent.change(screen.getByLabelText(/\$20 bills/i), { target: { value: '2' } });
    fireEvent.change(screen.getByLabelText(/\$5 bills/i), { target: { value: '1' } });

    // Total should be $145.00 - look for it in the "Actual Balance" section
    expect(screen.getByText('Actual Balance')).toBeInTheDocument();
    const actualBalanceElements = screen.getAllByText(/\$145\.00/);
    expect(actualBalanceElements.length).toBeGreaterThan(0);
  });

  it('should show variance in green when actual matches expected', () => {
    mockUseCloseShift.mockReturnValue({
      mutateAsync: vi.fn(),
      isPending: false,
    });

    render(
      <CloseShiftModal
        isOpen={true}
        onClose={vi.fn()}
        shift={mockShift}
      />,
      { wrapper: createWrapper() }
    );

    // Enter exactly $150.00 (expected balance)
    fireEvent.change(screen.getByLabelText(/\$100 bills/i), { target: { value: '1' } });
    fireEvent.change(screen.getByLabelText(/\$50 bills/i), { target: { value: '1' } });

    // Find variance section - get parent container with background
    const varianceLabel = screen.getByText('Variance');
    const parentDiv = varianceLabel.parentElement;
    expect(parentDiv).toHaveClass('bg-green-50');

    // Variance amount should be green
    const varianceAmounts = screen.getAllByText(/\$0\.00/);
    const varianceAmount = varianceAmounts.find(el => el.classList.contains('text-green-600'));
    expect(varianceAmount).toBeDefined();
  });

  it('should show variance in red when actual is short', () => {
    mockUseCloseShift.mockReturnValue({
      mutateAsync: vi.fn(),
      isPending: false,
    });

    render(
      <CloseShiftModal
        isOpen={true}
        onClose={vi.fn()}
        shift={mockShift}
      />,
      { wrapper: createWrapper() }
    );

    // Enter $100.00 (short by $50)
    fireEvent.change(screen.getByLabelText(/\$100 bills/i), { target: { value: '1' } });

    // Find variance section - should be red
    const varianceLabel = screen.getByText('Variance');
    const parentDiv = varianceLabel.parentElement;
    expect(parentDiv).toHaveClass('bg-red-50');

    // Variance amount should be red
    const varianceText = screen.getByText(/-\$50\.00/);
    expect(varianceText).toHaveClass('text-red-600');
  });

  it('should show variance in green when actual is over', () => {
    mockUseCloseShift.mockReturnValue({
      mutateAsync: vi.fn(),
      isPending: false,
    });

    render(
      <CloseShiftModal
        isOpen={true}
        onClose={vi.fn()}
        shift={mockShift}
      />,
      { wrapper: createWrapper() }
    );

    // Enter $200.00 (over by $50)
    fireEvent.change(screen.getByLabelText(/\$100 bills/i), { target: { value: '2' } });

    // Find variance section - should be green
    const varianceLabel = screen.getByText('Variance');
    const parentDiv = varianceLabel.parentElement;
    expect(parentDiv).toHaveClass('bg-green-50');

    // Variance amount should be green and positive
    const varianceText = screen.getByText(/\+\$50\.00/);
    expect(varianceText).toHaveClass('text-green-600');
  });

  it('should allow adding closing notes', () => {
    mockUseCloseShift.mockReturnValue({
      mutateAsync: vi.fn(),
      isPending: false,
    });

    render(
      <CloseShiftModal
        isOpen={true}
        onClose={vi.fn()}
        shift={mockShift}
      />,
      { wrapper: createWrapper() }
    );

    const notesInput = screen.getByPlaceholderText(/notes about the shift/i);
    fireEvent.change(notesInput, { target: { value: 'Short due to refund' } });

    expect(notesInput).toHaveValue('Short due to refund');
  });

  it('should call onClose when Cancel clicked', () => {
    mockUseCloseShift.mockReturnValue({
      mutateAsync: vi.fn(),
      isPending: false,
    });

    const onClose = vi.fn();

    render(
      <CloseShiftModal
        isOpen={true}
        onClose={onClose}
        shift={mockShift}
      />,
      { wrapper: createWrapper() }
    );

    fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
    expect(onClose).toHaveBeenCalled();
  });

  it('should close shift with correct data when Close Shift clicked', async () => {
    const mockMutateAsync = vi.fn().mockResolvedValue({});
    mockUseCloseShift.mockReturnValue({
      mutateAsync: mockMutateAsync,
      isPending: false,
    });

    const onSuccess = vi.fn();

    render(
      <CloseShiftModal
        isOpen={true}
        onClose={vi.fn()}
        shift={mockShift}
        onSuccess={onSuccess}
      />,
      { wrapper: createWrapper() }
    );

    // Enter cash count
    fireEvent.change(screen.getByLabelText(/\$100 bills/i), { target: { value: '1' } });
    fireEvent.change(screen.getByLabelText(/\$20 bills/i), { target: { value: '2' } });
    fireEvent.change(screen.getByLabelText(/\$5 bills/i), { target: { value: '1' } });

    // Add notes
    const notesInput = screen.getByPlaceholderText(/notes about the shift/i);
    fireEvent.change(notesInput, { target: { value: 'End of day' } });

    // Submit
    fireEvent.click(screen.getByRole('button', { name: /close shift/i }));

    await waitFor(() => {
      expect(mockMutateAsync).toHaveBeenCalledWith({
        shiftId: 1,
        actual_balance_cents: 14500, // $145.00
        cash_breakdown: {
          '10000': 1, // 1x $100
          '2000': 2,  // 2x $20
          '500': 1,   // 1x $5
        },
        closing_notes: 'End of day',
      });
    });

    expect(onSuccess).toHaveBeenCalled();
  });

  it('should disable Close Shift button when amount is zero', () => {
    mockUseCloseShift.mockReturnValue({
      mutateAsync: vi.fn(),
      isPending: false,
    });

    render(
      <CloseShiftModal
        isOpen={true}
        onClose={vi.fn()}
        shift={mockShift}
      />,
      { wrapper: createWrapper() }
    );

    const closeButton = screen.getByRole('button', { name: /close shift/i });
    expect(closeButton).toBeDisabled();
  });

  it('should show loading state during submission', () => {
    mockUseCloseShift.mockReturnValue({
      mutateAsync: vi.fn(),
      isPending: true,
    });

    render(
      <CloseShiftModal
        isOpen={true}
        onClose={vi.fn()}
        shift={mockShift}
      />,
      { wrapper: createWrapper() }
    );

    expect(screen.getByText(/closing\.\.\./i)).toBeInTheDocument();
  });

  it('should handle coin denominations correctly', () => {
    mockUseCloseShift.mockReturnValue({
      mutateAsync: vi.fn(),
      isPending: false,
    });

    render(
      <CloseShiftModal
        isOpen={true}
        onClose={vi.fn()}
        shift={mockShift}
      />,
      { wrapper: createWrapper() }
    );

    // Enter: 4 quarters, 10 dimes, 2 nickels, 5 pennies
    // = $1.00 + $1.00 + $0.10 + $0.05 = $2.15
    fireEvent.change(screen.getByLabelText(/quarters/i), { target: { value: '4' } });
    fireEvent.change(screen.getByLabelText(/dimes/i), { target: { value: '10' } });
    fireEvent.change(screen.getByLabelText(/nickels/i), { target: { value: '2' } });
    fireEvent.change(screen.getByLabelText(/pennies/i), { target: { value: '5' } });

    // Should show $2.15 in the Actual Balance section
    expect(screen.getByText('Actual Balance')).toBeInTheDocument();
    const actualBalanceElements = screen.getAllByText(/\$2\.15/);
    expect(actualBalanceElements.length).toBeGreaterThan(0);
  });
});
