import React from 'react';
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
import ExperimentCalendar from '../src/components/ExperimentCalendar';
import { experimentsApi } from '../src/api/client';

jest.mock('../src/api/client', () => ({
  experimentsApi: {
    getCalendar: jest.fn(),
  },
}));

const EXP_ID = 'aaaaaaaa-0000-0000-0000-000000000001';

const TEMPLATE = [
  { fieldId: '00000000-0000-0000-0000-000000000002', key: 'vitals',  label: 'Vitals',   active: true,  builtin: true  },
  { fieldId: 'ww000000-0000-0000-0000-000000000099', key: 'weights', label: 'Weights',  active: true,  builtin: false },
  { fieldId: 'zz000000-0000-0000-0000-000000000001', key: 'notes',   label: 'Notes',    active: false, builtin: true  },
];

const ANIMALS = [
  { id: 'a1000000-0000-0000-0000-000000000001', animal_name: 'Rat A', animal_id_string: 'R001' },
  { id: 'a2000000-0000-0000-0000-000000000002', animal_name: 'Rat B', animal_id_string: 'R002' },
];

function todayYYYYMM() {
  const d = new Date();
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
}

function makeStatus(animalId, date, customFields = {}) {
  return {
    id: `s-${animalId}-${date}`,
    animal_id: animalId,
    date: `${date}T00:00:00.000Z`,
    experiment_description: null,
    vitals: null,
    treatment: null,
    notes: null,
    custom_fields: customFields,
  };
}

beforeEach(() => {
  jest.clearAllMocks();
  experimentsApi.getCalendar.mockResolvedValue({ field: 'ww000000-0000-0000-0000-000000000099', days: {} });
});

// ── Rendering ──────────────────────────────────────────────────────────────────

describe('ExperimentCalendar rendering', () => {
  it('renders the current month and year', async () => {
    render(
      <ExperimentCalendar
        experimentId={EXP_ID}
        template={TEMPLATE}
        allStatuses={[]}
        animals={ANIMALS}
      />,
    );
    const now = new Date();
    const monthLabel = now.toLocaleString('en-US', { month: 'long', year: 'numeric' });
    await waitFor(() => {
      expect(screen.getByText(monthLabel)).toBeInTheDocument();
    });
  });

  it('renders day-of-week headers', async () => {
    render(
      <ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} animals={ANIMALS} />,
    );
    for (const d of ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']) {
      expect(screen.getByText(d)).toBeInTheDocument();
    }
  });

  it('renders the field selector with active template fields only', async () => {
    render(
      <ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} animals={ANIMALS} />,
    );
    await waitFor(() => expect(screen.getByTestId('field-select')).toBeInTheDocument());
    const options = screen.getAllByRole('option');
    const labels = options.map((o) => o.textContent);
    expect(labels).toContain('Vitals');
    expect(labels).toContain('Weights');
    expect(labels).not.toContain('Notes'); // inactive
  });
});

// ── Default field selection ────────────────────────────────────────────────────

describe('default field selection', () => {
  it('defaults to the field whose label contains "weight"', async () => {
    render(
      <ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} animals={ANIMALS} />,
    );
    await waitFor(() => {
      expect(experimentsApi.getCalendar).toHaveBeenCalledWith(
        EXP_ID,
        'ww000000-0000-0000-0000-000000000099',
      );
    });
  });

  it('defaults to first field if none contains "weight"', async () => {
    const noWeightTemplate = [
      { fieldId: 'ff000000-0000-0000-0000-000000000001', key: 'treatment', label: 'Treatment', active: true, builtin: true },
      { fieldId: 'ff000000-0000-0000-0000-000000000002', key: 'vitals',    label: 'Vitals',    active: true, builtin: true },
    ];
    render(
      <ExperimentCalendar experimentId={EXP_ID} template={noWeightTemplate} allStatuses={[]} animals={ANIMALS} />,
    );
    await waitFor(() => {
      expect(experimentsApi.getCalendar).toHaveBeenCalledWith(EXP_ID, 'treatment');
    });
  });
});

// ── Count badges ──────────────────────────────────────────────────────────────

describe('count badges', () => {
  it('shows a count badge on days returned by the API', async () => {
    const today = new Date();
    const year  = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, '0');
    const day   = String(today.getDate()).padStart(2, '0');
    const dateStr = `${year}-${month}-${day}`;

    experimentsApi.getCalendar.mockResolvedValue({
      field: 'ww000000-0000-0000-0000-000000000099',
      days: { [dateStr]: 3 },
    });

    render(
      <ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} animals={ANIMALS} />,
    );

    await waitFor(() => {
      expect(screen.getByTestId(`count-${dateStr}`)).toHaveTextContent('3');
    });
  });

  it('does not show count badge on days with no data', async () => {
    const today = new Date();
    const year  = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, '0');
    const day   = String(today.getDate()).padStart(2, '0');
    const dateStr = `${year}-${month}-${day}`;

    experimentsApi.getCalendar.mockResolvedValue({ field: 'vitals', days: {} });

    render(
      <ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} animals={ANIMALS} />,
    );

    await waitFor(() => expect(experimentsApi.getCalendar).toHaveBeenCalled());
    expect(screen.queryByTestId(`count-${dateStr}`)).not.toBeInTheDocument();
  });
});

// ── Month navigation ──────────────────────────────────────────────────────────

describe('month navigation', () => {
  it('moves to the previous month on ‹ click', async () => {
    render(
      <ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} animals={ANIMALS} />,
    );
    const now  = new Date();
    const prev = new Date(now.getFullYear(), now.getMonth() - 1, 1);
    const prevLabel = prev.toLocaleString('en-US', { month: 'long', year: 'numeric' });

    fireEvent.click(screen.getByLabelText('Previous month'));
    await waitFor(() => expect(screen.getByText(prevLabel)).toBeInTheDocument());
  });

  it('moves to the next month on › click', async () => {
    render(
      <ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} animals={ANIMALS} />,
    );
    const now  = new Date();
    const next = new Date(now.getFullYear(), now.getMonth() + 1, 1);
    const nextLabel = next.toLocaleString('en-US', { month: 'long', year: 'numeric' });

    fireEvent.click(screen.getByLabelText('Next month'));
    await waitFor(() => expect(screen.getByText(nextLabel)).toBeInTheDocument());
  });

  it('re-fetches calendar data after field change', async () => {
    render(
      <ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} animals={ANIMALS} />,
    );
    await waitFor(() => expect(experimentsApi.getCalendar).toHaveBeenCalledTimes(1));

    fireEvent.change(screen.getByTestId('field-select'), { target: { value: 'vitals' } });

    await waitFor(() => expect(experimentsApi.getCalendar).toHaveBeenCalledTimes(2));
    expect(experimentsApi.getCalendar).toHaveBeenLastCalledWith(EXP_ID, 'vitals');
  });
});

// ── Day click → modal ─────────────────────────────────────────────────────────

describe('day click modal', () => {
  it('opens a modal showing subject data when a day with data is clicked', async () => {
    const today   = new Date();
    const year    = today.getFullYear();
    const month   = String(today.getMonth() + 1).padStart(2, '0');
    const day     = String(today.getDate()).padStart(2, '0');
    const dateStr = `${year}-${month}-${day}`;

    experimentsApi.getCalendar.mockResolvedValue({
      field: 'ww000000-0000-0000-0000-000000000099',
      days: { [dateStr]: 1 },
    });

    const statuses = [
      makeStatus('a1000000-0000-0000-0000-000000000001', dateStr, {
        'ww000000-0000-0000-0000-000000000099': '325',
      }),
    ];

    render(
      <ExperimentCalendar
        experimentId={EXP_ID}
        template={TEMPLATE}
        allStatuses={statuses}
        animals={ANIMALS}
      />,
    );

    await waitFor(() => expect(screen.getByTestId(`day-${dateStr}`)).not.toBeDisabled());

    fireEvent.click(screen.getByTestId(`day-${dateStr}`));

    await waitFor(() => {
      expect(screen.getByText('Rat A')).toBeInTheDocument();
      expect(screen.getByText('325')).toBeInTheDocument();
    });
  });

  it('does not open a modal when clicking a day without data', async () => {
    const today   = new Date();
    const year    = today.getFullYear();
    const month   = String(today.getMonth() + 1).padStart(2, '0');
    const day     = String(today.getDate()).padStart(2, '0');
    const dateStr = `${year}-${month}-${day}`;

    experimentsApi.getCalendar.mockResolvedValue({ field: 'vitals', days: {} });

    render(
      <ExperimentCalendar experimentId={EXP_ID} template={TEMPLATE} allStatuses={[]} animals={ANIMALS} />,
    );

    await waitFor(() => expect(experimentsApi.getCalendar).toHaveBeenCalled());

    // Day button is disabled — click should not open modal
    const dayBtn = screen.getByTestId(`day-${dateStr}`);
    expect(dayBtn).toBeDisabled();
    fireEvent.click(dayBtn);

    expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
  });

  it('shows all subjects with data on the selected day', async () => {
    const today   = new Date();
    const year    = today.getFullYear();
    const month   = String(today.getMonth() + 1).padStart(2, '0');
    const day     = String(today.getDate()).padStart(2, '0');
    const dateStr = `${year}-${month}-${day}`;

    experimentsApi.getCalendar.mockResolvedValue({
      field: 'ww000000-0000-0000-0000-000000000099',
      days: { [dateStr]: 2 },
    });

    const statuses = [
      makeStatus('a1000000-0000-0000-0000-000000000001', dateStr, { 'ww000000-0000-0000-0000-000000000099': '320' }),
      makeStatus('a2000000-0000-0000-0000-000000000002', dateStr, { 'ww000000-0000-0000-0000-000000000099': '310' }),
    ];

    render(
      <ExperimentCalendar
        experimentId={EXP_ID}
        template={TEMPLATE}
        allStatuses={statuses}
        animals={ANIMALS}
      />,
    );

    await waitFor(() => expect(screen.getByTestId(`day-${dateStr}`)).not.toBeDisabled());
    fireEvent.click(screen.getByTestId(`day-${dateStr}`));

    await waitFor(() => {
      expect(screen.getByText('Rat A')).toBeInTheDocument();
      expect(screen.getByText('Rat B')).toBeInTheDocument();
    });
  });
});
