import { fireEvent, render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, expect, it, vi } from 'vitest';

import CalendarView, { type CalendarEntry } from '@/Components/Calendar/CalendarView';

function makeCalendarEntry(overrides: Partial<CalendarEntry> = {}): CalendarEntry {
  return {
    id: 1,
    title: 'Test Task',
    description: 'Test description',
    due_date: '2026-03-15',
    scheduled_date: null,
    status: 'planned',
    source_type: null,
    source_id: null,
    page_url: null,
    metadata: null,
    ...overrides,
  };
}

describe('CalendarView', () => {
  it('renders with default month view', () => {
    render(<CalendarView />);

    expect(screen.getByText('SEO Calendar')).toBeInTheDocument();
    expect(screen.getByRole('tab', { name: 'Month', selected: true })).toBeInTheDocument();
  });

  it('renders view switching tabs', () => {
    render(<CalendarView />);

    expect(screen.getByRole('tab', { name: 'Month' })).toBeInTheDocument();
    expect(screen.getByRole('tab', { name: 'Week' })).toBeInTheDocument();
    expect(screen.getByRole('tab', { name: 'Day' })).toBeInTheDocument();
  });

  it('switches to week view when week tab is clicked', async () => {
    const user = userEvent.setup();
    render(<CalendarView />);

    const weekTab = screen.getByRole('tab', { name: 'Week' });
    await user.click(weekTab);

    expect(screen.getByRole('tab', { name: 'Week', selected: true })).toBeInTheDocument();
  });

  it('switches to day view when day tab is clicked', async () => {
    const user = userEvent.setup();
    render(<CalendarView />);

    const dayTab = screen.getByRole('tab', { name: 'Day' });
    await user.click(dayTab);

    expect(screen.getByRole('tab', { name: 'Day', selected: true })).toBeInTheDocument();
  });

  it('calls onViewChange callback when view changes', async () => {
    const user = userEvent.setup();
    const onViewChange = vi.fn();
    render(<CalendarView onViewChange={onViewChange} />);

    const weekTab = screen.getByRole('tab', { name: 'Week' });
    await user.click(weekTab);

    expect(onViewChange).toHaveBeenCalledWith('week');
  });

  it('displays entry in day view', async () => {
    const user = userEvent.setup();
    const entry = makeCalendarEntry({
      title: 'Refresh blog post',
      due_date: new Date().toISOString().split('T')[0],
    });

    render(<CalendarView entries={[entry]} selectedDate={new Date()} />);

    const dayTab = screen.getByRole('tab', { name: 'Day' });
    await user.click(dayTab);

    expect(screen.getByText('Refresh blog post')).toBeInTheDocument();
  });

  it('displays entry description in day view', async () => {
    const user = userEvent.setup();
    const entry = makeCalendarEntry({
      title: 'Refresh blog post',
      description: 'Update content for freshness',
      due_date: new Date().toISOString().split('T')[0],
    });

    render(<CalendarView entries={[entry]} selectedDate={new Date()} />);

    const dayTab = screen.getByRole('tab', { name: 'Day' });
    await user.click(dayTab);

    expect(screen.getByText('Update content for freshness')).toBeInTheDocument();
  });

  it('displays entry page URL in day view', async () => {
    const user = userEvent.setup();
    const entry = makeCalendarEntry({
      title: 'Refresh blog post',
      page_url: '/blog/test-post',
      due_date: new Date().toISOString().split('T')[0],
    });

    render(<CalendarView entries={[entry]} selectedDate={new Date()} />);

    const dayTab = screen.getByRole('tab', { name: 'Day' });
    await user.click(dayTab);

    expect(screen.getByText('/blog/test-post')).toBeInTheDocument();
  });

  it('displays status badge for entry in day view', async () => {
    const user = userEvent.setup();
    const entry = makeCalendarEntry({
      title: 'Refresh blog post',
      status: 'completed',
      due_date: new Date().toISOString().split('T')[0],
    });

    render(<CalendarView entries={[entry]} selectedDate={new Date()} />);

    const dayTab = screen.getByRole('tab', { name: 'Day' });
    await user.click(dayTab);

    expect(screen.getByText('completed')).toBeInTheDocument();
  });

  it('shows empty state when no entries for selected day', async () => {
    const user = userEvent.setup();
    const entry = makeCalendarEntry({
      title: 'Future task',
      due_date: '2026-12-31',
    });

    render(<CalendarView entries={[entry]} selectedDate={new Date()} />);

    const dayTab = screen.getByRole('tab', { name: 'Day' });
    await user.click(dayTab);

    expect(screen.getByText('No tasks scheduled for this day')).toBeInTheDocument();
  });

  it('displays multiple entries in day view', async () => {
    const user = userEvent.setup();
    const today = new Date().toISOString().split('T')[0];
    const entries = [
      makeCalendarEntry({ id: 1, title: 'Task 1', due_date: today }),
      makeCalendarEntry({ id: 2, title: 'Task 2', due_date: today }),
      makeCalendarEntry({ id: 3, title: 'Task 3', due_date: today }),
    ];

    render(<CalendarView entries={entries} selectedDate={new Date()} />);

    const dayTab = screen.getByRole('tab', { name: 'Day' });
    await user.click(dayTab);

    expect(screen.getByText('Task 1')).toBeInTheDocument();
    expect(screen.getByText('Task 2')).toBeInTheDocument();
    expect(screen.getByText('Task 3')).toBeInTheDocument();
  });

  it('applies correct status styling for completed tasks', async () => {
    const user = userEvent.setup();
    const entry = makeCalendarEntry({
      title: 'Completed task',
      status: 'completed',
      due_date: new Date().toISOString().split('T')[0],
    });

    render(<CalendarView entries={[entry]} selectedDate={new Date()} />);

    const dayTab = screen.getByRole('tab', { name: 'Day' });
    await user.click(dayTab);

    const statusBadge = screen.getByText('completed');
    expect(statusBadge).toHaveClass('bg-green-100', 'text-green-800');
  });

  it('applies correct status styling for in progress tasks', async () => {
    const user = userEvent.setup();
    const entry = makeCalendarEntry({
      title: 'In progress task',
      status: 'in_progress',
      due_date: new Date().toISOString().split('T')[0],
    });

    render(<CalendarView entries={[entry]} selectedDate={new Date()} />);

    const dayTab = screen.getByRole('tab', { name: 'Day' });
    await user.click(dayTab);

    const statusBadge = screen.getByText('in progress');
    expect(statusBadge).toHaveClass('bg-blue-100', 'text-blue-800');
  });

  it('applies correct status styling for planned tasks', async () => {
    const user = userEvent.setup();
    const entry = makeCalendarEntry({
      title: 'Planned task',
      status: 'planned',
      due_date: new Date().toISOString().split('T')[0],
    });

    render(<CalendarView entries={[entry]} selectedDate={new Date()} />);

    const dayTab = screen.getByRole('tab', { name: 'Day' });
    await user.click(dayTab);

    const statusBadge = screen.getByText('planned');
    expect(statusBadge).toHaveClass('bg-muted', 'text-foreground');
  });

  it('applies correct status styling for overdue tasks', async () => {
    const user = userEvent.setup();
    const entry = makeCalendarEntry({
      title: 'Overdue task',
      status: 'overdue',
      due_date: new Date().toISOString().split('T')[0],
    });

    render(<CalendarView entries={[entry]} selectedDate={new Date()} />);

    const dayTab = screen.getByRole('tab', { name: 'Day' });
    await user.click(dayTab);

    const statusBadge = screen.getByText('overdue');
    expect(statusBadge).toHaveClass('bg-red-100', 'text-red-800');
  });

  it('displays week view with 7 day columns', async () => {
    const user = userEvent.setup();
    render(<CalendarView />);

    const weekTab = screen.getByRole('tab', { name: 'Week' });
    await user.click(weekTab);

    // Check that there are 7 day columns (grid should have 7 children)
    const weekGrid = screen.getByRole('tabpanel');
    const dayColumns = within(weekGrid).getAllByText(/Mon|Tue|Wed|Thu|Fri|Sat|Sun/);
    expect(dayColumns.length).toBeGreaterThan(0);
  });

  it('displays entries in correct day of week view', async () => {
    const user = userEvent.setup();
    const today = new Date();
    const entry = makeCalendarEntry({
      title: 'Week task',
      due_date: today.toISOString().split('T')[0],
    });

    render(<CalendarView entries={[entry]} selectedDate={today} />);

    const weekTab = screen.getByRole('tab', { name: 'Week' });
    await user.click(weekTab);

    expect(screen.getByText('Week task')).toBeInTheDocument();
  });

  it('passes onDateSelect callback to calendar component', () => {
    const onDateSelect = vi.fn();
    const { container } = render(<CalendarView onDateSelect={onDateSelect} />);

    // Verify the calendar is rendered (it should be in the month view by default)
    const calendar = container.querySelector('.rdp-root');
    expect(calendar).toBeInTheDocument();

    // The Calendar component should have the onSelect handler wired up
    // This test verifies the component structure is correct
    expect(screen.getByRole('grid')).toBeInTheDocument();
  });

  it('uses initial selected date', () => {
    const initialDate = new Date('2026-03-15');
    render(<CalendarView selectedDate={initialDate} />);

    // The calendar should show March 2026
    expect(screen.getByText(/March/i)).toBeInTheDocument();
  });

  it('accepts custom className', () => {
    const { container } = render(<CalendarView className="custom-class" />);

    const card = container.querySelector('.custom-class');
    expect(card).toBeInTheDocument();
  });

  it('handles empty entries array', async () => {
    const user = userEvent.setup();
    render(<CalendarView entries={[]} />);

    const dayTab = screen.getByRole('tab', { name: 'Day' });
    await user.click(dayTab);

    expect(screen.getByText('No tasks scheduled for this day')).toBeInTheDocument();
  });

  it('filters entries by date correctly', async () => {
    const user = userEvent.setup();
    const entries = [
      makeCalendarEntry({ id: 1, title: 'Today task', due_date: '2026-03-15' }),
      makeCalendarEntry({ id: 2, title: 'Tomorrow task', due_date: '2026-03-16' }),
    ];

    render(<CalendarView entries={entries} selectedDate={new Date('2026-03-15')} />);

    const dayTab = screen.getByRole('tab', { name: 'Day' });
    await user.click(dayTab);

    expect(screen.getByText('Today task')).toBeInTheDocument();
    expect(screen.queryByText('Tomorrow task')).not.toBeInTheDocument();
  });

  describe('drag and drop', () => {
    it('makes entries draggable when onReschedule is provided', async () => {
      const user = userEvent.setup();
      const onReschedule = vi.fn();
      const entry = makeCalendarEntry({
        id: 1,
        title: 'Draggable task',
        due_date: new Date().toISOString().split('T')[0],
      });

      render(<CalendarView entries={[entry]} onReschedule={onReschedule} />);

      const weekTab = screen.getByRole('tab', { name: 'Week' });
      await user.click(weekTab);

      const entryElement = screen.getByTestId('calendar-entry-1');
      expect(entryElement).toHaveAttribute('draggable', 'true');
      expect(entryElement).toHaveClass('cursor-move');
    });

    it('entries are not draggable when onReschedule is not provided', async () => {
      const user = userEvent.setup();
      const entry = makeCalendarEntry({
        id: 1,
        title: 'Non-draggable task',
        due_date: new Date().toISOString().split('T')[0],
      });

      render(<CalendarView entries={[entry]} />);

      const weekTab = screen.getByRole('tab', { name: 'Week' });
      await user.click(weekTab);

      const entryElement = screen.getByTestId('calendar-entry-1');
      expect(entryElement).toHaveAttribute('draggable', 'false');
      expect(entryElement).not.toHaveClass('cursor-move');
    });

    it('calls onReschedule when entry is dropped on a different day', async () => {
      const user = userEvent.setup();
      const onReschedule = vi.fn();

      const today = new Date(2026, 1, 24); // Feb 24, 2026 (Tuesday)
      const targetDate = new Date(2026, 1, 25); // Feb 25, 2026 (Wednesday)

      const entry = makeCalendarEntry({
        id: 1,
        title: 'Task to move',
        due_date: today.toISOString().split('T')[0],
      });

      render(<CalendarView entries={[entry]} selectedDate={today} onReschedule={onReschedule} />);

      const weekTab = screen.getByRole('tab', { name: 'Week' });
      await user.click(weekTab);

      const entryElement = screen.getByTestId('calendar-entry-1');
      const targetDay = screen.getByTestId(`week-day-${targetDate.toISOString().split('T')[0]}`);

      // Simulate drag and drop
      fireEvent.dragStart(entryElement, {
        dataTransfer: {
          effectAllowed: '',
          setData: vi.fn(),
          getData: vi.fn(() => '1'),
          dropEffect: '',
        },
      });

      fireEvent.drop(targetDay, {
        dataTransfer: {
          getData: vi.fn(() => '1'),
        },
      });

      expect(onReschedule).toHaveBeenCalledWith(1, targetDate.toISOString().split('T')[0]);
    });

    it('applies hover styles when dragging over a day cell', async () => {
      const user = userEvent.setup();
      const onReschedule = vi.fn();

      const today = new Date(2026, 1, 24); // Feb 24, 2026 (Tuesday)
      const targetDate = new Date(2026, 1, 25); // Feb 25, 2026 (Wednesday)

      const entry = makeCalendarEntry({
        id: 1,
        title: 'Task to move',
        due_date: today.toISOString().split('T')[0],
      });

      render(<CalendarView entries={[entry]} selectedDate={today} onReschedule={onReschedule} />);

      const weekTab = screen.getByRole('tab', { name: 'Week' });
      await user.click(weekTab);

      const targetDay = screen.getByTestId(`week-day-${targetDate.toISOString().split('T')[0]}`);

      // Simulate drag over
      fireEvent.dragOver(targetDay, {
        dataTransfer: {
          dropEffect: '',
        },
      });

      expect(targetDay).toHaveClass('bg-blue-50');
      expect(targetDay).toHaveClass('border-blue-300');
    });

    it('removes hover styles when drag leaves a day cell', async () => {
      const user = userEvent.setup();
      const onReschedule = vi.fn();

      const today = new Date(2026, 1, 24); // Feb 24, 2026 (Tuesday)
      const targetDate = new Date(2026, 1, 25); // Feb 25, 2026 (Wednesday)

      const entry = makeCalendarEntry({
        id: 1,
        title: 'Task to move',
        due_date: today.toISOString().split('T')[0],
      });

      render(<CalendarView entries={[entry]} selectedDate={today} onReschedule={onReschedule} />);

      const weekTab = screen.getByRole('tab', { name: 'Week' });
      await user.click(weekTab);

      const targetDay = screen.getByTestId(`week-day-${targetDate.toISOString().split('T')[0]}`);

      // Simulate drag over
      fireEvent.dragOver(targetDay, {
        dataTransfer: {
          dropEffect: '',
        },
      });

      expect(targetDay).toHaveClass('bg-blue-50');

      // Simulate drag leave
      fireEvent.dragLeave(targetDay);

      expect(targetDay).not.toHaveClass('bg-blue-50');
    });

    it('allows dropping on the same day without calling onReschedule unnecessarily', async () => {
      const user = userEvent.setup();
      const onReschedule = vi.fn();

      const today = new Date();

      const entry = makeCalendarEntry({
        id: 1,
        title: 'Task to move',
        due_date: today.toISOString().split('T')[0],
      });

      render(<CalendarView entries={[entry]} selectedDate={today} onReschedule={onReschedule} />);

      const weekTab = screen.getByRole('tab', { name: 'Week' });
      await user.click(weekTab);

      const entryElement = screen.getByTestId('calendar-entry-1');
      const sameDay = screen.getByTestId(`week-day-${today.toISOString().split('T')[0]}`);

      // Simulate drag and drop on the same day
      fireEvent.dragStart(entryElement, {
        dataTransfer: {
          effectAllowed: '',
          setData: vi.fn(),
          getData: vi.fn(() => '1'),
          dropEffect: '',
        },
      });

      fireEvent.drop(sameDay, {
        dataTransfer: {
          getData: vi.fn(() => '1'),
        },
      });

      // onReschedule is still called - the parent component decides if it's a no-op
      expect(onReschedule).toHaveBeenCalledWith(1, today.toISOString().split('T')[0]);
    });

    it('handles multiple entries being dragged sequentially', async () => {
      const user = userEvent.setup();
      const onReschedule = vi.fn();

      const today = new Date(2026, 1, 24); // Feb 24, 2026 (Tuesday)
      const targetDate = new Date(2026, 1, 25); // Feb 25, 2026 (Wednesday)

      const entries = [
        makeCalendarEntry({ id: 1, title: 'Task 1', due_date: today.toISOString().split('T')[0] }),
        makeCalendarEntry({ id: 2, title: 'Task 2', due_date: today.toISOString().split('T')[0] }),
      ];

      render(<CalendarView entries={entries} selectedDate={today} onReschedule={onReschedule} />);

      const weekTab = screen.getByRole('tab', { name: 'Week' });
      await user.click(weekTab);

      const targetDay = screen.getByTestId(`week-day-${targetDate.toISOString().split('T')[0]}`);

      // Drag first entry
      const entry1 = screen.getByTestId('calendar-entry-1');
      fireEvent.dragStart(entry1, {
        dataTransfer: {
          effectAllowed: '',
          setData: vi.fn(),
          getData: vi.fn(() => '1'),
          dropEffect: '',
        },
      });

      fireEvent.drop(targetDay, {
        dataTransfer: {
          getData: vi.fn(() => '1'),
        },
      });

      // Drag second entry
      const entry2 = screen.getByTestId('calendar-entry-2');
      fireEvent.dragStart(entry2, {
        dataTransfer: {
          effectAllowed: '',
          setData: vi.fn(),
          getData: vi.fn(() => '2'),
          dropEffect: '',
        },
      });

      fireEvent.drop(targetDay, {
        dataTransfer: {
          getData: vi.fn(() => '2'),
        },
      });

      expect(onReschedule).toHaveBeenCalledTimes(2);
      expect(onReschedule).toHaveBeenNthCalledWith(1, 1, targetDate.toISOString().split('T')[0]);
      expect(onReschedule).toHaveBeenNthCalledWith(2, 2, targetDate.toISOString().split('T')[0]);
    });
  });
});
