/**
 * @vitest-environment jsdom
 */
import React from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { render, screen, waitFor, fireEvent, cleanup } from '@testing-library/react';
import type { IProjectMaterial, IServicePrice } from '@alga-psa/types';
import type { CatalogPickerItem } from '../src/actions/materialCatalogActions';
import { formatCurrencyFromMinorUnits } from '@alga-psa/core';

let mockMaterials: IProjectMaterial[] = [];
let mockProducts: CatalogPickerItem[] = [];
let mockPrices: IServicePrice[] = [];

vi.mock('react-hot-toast', () => ({
  toast: {
    success: vi.fn(),
    error: vi.fn(),
  },
}));

vi.mock('@alga-psa/ui/components/AsyncSearchableSelect', () => {
  // Minimal mock: renders a select that calls loadOptions on mount and on search,
  // then exposes the options for selection.
  const React = require('react');
  function MockAsyncSearchableSelect({ value, onChange, loadOptions, placeholder, selectedLabel, limit }: any) {
    const [options, setOptions] = React.useState<any[]>([]);
    React.useEffect(() => {
      loadOptions({ search: '', page: 1, limit: limit ?? 10 }).then((result: any) => {
        setOptions(result.options);
      });
    }, [loadOptions, limit]);
    return (
      <select
        data-testid="async-searchable-select"
        value={value}
        onChange={(event: any) => {
          const opt = options.find((o: any) => o.value === event.target.value);
          onChange(event.target.value, opt);
        }}
      >
        <option value="">{placeholder}</option>
        {options.map((option: any) => (
          <option key={option.value} value={option.value}>
            {option.label}
          </option>
        ))}
      </select>
    );
  }
  return {
    default: MockAsyncSearchableSelect,
    __esModule: true,
  };
});

vi.mock('@alga-psa/ui/components/CustomSelect', () => ({
  default: ({ options, value, onValueChange, placeholder, id }: any) => (
    <select
      data-testid={id || 'custom-select'}
      value={value}
      onChange={(event) => onValueChange(event.target.value)}
    >
      <option value="">{placeholder}</option>
      {options.map((option: any) => (
        <option key={option.value} value={option.value}>
          {option.label}
        </option>
      ))}
    </select>
  ),
}));

vi.mock('@alga-psa/ui/ui-reflection/useAutomationIdAndRegister', () => ({
  useAutomationIdAndRegister: (_config: any, _actions: any, dataAutomationId?: string) => ({
    automationIdProps: dataAutomationId ? { 'data-automation-id': dataAutomationId } : {},
    updateMetadata: vi.fn(),
  }),
}));

vi.mock('../src/actions/materialCatalogActions', () => ({
  listProjectMaterials: vi.fn(async () => mockMaterials),
  searchServiceCatalogForPicker: vi.fn(async () => ({ items: mockProducts, totalCount: mockProducts.length })),
  getServicePrices: vi.fn(async () => mockPrices),
  addProjectMaterial: vi.fn(async () => undefined),
  deleteProjectMaterial: vi.fn(async () => undefined),
}));

describe('ProjectMaterialsDrawer', () => {
  beforeEach(async () => {
    cleanup();
    mockMaterials = [];
    mockProducts = [];
    mockPrices = [];

    const actions = await import('../src/actions/materialCatalogActions');
    vi.mocked(actions.listProjectMaterials).mockClear();
    vi.mocked(actions.listProjectMaterials).mockImplementation(async () => mockMaterials);
    vi.mocked(actions.searchServiceCatalogForPicker).mockClear();
    vi.mocked(actions.searchServiceCatalogForPicker).mockImplementation(async () => ({ items: mockProducts, totalCount: mockProducts.length }));
    vi.mocked(actions.getServicePrices).mockClear();
    vi.mocked(actions.getServicePrices).mockImplementation(async () => mockPrices);
    vi.mocked(actions.addProjectMaterial).mockClear();
    vi.mocked(actions.deleteProjectMaterial).mockClear();

    const toast = await import('react-hot-toast');
    vi.mocked(toast.toast.error).mockClear();
    vi.mocked(toast.toast.success).mockClear();
  });

  it('shows loading state while materials are fetched (T003)', async () => {
    const actions = await import('../src/actions/materialCatalogActions');
    let resolveMaterials: (value: IProjectMaterial[]) => void = () => undefined;
    const pending = new Promise<IProjectMaterial[]>((resolve) => {
      resolveMaterials = resolve;
    });

    vi.mocked(actions.listProjectMaterials).mockReturnValueOnce(pending);

    const ProjectMaterialsDrawer = (await import('../src/components/ProjectMaterialsDrawer')).default;
    render(<ProjectMaterialsDrawer projectId="project-1" clientId="client-1" />);

    expect(screen.getByText('Loading materials...')).toBeInTheDocument();

    resolveMaterials([]);
    await waitFor(() => {
      expect(screen.getByText('No materials added to this project.')).toBeInTheDocument();
    });
  });

  it('shows empty state when no materials exist (T004)', async () => {
    mockMaterials = [];

    const ProjectMaterialsDrawer = (await import('../src/components/ProjectMaterialsDrawer')).default;
    render(<ProjectMaterialsDrawer projectId="project-1" clientId="client-1" />);

    expect(await screen.findByText('No materials added to this project.')).toBeInTheDocument();
  });

  it('renders table columns and material data (T005)', async () => {
    mockMaterials = [
      {
        project_material_id: 'material-1',
        project_id: 'project-1',
        client_id: 'client-1',
        service_id: 'service-1',
        service_name: 'Widget',
        sku: 'W-100',
        quantity: 2,
        rate: 5000,
        currency_code: 'USD',
        description: null,
        is_billed: false,
      } as IProjectMaterial,
    ];

    const ProjectMaterialsDrawer = (await import('../src/components/ProjectMaterialsDrawer')).default;
    render(<ProjectMaterialsDrawer projectId="project-1" clientId="client-1" />);

    expect(await screen.findByText('Product')).toBeInTheDocument();
    expect(screen.getByText('Qty')).toBeInTheDocument();
    expect(screen.getByText('Rate')).toBeInTheDocument();
    expect(screen.getByText('Total')).toBeInTheDocument();
    expect(screen.getByText('Status')).toBeInTheDocument();

    expect(screen.getByText('Widget')).toBeInTheDocument();
    expect(screen.getByText('(W-100)')).toBeInTheDocument();
    expect(screen.getByText('2')).toBeInTheDocument();
    expect(screen.getByText(formatCurrencyFromMinorUnits(5000, 'en-US', 'USD'))).toBeInTheDocument();
    expect(screen.getAllByText(formatCurrencyFromMinorUnits(10000, 'en-US', 'USD'))[0]).toBeInTheDocument();
  });

  it('shows Pending and Billed badges based on billing state (T006)', async () => {
    mockMaterials = [
      {
        project_material_id: 'material-1',
        project_id: 'project-1',
        client_id: 'client-1',
        service_id: 'service-1',
        service_name: 'Widget',
        sku: null,
        quantity: 1,
        rate: 2500,
        currency_code: 'USD',
        description: null,
        is_billed: false,
      } as IProjectMaterial,
      {
        project_material_id: 'material-2',
        project_id: 'project-1',
        client_id: 'client-1',
        service_id: 'service-2',
        service_name: 'Gadget',
        sku: null,
        quantity: 1,
        rate: 3500,
        currency_code: 'USD',
        description: null,
        is_billed: true,
      } as IProjectMaterial,
    ];

    const ProjectMaterialsDrawer = (await import('../src/components/ProjectMaterialsDrawer')).default;
    render(<ProjectMaterialsDrawer projectId="project-1" clientId="client-1" />);

    expect(await screen.findByText('Pending')).toBeInTheDocument();
    expect(screen.getByText('Billed')).toBeInTheDocument();
  });

  it('formats currency values from minor units (T007)', async () => {
    mockMaterials = [
      {
        project_material_id: 'material-1',
        project_id: 'project-1',
        client_id: 'client-1',
        service_id: 'service-1',
        service_name: 'Widget',
        sku: null,
        quantity: 3,
        rate: 1234,
        currency_code: 'EUR',
        description: null,
        is_billed: false,
      } as IProjectMaterial,
    ];

    const ProjectMaterialsDrawer = (await import('../src/components/ProjectMaterialsDrawer')).default;
    render(<ProjectMaterialsDrawer projectId="project-1" clientId="client-1" />);

    expect(await screen.findByText(formatCurrencyFromMinorUnits(1234, 'en-US', 'EUR'))).toBeInTheDocument();
    expect(screen.getAllByText(formatCurrencyFromMinorUnits(3702, 'en-US', 'EUR'))[0]).toBeInTheDocument();
  });

  it('groups unbilled totals by currency (T008)', async () => {
    mockMaterials = [
      {
        project_material_id: 'material-1',
        project_id: 'project-1',
        client_id: 'client-1',
        service_id: 'service-1',
        service_name: 'Widget',
        sku: null,
        quantity: 2,
        rate: 5000,
        currency_code: 'USD',
        description: null,
        is_billed: false,
      } as IProjectMaterial,
      {
        project_material_id: 'material-2',
        project_id: 'project-1',
        client_id: 'client-1',
        service_id: 'service-2',
        service_name: 'Gadget',
        sku: null,
        quantity: 1,
        rate: 1000,
        currency_code: 'EUR',
        description: null,
        is_billed: false,
      } as IProjectMaterial,
      {
        project_material_id: 'material-3',
        project_id: 'project-1',
        client_id: 'client-1',
        service_id: 'service-3',
        service_name: 'Billed Item',
        sku: null,
        quantity: 1,
        rate: 999,
        currency_code: 'USD',
        description: null,
        is_billed: true,
      } as IProjectMaterial,
    ];

    const ProjectMaterialsDrawer = (await import('../src/components/ProjectMaterialsDrawer')).default;
    render(<ProjectMaterialsDrawer projectId="project-1" clientId="client-1" />);

    expect(await screen.findByText('Unbilled (USD):')).toBeInTheDocument();
    expect(screen.getByText('Unbilled (EUR):')).toBeInTheDocument();
    expect(screen.getAllByText(formatCurrencyFromMinorUnits(10000, 'en-US', 'USD'))[0]).toBeInTheDocument();
    expect(screen.getAllByText(formatCurrencyFromMinorUnits(1000, 'en-US', 'EUR'))[0]).toBeInTheDocument();
  });

  it('loads product options for the dropdown (T009)', async () => {
    mockProducts = [
      { service_id: 'service-1', service_name: 'Widget', sku: 'W-1' } as CatalogPickerItem,
      { service_id: 'service-2', service_name: 'Gadget', sku: null } as CatalogPickerItem,
    ];

    const actions = await import('../src/actions/materialCatalogActions');
    const ProjectMaterialsDrawer = (await import('../src/components/ProjectMaterialsDrawer')).default;
    render(<ProjectMaterialsDrawer projectId="project-1" clientId="client-1" />);

    await screen.findByText('Materials');
    await screen.findByRole('button', { name: 'Add' }).then((button) => button.click());

    expect(await screen.findByText('Widget (W-1)')).toBeInTheDocument();
    expect(screen.getByText('Gadget')).toBeInTheDocument();
    expect(actions.searchServiceCatalogForPicker).toHaveBeenCalledWith({
      search: '',
      page: 1,
      limit: 10,
      item_kinds: ['product'],
      is_active: true,
    });
  });

  it('shows price selector options after product selection (T010)', async () => {
    mockProducts = [
      { service_id: 'service-1', service_name: 'Widget', sku: 'W-1' } as CatalogPickerItem,
    ];
    mockPrices = [
      { service_id: 'service-1', currency_code: 'USD', rate: 1000 } as IServicePrice,
      { service_id: 'service-1', currency_code: 'EUR', rate: 900 } as IServicePrice,
    ];

    const ProjectMaterialsDrawer = (await import('../src/components/ProjectMaterialsDrawer')).default;
    render(<ProjectMaterialsDrawer projectId="project-1" clientId="client-1" />);

    const addButton = await screen.findByRole('button', { name: 'Add' });
    addButton.click();

    const productSelect = await screen.findByTestId('async-searchable-select');
    fireEvent.change(productSelect, { target: { value: 'service-1' } });

    const usdLabel = `USD - ${formatCurrencyFromMinorUnits(1000, 'en-US', 'USD')}`;
    const eurLabel = `EUR - ${formatCurrencyFromMinorUnits(900, 'en-US', 'EUR')}`;
    expect(await screen.findByText(usdLabel)).toBeInTheDocument();
    expect(screen.getByText(eurLabel)).toBeInTheDocument();
  });

  it('defaults quantity to 1 and prevents values below 1 (T011)', async () => {
    const ProjectMaterialsDrawer = (await import('../src/components/ProjectMaterialsDrawer')).default;
    render(<ProjectMaterialsDrawer projectId="project-1" clientId="client-1" />);

    const addButton = await screen.findByRole('button', { name: 'Add' });
    addButton.click();

    const quantityInput = await screen.findByRole('spinbutton');
    expect(quantityInput).toHaveValue(1);

    fireEvent.change(quantityInput, { target: { value: '0' } });
    expect(quantityInput).toHaveValue(1);
  });

  it('updates total when quantity or currency changes (T012)', async () => {
    mockProducts = [
      { service_id: 'service-1', service_name: 'Widget', sku: null } as CatalogPickerItem,
    ];
    mockPrices = [
      { service_id: 'service-1', currency_code: 'USD', rate: 1000 } as IServicePrice,
      { service_id: 'service-1', currency_code: 'EUR', rate: 2000 } as IServicePrice,
    ];

    const ProjectMaterialsDrawer = (await import('../src/components/ProjectMaterialsDrawer')).default;
    render(<ProjectMaterialsDrawer projectId="project-1" clientId="client-1" />);

    const addButton = await screen.findByRole('button', { name: 'Add' });
    addButton.click();

    const productSelect = await screen.findByTestId('async-searchable-select');
    fireEvent.change(productSelect, { target: { value: 'service-1' } });

    const initialTotal = formatCurrencyFromMinorUnits(1000, 'en-US', 'USD');
    expect(await screen.findByText(initialTotal)).toBeInTheDocument();

    const quantityInput = await screen.findByRole('spinbutton');
    fireEvent.change(quantityInput, { target: { value: '2' } });

    const updatedTotal = formatCurrencyFromMinorUnits(2000, 'en-US', 'USD');
    expect(await screen.findByText(updatedTotal)).toBeInTheDocument();

    const currencySelect = await screen.findByTestId('project-materials-currency-select');
    fireEvent.change(currencySelect, { target: { value: 'EUR' } });

    const eurTotal = formatCurrencyFromMinorUnits(4000, 'en-US', 'EUR');
    expect(await screen.findByText(eurTotal)).toBeInTheDocument();
  });

  it('adds material and refreshes the list (T013)', async () => {
    mockProducts = [
      { service_id: 'service-1', service_name: 'Widget', sku: null } as CatalogPickerItem,
    ];
    mockPrices = [
      { service_id: 'service-1', currency_code: 'USD', rate: 1500 } as IServicePrice,
    ];

    const actions = await import('../src/actions/materialCatalogActions');
    const ProjectMaterialsDrawer = (await import('../src/components/ProjectMaterialsDrawer')).default;
    render(<ProjectMaterialsDrawer projectId="project-1" clientId="client-1" />);

    const addButton = await screen.findByRole('button', { name: 'Add' });
    addButton.click();

    const productSelect = await screen.findByTestId('async-searchable-select');
    fireEvent.change(productSelect, { target: { value: 'service-1' } });

    const quantityInput = await screen.findByRole('spinbutton');
    fireEvent.change(quantityInput, { target: { value: '2' } });

    const descriptionInput = await screen.findByPlaceholderText('Additional notes...');
    fireEvent.change(descriptionInput, { target: { value: 'Install notes' } });

    const submitButton = await screen.findByRole('button', { name: 'Add Material' });
    submitButton.click();

    await waitFor(() => {
      expect(actions.addProjectMaterial).toHaveBeenCalledWith({
        project_id: 'project-1',
        client_id: 'client-1',
        service_id: 'service-1',
        quantity: 2,
        rate: 1500,
        currency_code: 'USD',
        description: 'Install notes',
      });
    });

    await waitFor(() => {
      expect(actions.listProjectMaterials).toHaveBeenCalledTimes(2);
    });
  });

  it('shows validation errors for missing product or price (T014)', async () => {
    mockProducts = [
      { service_id: 'service-1', service_name: 'Widget', sku: null } as CatalogPickerItem,
    ];
    mockPrices = [];

    const toast = await import('react-hot-toast');
    const ProjectMaterialsDrawer = (await import('../src/components/ProjectMaterialsDrawer')).default;
    render(<ProjectMaterialsDrawer projectId="project-1" clientId="client-1" />);

    const addButton = await screen.findByRole('button', { name: 'Add' });
    addButton.click();

    const submitButton = await screen.findByRole('button', { name: 'Add Material' });
    submitButton.click();

    expect(toast.toast.error).toHaveBeenCalledWith('Please select a product');

    const productSelect = await screen.findByTestId('async-searchable-select');
    fireEvent.change(productSelect, { target: { value: 'service-1' } });

    submitButton.click();

    expect(toast.toast.error).toHaveBeenCalledWith('Please select a currency');
  });

  it('only shows delete button for unbilled materials (T015)', async () => {
    mockMaterials = [
      {
        project_material_id: 'material-1',
        project_id: 'project-1',
        client_id: 'client-1',
        service_id: 'service-1',
        service_name: 'Widget',
        sku: null,
        quantity: 1,
        rate: 1000,
        currency_code: 'USD',
        description: null,
        is_billed: false,
      } as IProjectMaterial,
      {
        project_material_id: 'material-2',
        project_id: 'project-1',
        client_id: 'client-1',
        service_id: 'service-2',
        service_name: 'Gadget',
        sku: null,
        quantity: 1,
        rate: 2000,
        currency_code: 'USD',
        description: null,
        is_billed: true,
      } as IProjectMaterial,
    ];

    const ProjectMaterialsDrawer = (await import('../src/components/ProjectMaterialsDrawer')).default;
    const { container } = render(<ProjectMaterialsDrawer projectId="project-1" clientId="client-1" />);

    await screen.findByText('Widget');

    expect(
      container.querySelector('[data-automation-id="project-materials-drawer-delete-material-1"]')
    ).toBeInTheDocument();
    expect(
      container.querySelector('[data-automation-id="project-materials-drawer-delete-material-2"]')
    ).toBeNull();
  });

  it('deletes material and refreshes the list (T016)', async () => {
    mockMaterials = [
      {
        project_material_id: 'material-1',
        project_id: 'project-1',
        client_id: 'client-1',
        service_id: 'service-1',
        service_name: 'Widget',
        sku: null,
        quantity: 1,
        rate: 1000,
        currency_code: 'USD',
        description: null,
        is_billed: false,
      } as IProjectMaterial,
    ];

    const actions = await import('../src/actions/materialCatalogActions');
    const ProjectMaterialsDrawer = (await import('../src/components/ProjectMaterialsDrawer')).default;
    const { container } = render(<ProjectMaterialsDrawer projectId="project-1" clientId="client-1" />);

    await screen.findByText('Widget');

    const deleteButton = container.querySelector(
      '[data-automation-id="project-materials-drawer-delete-material-1"]'
    ) as HTMLButtonElement;
    deleteButton.click();

    await waitFor(() => {
      expect(actions.deleteProjectMaterial).toHaveBeenCalledWith('material-1');
    });

    await waitFor(() => {
      expect(actions.listProjectMaterials).toHaveBeenCalledTimes(2);
    });
  });

  it('shows no-client warning and hides add button (T017)', async () => {
    const ProjectMaterialsDrawer = (await import('../src/components/ProjectMaterialsDrawer')).default;
    render(<ProjectMaterialsDrawer projectId="project-1" />);

    expect(
      await screen.findByText('A client must be assigned to this project before materials can be added.')
    ).toBeInTheDocument();
    expect(screen.queryByRole('button', { name: 'Add' })).toBeNull();
  });

  it('includes data automation ids on interactive elements (T018)', async () => {
    mockMaterials = [
      {
        project_material_id: 'material-1',
        project_id: 'project-1',
        client_id: 'client-1',
        service_id: 'service-1',
        service_name: 'Widget',
        sku: null,
        quantity: 1,
        rate: 1000,
        currency_code: 'USD',
        description: null,
        is_billed: false,
      } as IProjectMaterial,
    ];

    const ProjectMaterialsDrawer = (await import('../src/components/ProjectMaterialsDrawer')).default;
    const { container } = render(<ProjectMaterialsDrawer projectId="project-1" clientId="client-1" />);

    expect(container.querySelector('[data-automation-id="project-materials-drawer"]')).toBeInTheDocument();

    const addButton = await screen.findByRole('button', { name: 'Add' });
    expect(addButton).toHaveAttribute('data-automation-id', 'project-materials-drawer-add-btn');
    addButton.click();
    await screen.findByText('Product');

    expect(
      container.querySelector('[data-automation-id="project-materials-drawer-quantity"]')
    ).toBeInTheDocument();
    expect(
      container.querySelector('[data-automation-id="project-materials-drawer-description"]')
    ).toBeInTheDocument();
    expect(
      container.querySelector('[data-automation-id="project-materials-drawer-cancel-add-btn"]')
    ).toBeInTheDocument();
    expect(
      container.querySelector('[data-automation-id="project-materials-drawer-save-add-btn"]')
    ).toBeInTheDocument();
    expect(
      container.querySelector('[data-automation-id="project-materials-drawer-delete-material-1"]')
    ).toBeInTheDocument();
  });
});
