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

import LeagueAdminSchedulePage from './page';

type SeasonSummaryViewModel = {
  seasonId: string;
  name: string;
  status: string;
  isPrimary: boolean;
  isParallelActive: boolean;
};

type AdminScheduleRaceViewModel = {
  id: string;
  name: string;
  scheduledAt: Date;
};

type AdminScheduleViewModel = {
  seasonId: string;
  published: boolean;
  races: AdminScheduleRaceViewModel[];
};

const mockGetLeagueSeasonSummaries = vi.fn<() => Promise<SeasonSummaryViewModel[]>>();

const mockGetAdminSchedule = vi.fn<(leagueId: string, seasonId: string) => Promise<AdminScheduleViewModel>>();

const mockGetLeagueScheduleDto = vi.fn(() => {
  throw new Error('LeagueAdminSchedulePage must not call getLeagueScheduleDto (DTO boundary violation)');
});

const mockPublishAdminSchedule = vi.fn<(leagueId: string, seasonId: string) => Promise<AdminScheduleViewModel>>();
const mockUnpublishAdminSchedule = vi.fn<(leagueId: string, seasonId: string) => Promise<AdminScheduleViewModel>>();

const mockCreateAdminScheduleRace = vi.fn<
  (leagueId: string, seasonId: string, input: { track: string; car: string; scheduledAtIso: string }) => Promise<AdminScheduleViewModel>
>();
const mockUpdateAdminScheduleRace = vi.fn<
  (
    leagueId: string,
    seasonId: string,
    raceId: string,
    input: Partial<{ track: string; car: string; scheduledAtIso: string }>,
  ) => Promise<AdminScheduleViewModel>
>();
const mockDeleteAdminScheduleRace = vi.fn<(leagueId: string, seasonId: string, raceId: string) => Promise<AdminScheduleViewModel>>();

const mockFetchLeagueMemberships = vi.fn<(leagueId: string) => Promise<unknown[]>>();
const mockGetMembership = vi.fn<
  (leagueId: string, driverId: string) => { role: 'admin' | 'owner' | 'member' | 'steward' } | null
>();

vi.mock('next/navigation', () => ({
  useParams: () => ({ id: 'league-1' }),
}));

vi.mock('@/hooks/useEffectiveDriverId', () => ({
  useEffectiveDriverId: () => 'driver-1',
}));

const mockServices = {
  leagueService: {
    getLeagueSeasonSummaries: mockGetLeagueSeasonSummaries,

    getAdminSchedule: mockGetAdminSchedule,

    publishAdminSchedule: mockPublishAdminSchedule,
    unpublishAdminSchedule: mockUnpublishAdminSchedule,

    createAdminScheduleRace: mockCreateAdminScheduleRace,
    updateAdminScheduleRace: mockUpdateAdminScheduleRace,
    deleteAdminScheduleRace: mockDeleteAdminScheduleRace,

    // Legacy method (should never be called by this page)
    getLeagueScheduleDto: mockGetLeagueScheduleDto,
  },
  leagueMembershipService: {
    fetchLeagueMemberships: mockFetchLeagueMemberships,
    getMembership: mockGetMembership,
  },
};

vi.mock('@/lib/services/ServiceProvider', () => ({
  useServices: () => mockServices,
}));

function createAdminScheduleViewModel(overrides: Partial<AdminScheduleViewModel> = {}): AdminScheduleViewModel {
  return {
    seasonId: 'season-1',
    published: false,
    races: [],
    ...overrides,
  };
}

describe('LeagueAdminSchedulePage', () => {
  afterEach(() => {
    cleanup();
  });

  beforeEach(() => {
    mockGetLeagueSeasonSummaries.mockReset();
    mockGetAdminSchedule.mockReset();
    mockGetLeagueScheduleDto.mockClear();

    mockPublishAdminSchedule.mockReset();
    mockUnpublishAdminSchedule.mockReset();
    mockCreateAdminScheduleRace.mockReset();
    mockUpdateAdminScheduleRace.mockReset();
    mockDeleteAdminScheduleRace.mockReset();

    mockFetchLeagueMemberships.mockReset();
    mockGetMembership.mockReset();

    mockFetchLeagueMemberships.mockResolvedValue([]);
    mockGetMembership.mockReturnValue({ role: 'admin' });
  });

  it('renders schedule using ViewModel fields (no DTO date field)', async () => {
    mockGetLeagueSeasonSummaries.mockResolvedValue([
      { seasonId: 'season-1', name: 'Season 1', status: 'active', isPrimary: true, isParallelActive: false },
    ]);

    mockGetAdminSchedule.mockResolvedValue(
      createAdminScheduleViewModel({
        seasonId: 'season-1',
        published: true,
        races: [{ id: 'race-1', name: 'Race 1', scheduledAt: new Date('2025-01-01T12:00:00.000Z') }],
      }),
    );

    render(<LeagueAdminSchedulePage />);

    expect(await screen.findByText('Schedule Admin')).toBeInTheDocument();
    expect(await screen.findByText('Race 1')).toBeInTheDocument();
    expect(await screen.findByText('2025-01-01T12:00:00.000Z')).toBeInTheDocument();
    expect(screen.getByText(/Status:/)).toHaveTextContent('Published');

    await waitFor(() => {
      expect(mockGetAdminSchedule).toHaveBeenCalledWith('league-1', 'season-1');
    });
    expect(mockGetLeagueScheduleDto).not.toHaveBeenCalled();
  });

  it('publish/unpublish uses admin schedule service API and updates UI status', async () => {
    mockGetLeagueSeasonSummaries.mockResolvedValue([
      { seasonId: 'season-1', name: 'Season 1', status: 'active', isPrimary: true, isParallelActive: false },
    ]);

    mockGetAdminSchedule.mockResolvedValue(createAdminScheduleViewModel({ published: false }));

    mockPublishAdminSchedule.mockResolvedValue(createAdminScheduleViewModel({ published: true }));
    mockUnpublishAdminSchedule.mockResolvedValue(createAdminScheduleViewModel({ published: false }));

    render(<LeagueAdminSchedulePage />);

    expect(await screen.findByText(/Status:/)).toHaveTextContent('Unpublished');

    await waitFor(() => {
      expect(mockGetAdminSchedule).toHaveBeenCalledWith('league-1', 'season-1');
    });
    expect(mockGetLeagueScheduleDto).not.toHaveBeenCalled();

    await waitFor(() => {
      expect(screen.getByRole('button', { name: 'Publish' })).toBeEnabled();
    });

    fireEvent.click(screen.getByRole('button', { name: 'Publish' }));

    await waitFor(() => {
      expect(mockPublishAdminSchedule).toHaveBeenCalledWith('league-1', 'season-1');
    });

    await waitFor(() => {
      expect(screen.getByText(/Status:/)).toHaveTextContent('Published');
    });

    await waitFor(() => {
      expect(screen.getByRole('button', { name: 'Unpublish' })).toBeEnabled();
    });

    fireEvent.click(screen.getByRole('button', { name: 'Unpublish' }));

    await waitFor(() => {
      expect(mockUnpublishAdminSchedule).toHaveBeenCalledWith('league-1', 'season-1');
    });

    await waitFor(() => {
      expect(screen.getByText(/Status:/)).toHaveTextContent('Unpublished');
    });
  });

  it('create/update/delete uses admin schedule service API and refreshes schedule list', async () => {
    const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true);

    mockGetLeagueSeasonSummaries.mockResolvedValue([
      { seasonId: 'season-1', name: 'Season 1', status: 'active', isPrimary: true, isParallelActive: false },
    ]);

    mockGetAdminSchedule.mockResolvedValueOnce(createAdminScheduleViewModel({ published: false, races: [] }));

    mockCreateAdminScheduleRace.mockResolvedValueOnce(
      createAdminScheduleViewModel({
        races: [{ id: 'race-1', name: 'Race 1', scheduledAt: new Date('2025-01-01T12:00:00.000Z') }],
      }),
    );

    mockUpdateAdminScheduleRace.mockResolvedValueOnce(
      createAdminScheduleViewModel({
        races: [{ id: 'race-1', name: 'Race 1', scheduledAt: new Date('2025-01-02T12:00:00.000Z') }],
      }),
    );

    mockDeleteAdminScheduleRace.mockResolvedValueOnce(createAdminScheduleViewModel({ races: [] }));

    render(<LeagueAdminSchedulePage />);

    await screen.findByText('Schedule Admin');

    await waitFor(() => {
      expect(mockGetAdminSchedule).toHaveBeenCalledWith('league-1', 'season-1');
    });
    expect(mockGetLeagueScheduleDto).not.toHaveBeenCalled();

    await waitFor(() => {
      expect(screen.queryByText('Loading…')).toBeNull();
    });

    await screen.findByLabelText('Track');
    await screen.findByLabelText('Car');
    await screen.findByLabelText('Scheduled At (ISO)');

    fireEvent.change(screen.getByLabelText('Track'), { target: { value: 'Laguna Seca' } });
    fireEvent.change(screen.getByLabelText('Car'), { target: { value: 'MX-5' } });
    fireEvent.change(screen.getByLabelText('Scheduled At (ISO)'), { target: { value: '2025-01-01T12:00:00.000Z' } });

    fireEvent.click(screen.getByRole('button', { name: 'Add race' }));

    await waitFor(() => {
      expect(mockCreateAdminScheduleRace).toHaveBeenCalledWith('league-1', 'season-1', {
        track: 'Laguna Seca',
        car: 'MX-5',
        scheduledAtIso: '2025-01-01T12:00:00.000Z',
      });
    });

    expect(await screen.findByText('Race 1')).toBeInTheDocument();
    expect(await screen.findByText('2025-01-01T12:00:00.000Z')).toBeInTheDocument();

    fireEvent.click(screen.getByRole('button', { name: 'Edit' }));
    fireEvent.change(screen.getByLabelText('Scheduled At (ISO)'), { target: { value: '2025-01-02T12:00:00.000Z' } });

    fireEvent.click(screen.getByRole('button', { name: 'Save' }));

    await waitFor(() => {
      expect(mockUpdateAdminScheduleRace).toHaveBeenCalledWith('league-1', 'season-1', 'race-1', {
        scheduledAtIso: '2025-01-02T12:00:00.000Z',
      });
    });

    expect(await screen.findByText('2025-01-02T12:00:00.000Z')).toBeInTheDocument();

    fireEvent.click(screen.getByRole('button', { name: 'Delete' }));

    await waitFor(() => {
      expect(mockDeleteAdminScheduleRace).toHaveBeenCalledWith('league-1', 'season-1', 'race-1');
    });

    await waitFor(() => {
      expect(screen.queryByText('Race 1')).toBeNull();
    });

    confirmSpy.mockRestore();
  });
});