import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';

const { mockLookupBarcode } = vi.hoisted(() => ({
  mockLookupBarcode: vi.fn(),
}));

vi.mock('@/services/products', () => ({
  lookupBarcode: mockLookupBarcode,
}));

import { ProductModal } from '../../../../src/app/(dashboard)/products/ProductModal';
import { ProductCategory, ServingUnit } from '@meshitrack/shared';

beforeEach(() => vi.clearAllMocks());

const defaultProps = {
  open: true,
  onClose: vi.fn(),
  onSave: vi.fn().mockResolvedValue(undefined),
  householdId: 'hh1',
};

describe('ProductModal', () => {
  it('renders nothing when closed', () => {
    const { container } = render(<ProductModal {...defaultProps} open={false} />);
    expect(container.innerHTML).toBe('');
  });

  it('renders Add Product title by default', () => {
    render(<ProductModal {...defaultProps} />);
    expect(screen.getByText('Add Product')).toBeInTheDocument();
  });

  it('renders custom title when provided', () => {
    render(<ProductModal {...defaultProps} title="Custom Title" />);
    expect(screen.getByText('Custom Title')).toBeInTheDocument();
  });

  it('renders Edit Product title when initial is provided', () => {
    render(<ProductModal {...defaultProps} initial={{ name: 'Test' }} />);
    expect(screen.getByText('Edit Product')).toBeInTheDocument();
  });

  it('pre-fills form fields from initial', () => {
    render(
      <ProductModal
        {...defaultProps}
        initial={{
          name: 'Chicken',
          brand: 'Tyson',
          barcode: '12345678',
          category: ProductCategory.MEAT,
          servingSize: 100,
          servingUnit: ServingUnit.GRAMS,
          tags: ['organic', 'protein'],
          nutrition: { calories: 165, protein: 31, carbs: 0, fat: 3.6 },
        }}
      />,
    );
    expect(screen.getByDisplayValue('Chicken')).toBeInTheDocument();
    expect(screen.getByDisplayValue('Tyson')).toBeInTheDocument();
    expect(screen.getByDisplayValue('12345678')).toBeInTheDocument();
    expect(screen.getByDisplayValue('100')).toBeInTheDocument();
    expect(screen.getByDisplayValue('organic, protein')).toBeInTheDocument();
    expect(screen.getByDisplayValue('165')).toBeInTheDocument();
  });

  it('shows error when name is empty and form submitted', async () => {
    render(<ProductModal {...defaultProps} />);
    fireEvent.click(screen.getByText('Save'));
    expect(screen.getByText('Name is required')).toBeInTheDocument();
    expect(defaultProps.onSave).not.toHaveBeenCalled();
  });

  it('shows error when serving size is empty', async () => {
    render(<ProductModal {...defaultProps} />);
    fireEvent.change(screen.getByPlaceholderText('Product name'), {
      target: { value: 'Test' },
    });
    fireEvent.click(screen.getByText('Save'));
    expect(screen.getByText('Serving size must be a positive number')).toBeInTheDocument();
  });

  it('shows error when nutrition fields are missing', async () => {
    render(<ProductModal {...defaultProps} />);
    fireEvent.change(screen.getByPlaceholderText('Product name'), {
      target: { value: 'Test' },
    });
    fireEvent.change(screen.getByPlaceholderText('e.g. 100'), {
      target: { value: '100' },
    });
    fireEvent.click(screen.getByText('Save'));
    expect(
      screen.getByText('Calories, protein, carbs, and fat are required'),
    ).toBeInTheDocument();
  });

  it('calls onSave with correct data on valid submit', async () => {
    render(<ProductModal {...defaultProps} />);

    fireEvent.change(screen.getByPlaceholderText('Product name'), {
      target: { value: 'Chicken Breast' },
    });
    fireEvent.change(screen.getByPlaceholderText('e.g. 100'), {
      target: { value: '100' },
    });

    // Fill nutrition
    const numberInputs = screen.getAllByRole('spinbutton');
    // serving size is index 0, density=1, calories=2, protein=3, carbs=4, fat=5
    fireEvent.change(numberInputs[2]!, { target: { value: '165' } });
    fireEvent.change(numberInputs[3]!, { target: { value: '31' } });
    fireEvent.change(numberInputs[4]!, { target: { value: '0' } });
    fireEvent.change(numberInputs[5]!, { target: { value: '3.6' } });

    fireEvent.click(screen.getByText('Save'));

    await waitFor(() => {
      expect(defaultProps.onSave).toHaveBeenCalledWith(
        expect.objectContaining({
          name: 'Chicken Breast',
          servingSize: 100,
          nutrition: expect.objectContaining({
            calories: 165,
            protein: 31,
            carbs: 0,
            fat: 3.6,
          }),
        }),
      );
    });
  });

  it('closes modal after successful save', async () => {
    render(<ProductModal {...defaultProps} />);

    fireEvent.change(screen.getByPlaceholderText('Product name'), {
      target: { value: 'Test' },
    });
    fireEvent.change(screen.getByPlaceholderText('e.g. 100'), { target: { value: '50' } });

    const numberInputs = screen.getAllByRole('spinbutton');
    fireEvent.change(numberInputs[2]!, { target: { value: '10' } });
    fireEvent.change(numberInputs[3]!, { target: { value: '5' } });
    fireEvent.change(numberInputs[4]!, { target: { value: '2' } });
    fireEvent.change(numberInputs[5]!, { target: { value: '1' } });

    fireEvent.click(screen.getByText('Save'));

    await waitFor(() => {
      expect(defaultProps.onClose).toHaveBeenCalled();
    });
  });

  it('shows error when onSave rejects', async () => {
    const saveFn = vi.fn().mockRejectedValue(new Error('Server error'));
    render(<ProductModal {...defaultProps} onSave={saveFn} />);

    fireEvent.change(screen.getByPlaceholderText('Product name'), {
      target: { value: 'Test' },
    });
    fireEvent.change(screen.getByPlaceholderText('e.g. 100'), { target: { value: '50' } });

    const numberInputs = screen.getAllByRole('spinbutton');
    fireEvent.change(numberInputs[2]!, { target: { value: '10' } });
    fireEvent.change(numberInputs[3]!, { target: { value: '5' } });
    fireEvent.change(numberInputs[4]!, { target: { value: '2' } });
    fireEvent.change(numberInputs[5]!, { target: { value: '1' } });

    fireEvent.click(screen.getByText('Save'));

    await waitFor(() => {
      expect(screen.getByText('Server error')).toBeInTheDocument();
    });
  });

  it('shows fallback error when onSave throws non-Error', async () => {
    const saveFn = vi.fn().mockRejectedValue('unknown');
    render(<ProductModal {...defaultProps} onSave={saveFn} />);

    fireEvent.change(screen.getByPlaceholderText('Product name'), {
      target: { value: 'Test' },
    });
    fireEvent.change(screen.getByPlaceholderText('e.g. 100'), { target: { value: '50' } });

    const numberInputs = screen.getAllByRole('spinbutton');
    fireEvent.change(numberInputs[2]!, { target: { value: '10' } });
    fireEvent.change(numberInputs[3]!, { target: { value: '5' } });
    fireEvent.change(numberInputs[4]!, { target: { value: '2' } });
    fireEvent.change(numberInputs[5]!, { target: { value: '1' } });

    fireEvent.click(screen.getByText('Save'));

    await waitFor(() => {
      expect(screen.getByText('Failed to save')).toBeInTheDocument();
    });
  });

  it('closes when Cancel is clicked', () => {
    render(<ProductModal {...defaultProps} />);
    fireEvent.click(screen.getByText('Cancel'));
    expect(defaultProps.onClose).toHaveBeenCalled();
  });

  it('closes when backdrop is clicked', () => {
    render(<ProductModal {...defaultProps} />);
    // Click the backdrop (outermost div with onClick=onClose)
    const backdrop = screen.getByText('Add Product').closest('div')!.parentElement!;
    fireEvent.click(backdrop);
    expect(defaultProps.onClose).toHaveBeenCalled();
  });

  it('Lookup button is disabled when barcode is empty', () => {
    render(<ProductModal {...defaultProps} />);
    const lookupBtn = screen.getByText('Lookup');
    expect(lookupBtn).toBeDisabled();
  });

  it('barcode lookup fills form on success', async () => {
    mockLookupBarcode.mockResolvedValue({
      _id: 'p1',
      householdId: 'hh1',
      name: 'Nutella',
      brand: 'Ferrero',
      category: 'snacks',
      servingSize: 15,
      servingUnit: 'g',
      nutrition: { calories: 80, protein: 1, carbs: 8.5, fat: 4.7 },
      tags: ['spread'],
      source: 'barcode_lookup',
      createdBy: 'u1',
      createdAt: '2026-01-01T00:00:00Z',
      updatedAt: '2026-01-01T00:00:00Z',
    });

    render(<ProductModal {...defaultProps} />);

    fireEvent.change(screen.getByPlaceholderText('8-14 digits'), {
      target: { value: '3017620422003' },
    });
    fireEvent.click(screen.getByText('Lookup'));

    await waitFor(() => {
      expect(screen.getByDisplayValue('Nutella')).toBeInTheDocument();
    });
    expect(screen.getByDisplayValue('Ferrero')).toBeInTheDocument();
    expect(screen.getByDisplayValue('80')).toBeInTheDocument();
  });

  it('barcode lookup shows not found', async () => {
    mockLookupBarcode.mockResolvedValue({ found: false });

    render(<ProductModal {...defaultProps} />);

    fireEvent.change(screen.getByPlaceholderText('8-14 digits'), {
      target: { value: '0000000000000' },
    });
    fireEvent.click(screen.getByText('Lookup'));

    await waitFor(() => {
      expect(screen.getByText('Product not found for this barcode')).toBeInTheDocument();
    });
  });

  it('barcode lookup shows error on failure', async () => {
    mockLookupBarcode.mockRejectedValue(new Error('Network error'));

    render(<ProductModal {...defaultProps} />);

    fireEvent.change(screen.getByPlaceholderText('8-14 digits'), {
      target: { value: '1234567890123' },
    });
    fireEvent.click(screen.getByText('Lookup'));

    await waitFor(() => {
      expect(screen.getByText('Barcode lookup failed')).toBeInTheDocument();
    });
  });
});
