import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { OCRUpload } from '@/components/ocr/OCRUpload';
import { OCRResults } from '@/components/ocr/OCRResults';
import { OCRDetail } from '@/components/ocr/OCRDetail';
import type { OCRResultResponse } from '@/lib/ocr';

// Mock fetch globally
const mockFetch = vi.fn();
global.fetch = mockFetch as unknown as typeof fetch;

// Mock localStorage
const localStorageMock = (() => {
  let store: Record<string, string> = {};
  return {
    getItem: (key: string) => store[key] || null,
    setItem: (key: string, value: string) => { store[key] = value; },
    removeItem: (key: string) => { delete store[key]; },
    clear: () => { store = {}; },
  };
})();
Object.defineProperty(window, 'localStorage', { value: localStorageMock });

// Mock apiFetch to avoid auth header complexity for list/get tests
vi.mock('@/lib/api', () => ({
  apiFetch: vi.fn(),
  API_BASE_URL: 'http://localhost:8000/api/v1',
}));

const { apiFetch } = await import('@/lib/api');

beforeEach(() => {
  vi.clearAllMocks();
  localStorageMock.clear();
});

const mockOCRResult: OCRResultResponse = {
  id: 'result-123',
  vehicle_id: 'vehicle-456',
  file_path: '/tmp/uploads/scan.png',
  file_name: 'scan.png',
  mime_type: 'image/png',
  status: 'completed',
  raw_text: '{"brand": "Mercedes"}',
  structured_data: {
    brand: 'Mercedes',
    model: 'Actros',
    vin: 'WDB9066351L123456',
    first_registration: '15.03.2020',
    mileage: 120000,
    power_kw: 350,
    fuel_type: 'Diesel',
  },
  confidence_score: 0.92,
  error_message: null,
  created_at: '2026-07-16T10:00:00Z',
  updated_at: '2026-07-16T10:01:00Z',
};

describe('OCRUpload', () => {
  it('renders drag-and-drop zone', () => {
    render(<OCRUpload />);
    expect(screen.getByTestId('ocr-dropzone')).toBeInTheDocument();
    expect(screen.getByText('Drag and drop scan image here')).toBeInTheDocument();
  });

  it('shows file input on click', () => {
    render(<OCRUpload />);
    const input = screen.getByTestId('ocr-file-input');
    expect(input).toHaveAttribute('type', 'file');
    expect(input).toHaveAttribute('accept', 'image/*');
  });

  it('shows error for non-image file', async () => {
    render(<OCRUpload />);
    const input = screen.getByTestId('ocr-file-input') as HTMLInputElement;

    const file = new File(['data'], 'doc.pdf', { type: 'application/pdf' });
    Object.defineProperty(input, 'files', { value: [file], writable: false });
    fireEvent.change(input);

    await waitFor(() => {
      expect(screen.getByTestId('ocr-upload-error')).toBeInTheDocument();
      expect(screen.getByText('Only image files are allowed')).toBeInTheDocument();
    });
  });

  it('uploads image file successfully', async () => {
    mockFetch.mockResolvedValueOnce({
      ok: true,
      json: async () => ({
        message: 'OCR processing queued',
        ocr_result_id: 'new-result-id',
        status: 'pending',
      }),
    });

    let uploadedId = '';
    render(<OCRUpload onUploadComplete={(id) => { uploadedId = id; }} />);
    const input = screen.getByTestId('ocr-file-input') as HTMLInputElement;

    const file = new File(['image-data'], 'scan.png', { type: 'image/png' });
    Object.defineProperty(input, 'files', { value: [file], writable: false });
    fireEvent.change(input);

    await waitFor(() => {
      expect(screen.getByTestId('ocr-upload-success')).toBeInTheDocument();
      expect(uploadedId).toBe('new-result-id');
    });

    expect(mockFetch).toHaveBeenCalledTimes(1);
    const call = mockFetch.mock.calls[0];
    expect(call[0]).toContain('/ocr/upload');
    expect(call[1].method).toBe('POST');
  });

  it('shows error on upload failure', async () => {
    mockFetch.mockResolvedValueOnce({
      ok: false,
      json: async () => ({
        error: { code: 'INVALID_MIME_TYPE', message: 'Invalid MIME type' },
      }),
    });

    render(<OCRUpload />);
    const input = screen.getByTestId('ocr-file-input') as HTMLInputElement;

    const file = new File(['image-data'], 'scan.png', { type: 'image/png' });
    Object.defineProperty(input, 'files', { value: [file], writable: false });
    fireEvent.change(input);

    await waitFor(() => {
      expect(screen.getByTestId('ocr-upload-error')).toBeInTheDocument();
      expect(screen.getByText('Invalid MIME type')).toBeInTheDocument();
    });
  });
});

describe('OCRResults', () => {
  it('renders results list', async () => {
    vi.mocked(apiFetch).mockResolvedValueOnce({
      items: [mockOCRResult],
      total: 1,
      page: 1,
      page_size: 20,
    });

    render(<OCRResults />);

    await waitFor(() => {
      expect(screen.getByText('scan.png')).toBeInTheDocument();
      expect(screen.getByText('completed')).toBeInTheDocument();
    });
  });

  it('shows loading state', async () => {
    vi.mocked(apiFetch).mockImplementationOnce(
      () => new Promise(resolve => setTimeout(() => resolve({
        items: [],
        total: 0,
        page: 1,
        page_size: 20,
      }), 100))
    );

    render(<OCRResults />);
    expect(screen.getByTestId('ocr-results-loading')).toBeInTheDocument();
  });

  it('shows error on fetch failure', async () => {
    vi.mocked(apiFetch).mockRejectedValueOnce({
      error: { code: 'UNKNOWN', message: 'Network error' },
    });

    render(<OCRResults />);

    await waitFor(() => {
      expect(screen.getByTestId('ocr-results-error')).toBeInTheDocument();
      expect(screen.getByText('Network error')).toBeInTheDocument();
    });
  });

  it('calls onSelectResult when row is clicked', async () => {
    vi.mocked(apiFetch).mockResolvedValueOnce({
      items: [mockOCRResult],
      total: 1,
      page: 1,
      page_size: 20,
    });

    const onSelect = vi.fn();
    render(<OCRResults onSelectResult={onSelect} />);

    await waitFor(() => {
      expect(screen.getByTestId('ocr-row-result-123')).toBeInTheDocument();
    });

    fireEvent.click(screen.getByTestId('ocr-row-result-123'));
    expect(onSelect).toHaveBeenCalledWith(mockOCRResult);
  });
});

describe('OCRDetail', () => {
  it('renders side-by-side original and extracted data', () => {
    render(<OCRDetail result={mockOCRResult} />);

    expect(screen.getByTestId('ocr-original-scan')).toBeInTheDocument();
    expect(screen.getByTestId('ocr-extracted-data')).toBeInTheDocument();
    expect(screen.getByTestId('ocr-field-brand')).toHaveTextContent('Mercedes');
    expect(screen.getByTestId('ocr-field-model')).toHaveTextContent('Actros');
    expect(screen.getByTestId('ocr-field-vin')).toHaveTextContent('WDB9066351L123456');
    expect(screen.getByTestId('ocr-field-mileage')).toHaveTextContent('120000 km');
    expect(screen.getByTestId('ocr-field-power_kw')).toHaveTextContent('350 kW');
    expect(screen.getByTestId('ocr-field-fuel_type')).toHaveTextContent('Diesel');
  });

  it('shows apply button when status is completed and vehicle linked', () => {
    render(<OCRDetail result={mockOCRResult} />);
    expect(screen.getByTestId('ocr-apply-button')).toBeInTheDocument();
  });

  it('hides apply button when no vehicle linked', () => {
    const noVehicleResult = { ...mockOCRResult, vehicle_id: null };
    render(<OCRDetail result={noVehicleResult} />);
    expect(screen.queryByTestId('ocr-apply-button')).not.toBeInTheDocument();
  });

  it('hides apply button when status is pending', () => {
    const pendingResult = { ...mockOCRResult, status: 'pending' as const };
    render(<OCRDetail result={pendingResult} />);
    expect(screen.queryByTestId('ocr-apply-button')).not.toBeInTheDocument();
  });

  it('sends POST apply request on button click', async () => {
    vi.mocked(apiFetch).mockResolvedValueOnce({
      message: 'OCR data applied to vehicle',
      ocr_result_id: 'result-123',
      vehicle_id: 'vehicle-456',
      updated_fields: ['make', 'model', 'mileage_km'],
    });

    render(<OCRDetail result={mockOCRResult} />);
    fireEvent.click(screen.getByTestId('ocr-apply-button'));

    await waitFor(() => {
      expect(screen.getByTestId('ocr-apply-success')).toBeInTheDocument();
      expect(screen.getByText(/Applied 3 fields/)).toBeInTheDocument();
    });

    expect(apiFetch).toHaveBeenCalledWith('/ocr/results/result-123/apply', {
      method: 'POST',
    });
  });

  it('shows error on apply failure', async () => {
    vi.mocked(apiFetch).mockRejectedValueOnce({
      error: { code: 'APPLY_FAILED', message: 'Vehicle not found' },
    });

    render(<OCRDetail result={mockOCRResult} />);
    fireEvent.click(screen.getByTestId('ocr-apply-button'));

    await waitFor(() => {
      expect(screen.getByTestId('ocr-apply-error')).toBeInTheDocument();
      expect(screen.getByText('Vehicle not found')).toBeInTheDocument();
    });
  });

  it('shows raw text when available', () => {
    render(<OCRDetail result={mockOCRResult} />);
    expect(screen.getByTestId('ocr-raw-text')).toBeInTheDocument();
    expect(screen.getByTestId('ocr-raw-text')).toHaveTextContent('Mercedes');
  });

  it('shows error message when status is failed', () => {
    const failedResult: OCRResultResponse = {
      ...mockOCRResult,
      status: 'failed',
      error_message: 'OpenRouter API unavailable',
      structured_data: null,
    };
    render(<OCRDetail result={failedResult} />);
    expect(screen.getByText(/OpenRouter API unavailable/)).toBeInTheDocument();
  });

  it('shows no data message when structured_data is null', () => {
    const noDataResult: OCRResultResponse = {
      ...mockOCRResult,
      structured_data: null,
    };
    render(<OCRDetail result={noDataResult} />);
    expect(screen.getByText('No structured data available yet.')).toBeInTheDocument();
  });
});
