// FE-PLANNER-TRANSMODAL-001 to FE-PLANNER-TRANSMODAL-021
import { render, screen, waitFor, fireEvent } from '../../../tests/helpers/render';
import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
import { server } from '../../../tests/helpers/msw/server';
import { useAuthStore } from '../../store/authStore';
import { useTripStore } from '../../store/tripStore';
import { useAddonStore } from '../../store/addonStore';
import { resetAllStores, seedStore } from '../../../tests/helpers/store';
import {
  buildUser,
  buildTrip,
  buildDay,
  buildReservation,
  buildTripFile,
} from '../../../tests/helpers/factories';
import { TransportModal } from './TransportModal';

vi.mock('react-router-dom', async (importActual) => {
  const actual = await importActual<typeof import('react-router-dom')>();
  return { ...actual, useParams: () => ({ id: '1' }) };
});

vi.mock('../shared/CustomTimePicker', () => ({
  default: ({ value, onChange }: { value: string; onChange: (v: string) => void }) => (
    <input data-testid="time-picker" type="text" value={value} onChange={e => onChange(e.target.value)} />
  ),
}));

vi.mock('./AirportSelect', () => ({
  default: ({ onChange }: { onChange: (a: any) => void }) => (
    <input data-testid="airport-select" type="text" onChange={e => onChange({ iata: e.target.value, name: e.target.value, city: '', country: '', lat: 0, lng: 0, tz: 'UTC', icao: null })} />
  ),
}));

vi.mock('./LocationSelect', () => ({
  default: ({ onChange }: { onChange: (l: any) => void }) => (
    <input data-testid="location-select" type="text" onChange={e => onChange({ name: e.target.value, lat: 0, lng: 0, address: null })} />
  ),
}));

const defaultProps = {
  isOpen: true,
  onClose: vi.fn(),
  onSave: vi.fn().mockResolvedValue(undefined),
  reservation: null,
  days: [],
  selectedDayId: null,
  files: [],
  onFileUpload: vi.fn().mockResolvedValue(undefined),
  onFileDelete: vi.fn().mockResolvedValue(undefined),
};

beforeEach(() => {
  resetAllStores();
  seedStore(useAuthStore, { user: buildUser(), isAuthenticated: true });
  seedStore(useTripStore, { trip: buildTrip({ id: 1 }), budgetItems: [] });
  vi.clearAllMocks();
});

describe('TransportModal', () => {
  // ── Rendering ──────────────────────────────────────────────────────────────

  it('FE-PLANNER-TRANSMODAL-001: renders without crashing', () => {
    render(<TransportModal {...defaultProps} />);
    expect(document.body).toBeInTheDocument();
  });

  it('FE-PLANNER-TRANSMODAL-002: shows "Add transport" title for new transport', () => {
    render(<TransportModal {...defaultProps} reservation={null} />);
    expect(screen.getByText(/Add transport/i)).toBeInTheDocument();
  });

  it('FE-PLANNER-TRANSMODAL-003: shows "Edit transport" title when editing', () => {
    const res = buildReservation({ title: 'Paris Flight', type: 'flight' });
    render(<TransportModal {...defaultProps} reservation={res} />);
    expect(screen.getByText(/Edit transport/i)).toBeInTheDocument();
  });

  it('FE-PLANNER-TRANSMODAL-004: title input is required — onSave not called with empty title', async () => {
    const onSave = vi.fn().mockResolvedValue(undefined);
    render(<TransportModal {...defaultProps} onSave={onSave} />);
    await userEvent.click(screen.getByRole('button', { name: /^Add$/i }));
    expect(onSave).not.toHaveBeenCalled();
  });

  it('FE-PLANNER-TRANSMODAL-005: all 4 transport type buttons are visible', () => {
    render(<TransportModal {...defaultProps} />);
    expect(screen.getByRole('button', { name: /^Flight$/i })).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /^Train$/i })).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /^Car$/i })).toBeInTheDocument();
    expect(screen.getByRole('button', { name: /^Cruise$/i })).toBeInTheDocument();
  });

  it('FE-PLANNER-TRANSMODAL-006: editing pre-fills title', () => {
    const res = buildReservation({ title: 'LH123 Frankfurt', type: 'flight' });
    render(<TransportModal {...defaultProps} reservation={res} />);
    expect(screen.getByDisplayValue('LH123 Frankfurt')).toBeInTheDocument();
  });

  it('FE-PLANNER-TRANSMODAL-007: edit mode save button shows "Update"', () => {
    const res = buildReservation({ title: 'My Train', type: 'train' });
    render(<TransportModal {...defaultProps} reservation={res} />);
    expect(screen.getByRole('button', { name: /^Update$/i })).toBeInTheDocument();
  });

  it('FE-PLANNER-TRANSMODAL-008: Cancel button calls onClose', async () => {
    const onClose = vi.fn();
    render(<TransportModal {...defaultProps} onClose={onClose} />);
    await userEvent.click(screen.getByRole('button', { name: /Cancel/i }));
    expect(onClose).toHaveBeenCalled();
  });

  it('FE-PLANNER-TRANSMODAL-009: submitting valid flight calls onSave with correct type', async () => {
    const onSave = vi.fn().mockResolvedValue(undefined);
    render(<TransportModal {...defaultProps} onSave={onSave} />);
    await userEvent.type(screen.getByPlaceholderText(/e\.g\. Lufthansa/i), 'LH456');
    await userEvent.click(screen.getByRole('button', { name: /^Add$/i }));
    await waitFor(() => expect(onSave).toHaveBeenCalled());
    expect(onSave).toHaveBeenCalledWith(expect.objectContaining({ title: 'LH456', type: 'flight' }));
  });

  it('FE-PLANNER-TRANSMODAL-010: switching to train type calls onSave with train type', async () => {
    const onSave = vi.fn().mockResolvedValue(undefined);
    render(<TransportModal {...defaultProps} onSave={onSave} />);
    await userEvent.click(screen.getByRole('button', { name: /^Train$/i }));
    await userEvent.type(screen.getByPlaceholderText(/e\.g\. Lufthansa/i), 'Eurostar');
    await userEvent.click(screen.getByRole('button', { name: /^Add$/i }));
    await waitFor(() => expect(onSave).toHaveBeenCalled());
    expect(onSave).toHaveBeenCalledWith(expect.objectContaining({ type: 'train' }));
  });

  // ── Budget addon ─────────────────────────────────────────────────────────────

  it('FE-PLANNER-TRANSMODAL-011: costs section (create expense) visible when budget addon is enabled', () => {
    seedStore(useAddonStore, {
      addons: [{ id: 'budget', name: 'Budget', type: 'budget', icon: '', enabled: true }],
      loaded: true,
    });
    render(<TransportModal {...defaultProps} />);
    expect(screen.getByRole('button', { name: /Create expense/i })).toBeInTheDocument();
  });

  it('FE-PLANNER-TRANSMODAL-012: costs section not shown when budget addon is disabled', () => {
    render(<TransportModal {...defaultProps} />);
    expect(screen.queryByRole('button', { name: /Create expense/i })).not.toBeInTheDocument();
  });

  it('FE-PLANNER-TRANSMODAL-013: create-expense saves the booking (no create_budget_entry) then opens the Costs editor', async () => {
    seedStore(useAddonStore, {
      addons: [{ id: 'budget', name: 'Budget', type: 'budget', icon: '', enabled: true }],
      loaded: true,
    });
    const onSave = vi.fn().mockResolvedValue({ id: 42 });
    const onOpenExpense = vi.fn();
    render(<TransportModal {...defaultProps} onSave={onSave} onOpenExpense={onOpenExpense} />);
    await userEvent.type(screen.getByPlaceholderText(/e\.g\. Lufthansa/i), 'ICE Train');
    await userEvent.click(screen.getByRole('button', { name: /Create expense/i }));
    await waitFor(() => expect(onSave).toHaveBeenCalled());
    // The legacy auto-budget mechanism is gone; the expense is created via the editor instead.
    expect(onSave).not.toHaveBeenCalledWith(expect.objectContaining({ create_budget_entry: expect.anything() }));
    await waitFor(() =>
      expect(onOpenExpense).toHaveBeenCalledWith(
        expect.objectContaining({ prefill: expect.objectContaining({ reservationId: 42 }) })
      )
    );
  });

  // ── File attachment ───────────────────────────────────────────────────────────

  it('FE-PLANNER-TRANSMODAL-014: attach file button rendered when onFileUpload provided', () => {
    render(<TransportModal {...defaultProps} />);
    expect(screen.getByRole('button', { name: /Attach file/i })).toBeInTheDocument();
  });

  it('FE-PLANNER-TRANSMODAL-015: attach file button absent when onFileUpload is undefined', () => {
    render(<TransportModal {...defaultProps} onFileUpload={undefined} />);
    expect(screen.queryByRole('button', { name: /Attach file/i })).not.toBeInTheDocument();
  });

  it('FE-PLANNER-TRANSMODAL-016: attached files shown for existing transport', () => {
    const res = buildReservation({ id: 5, type: 'flight' });
    const file = buildTripFile({ id: 1, trip_id: 1, original_name: 'boarding-pass.pdf' });
    (file as any).reservation_id = 5;

    render(<TransportModal {...defaultProps} reservation={res} files={[file]} />);
    expect(screen.getByText('boarding-pass.pdf')).toBeInTheDocument();
  });

  it('FE-PLANNER-TRANSMODAL-017: pending file added for new transport on file input change', async () => {
    render(<TransportModal {...defaultProps} reservation={null} />);

    const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
    const testFile = new File(['content'], 'itinerary.pdf', { type: 'application/pdf' });
    fireEvent.change(fileInput, { target: { files: [testFile] } });

    await waitFor(() => expect(screen.getByText('itinerary.pdf')).toBeInTheDocument());
  });

  it('FE-PLANNER-TRANSMODAL-018: file upload to existing transport calls onFileUpload with correct FormData', async () => {
    const onFileUpload = vi.fn().mockResolvedValue(undefined);
    const res = buildReservation({ id: 10, type: 'train', title: 'Eurostar' });

    render(<TransportModal {...defaultProps} reservation={res} onFileUpload={onFileUpload} />);

    const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
    const testFile = new File(['content'], 'ticket.pdf', { type: 'application/pdf' });
    fireEvent.change(fileInput, { target: { files: [testFile] } });

    await waitFor(() => expect(onFileUpload).toHaveBeenCalled());
    const [fd] = onFileUpload.mock.calls[0] as [FormData];
    expect(fd.get('file')).toBeTruthy();
    expect(fd.get('reservation_id')).toBe('10');
  });

  it('FE-PLANNER-TRANSMODAL-019: link existing file button appears when unattached files exist', () => {
    const res = buildReservation({ id: 5, type: 'flight' });
    const unattachedFile = buildTripFile({ id: 99, original_name: 'invoice.pdf' });

    render(<TransportModal {...defaultProps} reservation={res} files={[unattachedFile]} />);
    expect(screen.getByRole('button', { name: /Link existing file/i })).toBeInTheDocument();
  });

  it('FE-PLANNER-TRANSMODAL-020: clicking "link existing file" shows file picker dropdown', async () => {
    const res = buildReservation({ id: 5, type: 'flight' });
    const unattachedFile = buildTripFile({ id: 99, original_name: 'invoice.pdf' });

    render(<TransportModal {...defaultProps} reservation={res} files={[unattachedFile]} />);
    await userEvent.click(screen.getByRole('button', { name: /Link existing file/i }));
    expect(screen.getByText('invoice.pdf')).toBeInTheDocument();
  });

  it('FE-PLANNER-TRANSMODAL-021: clicking file in picker links it and closes picker', async () => {
    server.use(
      http.post('/api/trips/1/files/99/link', () => HttpResponse.json({ success: true })),
      http.get('/api/trips/1/files', () => HttpResponse.json({ files: [] })),
    );

    const res = buildReservation({ id: 5, type: 'flight' });
    const unattachedFile = buildTripFile({ id: 99, original_name: 'invoice.pdf' });

    render(<TransportModal {...defaultProps} reservation={res} files={[unattachedFile]} />);
    await userEvent.click(screen.getByRole('button', { name: /Link existing file/i }));
    await userEvent.click(screen.getByText('invoice.pdf'));

    await waitFor(() => {
      expect(screen.queryByRole('button', { name: /Link existing file/i })).not.toBeInTheDocument();
    });
  });

  it('FE-PLANNER-TRANSMODAL-022: removing pending file removes it from list', async () => {
    render(<TransportModal {...defaultProps} reservation={null} />);

    const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
    const testFile = new File(['content'], 'draft.pdf', { type: 'application/pdf' });
    fireEvent.change(fileInput, { target: { files: [testFile] } });

    await waitFor(() => expect(screen.getByText('draft.pdf')).toBeInTheDocument());

    const pendingFileRow = screen.getByText('draft.pdf').closest('div')!;
    const removeBtn = pendingFileRow.querySelector('button')!;
    await userEvent.click(removeBtn);

    await waitFor(() => expect(screen.queryByText('draft.pdf')).not.toBeInTheDocument());
  });

  it('FE-PLANNER-TRANSMODAL-023: clicking attach file button triggers file input click', async () => {
    render(<TransportModal {...defaultProps} />);
    const attachBtn = screen.getByRole('button', { name: /Attach file/i });
    const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
    const clickSpy = vi.spyOn(fileInput, 'click').mockImplementation(() => {});
    await userEvent.click(attachBtn);
    expect(clickSpy).toHaveBeenCalled();
    clickSpy.mockRestore();
  });

  it('FE-PLANNER-TRANSMODAL-024: unlinking a linked file removes it from attached list', async () => {
    server.use(
      http.post('/api/trips/1/files/42/link', () => HttpResponse.json({ success: true })),
      http.get('/api/trips/1/files/42/links', () => HttpResponse.json({ links: [{ id: 1, reservation_id: 7 }] })),
      http.delete('/api/trips/1/files/42/link/1', () => HttpResponse.json({ success: true })),
      http.get('/api/trips/1/files', () => HttpResponse.json({ files: [] })),
    );

    const res = buildReservation({ id: 7, type: 'car' });
    const looseFile = buildTripFile({ id: 42, original_name: 'rental-agreement.pdf' });

    render(<TransportModal {...defaultProps} reservation={res} files={[looseFile]} />);

    await userEvent.click(screen.getByRole('button', { name: /Link existing file/i }));
    await waitFor(() => expect(screen.getByText('rental-agreement.pdf')).toBeInTheDocument());
    await userEvent.click(screen.getByText('rental-agreement.pdf'));

    await waitFor(() =>
      expect(screen.queryByRole('button', { name: /Link existing file/i })).not.toBeInTheDocument()
    );

    const fileRow = screen.getByText('rental-agreement.pdf').closest('div')!;
    const unlinkBtn = fileRow.querySelector('button[type="button"]')!;
    await userEvent.click(unlinkBtn);

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

  it('FE-PLANNER-TRANSMODAL-025: pending files flushed after saving new transport', async () => {
    const savedReservation = buildReservation({ id: 99, type: 'flight' });
    const onSave = vi.fn().mockResolvedValue(savedReservation);
    const onFileUpload = vi.fn().mockResolvedValue(undefined);

    render(<TransportModal {...defaultProps} onSave={onSave} onFileUpload={onFileUpload} reservation={null} />);

    const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
    const testFile = new File(['content'], 'boarding.pdf', { type: 'application/pdf' });
    fireEvent.change(fileInput, { target: { files: [testFile] } });
    await waitFor(() => expect(screen.getByText('boarding.pdf')).toBeInTheDocument());

    await userEvent.type(screen.getByPlaceholderText(/e\.g\. Lufthansa/i), 'LH001');
    await userEvent.click(screen.getByRole('button', { name: /^Add$/i }));

    await waitFor(() => expect(onFileUpload).toHaveBeenCalled());
    const [fd] = onFileUpload.mock.calls[0] as [FormData];
    expect(fd.get('reservation_id')).toBe('99');
    expect(fd.get('file')).toBeTruthy();
  });
});
