/**
 * @ai-summary Tests for CameraCapture component
 * @ai-context Validates camera permission, capture flow, cropping, and file fallback
 */

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import { CameraCapture } from './CameraCapture';
import { FileInputFallback } from './FileInputFallback';
import { GuidanceOverlay } from './GuidanceOverlay';

// Mock MUI useMediaQuery
jest.mock('@mui/material', () => ({
  ...jest.requireActual('@mui/material'),
  useMediaQuery: jest.fn(() => false),
}));

// Mock navigator.mediaDevices
const mockGetUserMedia = jest.fn();
const mockEnumerateDevices = jest.fn();
const mockMediaStream = {
  getTracks: () => [{ stop: jest.fn() }],
  getVideoTracks: () => [
    {
      getSettings: () => ({ width: 1920, height: 1080 }),
      stop: jest.fn(),
    },
  ],
};

beforeAll(() => {
  Object.defineProperty(navigator, 'mediaDevices', {
    value: {
      getUserMedia: mockGetUserMedia,
      enumerateDevices: mockEnumerateDevices,
    },
    writable: true,
  });
});

beforeEach(() => {
  jest.clearAllMocks();
  mockEnumerateDevices.mockResolvedValue([
    { kind: 'videoinput', deviceId: 'camera1' },
  ]);
});

describe('CameraCapture', () => {
  const mockOnCapture = jest.fn();
  const mockOnCancel = jest.fn();

  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe('Permission handling', () => {
    it('shows loading state while requesting permission', () => {
      mockGetUserMedia.mockImplementation(() => new Promise(() => {})); // Never resolves

      render(
        <CameraCapture onCapture={mockOnCapture} onCancel={mockOnCancel} />
      );

      expect(screen.getByText(/requesting camera access/i)).toBeInTheDocument();
    });

    it('shows error when permission denied', async () => {
      mockGetUserMedia.mockRejectedValue(
        new DOMException('Permission denied', 'NotAllowedError')
      );

      render(
        <CameraCapture onCapture={mockOnCapture} onCancel={mockOnCancel} />
      );

      await waitFor(() => {
        expect(screen.getByText(/camera permission/i)).toBeInTheDocument();
      });

      expect(screen.getByRole('button', { name: /upload file/i })).toBeInTheDocument();
    });

    it('shows error when camera unavailable', async () => {
      mockGetUserMedia.mockRejectedValue(
        new DOMException('No camera', 'NotFoundError')
      );

      render(
        <CameraCapture onCapture={mockOnCapture} onCancel={mockOnCancel} />
      );

      await waitFor(() => {
        expect(screen.getByText(/no camera found/i)).toBeInTheDocument();
      });
    });
  });

  describe('Viewfinder', () => {
    it('shows viewfinder when camera access granted', async () => {
      mockGetUserMedia.mockResolvedValue(mockMediaStream);

      render(
        <CameraCapture onCapture={mockOnCapture} onCancel={mockOnCancel} />
      );

      await waitFor(() => {
        expect(screen.getByRole('button', { name: /take photo/i })).toBeInTheDocument();
      });
    });

    it('shows cancel button in viewfinder', async () => {
      mockGetUserMedia.mockResolvedValue(mockMediaStream);

      render(
        <CameraCapture onCapture={mockOnCapture} onCancel={mockOnCancel} />
      );

      await waitFor(() => {
        expect(screen.getByRole('button', { name: /cancel capture/i })).toBeInTheDocument();
      });
    });

    it('calls onCancel when cancel button clicked', async () => {
      mockGetUserMedia.mockResolvedValue(mockMediaStream);

      render(
        <CameraCapture onCapture={mockOnCapture} onCancel={mockOnCancel} />
      );

      await waitFor(() => {
        expect(screen.getByRole('button', { name: /cancel capture/i })).toBeInTheDocument();
      });

      fireEvent.click(screen.getByRole('button', { name: /cancel capture/i }));
      expect(mockOnCancel).toHaveBeenCalledTimes(1);
    });
  });

  describe('Guidance overlay', () => {
    it('shows VIN guidance when guidanceType is vin', async () => {
      mockGetUserMedia.mockResolvedValue(mockMediaStream);

      render(
        <CameraCapture
          onCapture={mockOnCapture}
          onCancel={mockOnCancel}
          guidanceType="vin"
        />
      );

      await waitFor(() => {
        expect(screen.getByText(/position vin/i)).toBeInTheDocument();
      });
    });

    it('shows receipt guidance when guidanceType is receipt', async () => {
      mockGetUserMedia.mockResolvedValue(mockMediaStream);

      render(
        <CameraCapture
          onCapture={mockOnCapture}
          onCancel={mockOnCancel}
          guidanceType="receipt"
        />
      );

      await waitFor(() => {
        expect(screen.getByText(/position receipt/i)).toBeInTheDocument();
      });
    });
  });

  describe('File fallback', () => {
    it('shows upload file button in viewfinder', async () => {
      mockGetUserMedia.mockResolvedValue(mockMediaStream);

      render(
        <CameraCapture onCapture={mockOnCapture} onCancel={mockOnCancel} />
      );

      await waitFor(() => {
        expect(screen.getByRole('button', { name: /upload file/i })).toBeInTheDocument();
      });
    });

    it('switches to file fallback when upload file clicked', async () => {
      mockGetUserMedia.mockResolvedValue(mockMediaStream);

      render(
        <CameraCapture onCapture={mockOnCapture} onCancel={mockOnCancel} />
      );

      await waitFor(() => {
        expect(screen.getByRole('button', { name: /upload file/i })).toBeInTheDocument();
      });

      fireEvent.click(screen.getByRole('button', { name: /upload file/i }));

      await waitFor(() => {
        expect(screen.getByText(/upload image/i)).toBeInTheDocument();
      });
    });
  });
});

describe('FileInputFallback', () => {
  const mockOnFileSelect = jest.fn();
  const mockOnCancel = jest.fn();

  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('renders upload area', () => {
    render(
      <FileInputFallback
        onFileSelect={mockOnFileSelect}
        onCancel={mockOnCancel}
        acceptedFormats={['image/jpeg', 'image/png']}
        maxFileSize={10 * 1024 * 1024}
      />
    );

    expect(screen.getByText(/drag and drop/i)).toBeInTheDocument();
  });

  it('shows accepted formats', () => {
    render(
      <FileInputFallback
        onFileSelect={mockOnFileSelect}
        onCancel={mockOnCancel}
        acceptedFormats={['image/jpeg', 'image/png', 'image/heic']}
        maxFileSize={10 * 1024 * 1024}
      />
    );

    expect(screen.getByText(/jpeg, png, heic/i)).toBeInTheDocument();
  });

  it('shows max file size', () => {
    render(
      <FileInputFallback
        onFileSelect={mockOnFileSelect}
        onCancel={mockOnCancel}
        acceptedFormats={['image/jpeg']}
        maxFileSize={10 * 1024 * 1024}
      />
    );

    expect(screen.getByText(/10mb/i)).toBeInTheDocument();
  });

  it('calls onCancel when cancel clicked', () => {
    render(
      <FileInputFallback
        onFileSelect={mockOnFileSelect}
        onCancel={mockOnCancel}
        acceptedFormats={['image/jpeg']}
        maxFileSize={10 * 1024 * 1024}
      />
    );

    fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
    expect(mockOnCancel).toHaveBeenCalledTimes(1);
  });

  it('shows error for invalid file type', async () => {
    render(
      <FileInputFallback
        onFileSelect={mockOnFileSelect}
        onCancel={mockOnCancel}
        acceptedFormats={['image/jpeg']}
        maxFileSize={10 * 1024 * 1024}
      />
    );

    const input = document.querySelector('input[type="file"]') as HTMLInputElement;
    const invalidFile = new File(['test'], 'test.txt', { type: 'text/plain' });

    Object.defineProperty(input, 'files', {
      value: [invalidFile],
    });

    fireEvent.change(input);

    await waitFor(() => {
      expect(screen.getByText(/invalid file type/i)).toBeInTheDocument();
    });
  });

  it('shows error for file too large', async () => {
    render(
      <FileInputFallback
        onFileSelect={mockOnFileSelect}
        onCancel={mockOnCancel}
        acceptedFormats={['image/jpeg']}
        maxFileSize={1024} // 1KB
      />
    );

    const input = document.querySelector('input[type="file"]') as HTMLInputElement;
    const largeFile = new File(['x'.repeat(2048)], 'large.jpg', {
      type: 'image/jpeg',
    });

    Object.defineProperty(input, 'files', {
      value: [largeFile],
    });

    fireEvent.change(input);

    await waitFor(() => {
      expect(screen.getByText(/file too large/i)).toBeInTheDocument();
    });
  });

  it('calls onFileSelect with valid file', async () => {
    render(
      <FileInputFallback
        onFileSelect={mockOnFileSelect}
        onCancel={mockOnCancel}
        acceptedFormats={['image/jpeg']}
        maxFileSize={10 * 1024 * 1024}
      />
    );

    const input = document.querySelector('input[type="file"]') as HTMLInputElement;
    const validFile = new File(['test'], 'test.jpg', { type: 'image/jpeg' });

    Object.defineProperty(input, 'files', {
      value: [validFile],
    });

    fireEvent.change(input);

    await waitFor(() => {
      expect(mockOnFileSelect).toHaveBeenCalledWith(validFile);
    });
  });
});

describe('GuidanceOverlay', () => {
  it('renders nothing when type is none', () => {
    const { container } = render(<GuidanceOverlay type="none" />);
    expect(container.firstChild).toBeNull();
  });

  it('renders VIN guidance with correct description', () => {
    render(<GuidanceOverlay type="vin" />);
    expect(screen.getByText(/position vin plate/i)).toBeInTheDocument();
  });

  it('renders receipt guidance with correct description', () => {
    render(<GuidanceOverlay type="receipt" />);
    expect(screen.getByText(/position receipt/i)).toBeInTheDocument();
  });

  it('renders document guidance with correct description', () => {
    render(<GuidanceOverlay type="document" />);
    expect(screen.getByText(/position document/i)).toBeInTheDocument();
  });
});
