import { describe, expect, it } from 'vitest';
import { RacingDomainInvariantError, RacingDomainValidationError } from '../errors/RacingDomainError';
import { SessionType } from '../value-objects/SessionType';
import { RaceEvent } from './RaceEvent';
import { Session } from './Session';

describe('RaceEvent', () => {
  const createMockSession = (overrides: Partial<{
    id: string;
    raceEventId: string;
    sessionType: SessionType;
    status: 'scheduled' | 'running' | 'completed' | 'cancelled';
    scheduledAt: Date;
  }> = {}): Session => {
    return Session.create({
      id: overrides.id ?? 'session-1',
      raceEventId: overrides.raceEventId ?? 'race-event-1',
      scheduledAt: overrides.scheduledAt ?? new Date('2023-01-01T10:00:00Z'),
      track: 'Monza',
      car: 'Ferrari SF21',
      sessionType: overrides.sessionType ?? SessionType.main(),
      status: overrides.status ?? 'scheduled',
    });
  };

  describe('create', () => {
    it('should create a race event with required fields', () => {
      const sessions = [createMockSession({ sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
      });

      expect(raceEvent.id).toBe('race-event-1');
      expect(raceEvent.seasonId).toBe('season-1');
      expect(raceEvent.leagueId).toBe('league-1');
      expect(raceEvent.name).toBe('Monza Grand Prix');
      expect(raceEvent.sessions).toHaveLength(1);
      expect(raceEvent.status).toBe('scheduled');
      expect(raceEvent.stewardingClosesAt).toBeUndefined();
    });

    it('should create a race event with all fields', () => {
      const sessions = [
        createMockSession({ id: 'session-1', sessionType: SessionType.practice() }),
        createMockSession({ id: 'session-2', sessionType: SessionType.qualifying() }),
        createMockSession({ id: 'session-3', sessionType: SessionType.main() }),
      ];
      const stewardingClosesAt = new Date('2023-01-02T10:00:00Z');
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
        status: 'in_progress',
        stewardingClosesAt,
      });

      expect(raceEvent.id).toBe('race-event-1');
      expect(raceEvent.seasonId).toBe('season-1');
      expect(raceEvent.leagueId).toBe('league-1');
      expect(raceEvent.name).toBe('Monza Grand Prix');
      expect(raceEvent.sessions).toHaveLength(3);
      expect(raceEvent.status).toBe('in_progress');
      expect(raceEvent.stewardingClosesAt).toEqual(stewardingClosesAt);
    });

    it('should throw error for invalid id', () => {
      const sessions = [createMockSession()];
      expect(() => RaceEvent.create({
        id: '',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for invalid seasonId', () => {
      const sessions = [createMockSession()];
      expect(() => RaceEvent.create({
        id: 'race-event-1',
        seasonId: '',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for invalid leagueId', () => {
      const sessions = [createMockSession()];
      expect(() => RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: '',
        name: 'Monza Grand Prix',
        sessions,
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for invalid name', () => {
      const sessions = [createMockSession()];
      expect(() => RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: '',
        sessions,
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for no sessions', () => {
      expect(() => RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions: [],
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for sessions not belonging to race event', () => {
      const sessions = [createMockSession({ raceEventId: 'other-race-event' })];
      expect(() => RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for duplicate session types', () => {
      const sessions = [
        createMockSession({ id: 'session-1', sessionType: SessionType.main() }),
        createMockSession({ id: 'session-2', sessionType: SessionType.main() }),
      ];
      expect(() => RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
      })).toThrow(RacingDomainValidationError);
    });

    it('should throw error for no main race session', () => {
      const sessions = [createMockSession({ sessionType: SessionType.practice() })];
      expect(() => RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
      })).toThrow(RacingDomainValidationError);
    });
  });

  describe('start', () => {
    it('should start a scheduled race event', () => {
      const sessions = [createMockSession({ sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
        status: 'scheduled',
      });
      const started = raceEvent.start();
      expect(started.status).toBe('in_progress');
    });

    it('should throw error if not scheduled', () => {
      const sessions = [createMockSession({ sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
        status: 'in_progress',
      });
      expect(() => raceEvent.start()).toThrow(RacingDomainInvariantError);
    });
  });

  describe('completeMainRace', () => {
    it('should complete main race and move to awaiting_stewarding', () => {
      const mainSession = createMockSession({ sessionType: SessionType.main(), status: 'completed' });
      const sessions = [mainSession];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
        status: 'in_progress',
      });
      const completed = raceEvent.completeMainRace();
      expect(completed.status).toBe('awaiting_stewarding');
    });

    it('should throw error if not in progress', () => {
      const sessions = [createMockSession({ sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
        status: 'scheduled',
      });
      expect(() => raceEvent.completeMainRace()).toThrow(RacingDomainInvariantError);
    });

    it('should throw error if main race not completed', () => {
      const sessions = [createMockSession({ sessionType: SessionType.main(), status: 'running' })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
        status: 'in_progress',
      });
      expect(() => raceEvent.completeMainRace()).toThrow(RacingDomainInvariantError);
    });
  });

  describe('closeStewarding', () => {
    it('should close stewarding and finalize race event', () => {
      const sessions = [createMockSession({ sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
        status: 'awaiting_stewarding',
      });
      const closed = raceEvent.closeStewarding();
      expect(closed.status).toBe('closed');
    });

    it('should throw error if not awaiting stewarding', () => {
      const sessions = [createMockSession({ sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
        status: 'in_progress',
      });
      expect(() => raceEvent.closeStewarding()).toThrow(RacingDomainInvariantError);
    });
  });

  describe('cancel', () => {
    it('should cancel a scheduled race event', () => {
      const sessions = [createMockSession({ sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
        status: 'scheduled',
      });
      const cancelled = raceEvent.cancel();
      expect(cancelled.status).toBe('cancelled');
    });

    it('should return same instance if already cancelled', () => {
      const sessions = [createMockSession({ sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
        status: 'cancelled',
      });
      const cancelled = raceEvent.cancel();
      expect(cancelled).toBe(raceEvent);
    });

    it('should throw error if closed', () => {
      const sessions = [createMockSession({ sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
        status: 'closed',
      });
      expect(() => raceEvent.cancel()).toThrow(RacingDomainInvariantError);
    });
  });

  describe('getMainRaceSession', () => {
    it('should return the main race session', () => {
      const mainSession = createMockSession({ sessionType: SessionType.main() });
      const practiceSession = createMockSession({ id: 'session-2', sessionType: SessionType.practice() });
      const sessions = [practiceSession, mainSession];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
      });
      expect(raceEvent.getMainRaceSession()).toBe(mainSession);
    });
  });

  describe('getSessionsByType', () => {
    it('should return sessions of specific type', () => {
      const practice = createMockSession({ id: 'p1', sessionType: SessionType.practice() });
      const qualifying = createMockSession({ id: 'q1', sessionType: SessionType.qualifying() });
      const main = createMockSession({ id: 'm1', sessionType: SessionType.main() });
      const sessions = [practice, qualifying, main];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
      });
      const practices = raceEvent.getSessionsByType(SessionType.practice());
      expect(practices).toHaveLength(1);
      expect(practices).toContain(practice);
    });
  });

  describe('getCompletedSessions', () => {
    it('should return only completed sessions', () => {
      const completed1 = createMockSession({ id: 'c1', sessionType: SessionType.practice(), status: 'completed' });
      const completed2 = createMockSession({ id: 'c2', sessionType: SessionType.qualifying(), status: 'completed' });
      const running = createMockSession({ id: 'r1', sessionType: SessionType.main(), status: 'running' });
      const sessions = [completed1, running, completed2];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
      });
      const completed = raceEvent.getCompletedSessions();
      expect(completed).toHaveLength(2);
      expect(completed).toContain(completed1);
      expect(completed).toContain(completed2);
    });
  });

  describe('areAllSessionsCompleted', () => {
    it('should return true if all sessions completed', () => {
      const sessions = [
        createMockSession({ id: 's1', sessionType: SessionType.practice(), status: 'completed' }),
        createMockSession({ id: 's2', sessionType: SessionType.main(), status: 'completed' }),
      ];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
      });
      expect(raceEvent.areAllSessionsCompleted()).toBe(true);
    });

    it('should return false if not all sessions completed', () => {
      const sessions = [
        createMockSession({ id: 's1', sessionType: SessionType.practice(), status: 'completed' }),
        createMockSession({ id: 's2', sessionType: SessionType.main(), status: 'running' }),
      ];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
      });
      expect(raceEvent.areAllSessionsCompleted()).toBe(false);
    });
  });

  describe('isMainRaceCompleted', () => {
    it('should return true if main race is completed', () => {
      const mainSession = createMockSession({ sessionType: SessionType.main(), status: 'completed' });
      const sessions = [mainSession];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
      });
      expect(raceEvent.isMainRaceCompleted()).toBe(true);
    });

    it('should return false if main race not completed', () => {
      const mainSession = createMockSession({ sessionType: SessionType.main(), status: 'running' });
      const sessions = [mainSession];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
      });
      expect(raceEvent.isMainRaceCompleted()).toBe(false);
    });
  });

  describe('hasStewardingExpired', () => {
    it('should return true if stewarding has expired', () => {
      const pastDate = new Date(Date.now() - 3600000);
      const sessions = [createMockSession({ sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
        stewardingClosesAt: pastDate,
      });
      expect(raceEvent.hasStewardingExpired()).toBe(true);
    });

    it('should return false if stewarding not set', () => {
      const sessions = [createMockSession({ sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
      });
      expect(raceEvent.hasStewardingExpired()).toBe(false);
    });

    it('should return false if stewarding not expired', () => {
      const futureDate = new Date(Date.now() + 3600000);
      const sessions = [createMockSession({ sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
        stewardingClosesAt: futureDate,
      });
      expect(raceEvent.hasStewardingExpired()).toBe(false);
    });
  });

  describe('isPast', () => {
    it('should return true if latest session is in past', () => {
      const pastDate = new Date(Date.now() - 3600000);
      const sessions = [createMockSession({ scheduledAt: pastDate, sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
      });
      expect(raceEvent.isPast()).toBe(true);
    });

    it('should return false if latest session is future', () => {
      const futureDate = new Date(Date.now() + 3600000);
      const sessions = [createMockSession({ scheduledAt: futureDate, sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
      });
      expect(raceEvent.isPast()).toBe(false);
    });
  });

  describe('isUpcoming', () => {
    it('should return true for scheduled future event', () => {
      const futureDate = new Date(Date.now() + 3600000);
      const sessions = [createMockSession({ scheduledAt: futureDate, sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
        status: 'scheduled',
      });
      expect(raceEvent.isUpcoming()).toBe(true);
    });

    it('should return false for past scheduled event', () => {
      const pastDate = new Date(Date.now() - 3600000);
      const sessions = [createMockSession({ scheduledAt: pastDate, sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
        status: 'scheduled',
      });
      expect(raceEvent.isUpcoming()).toBe(false);
    });
  });

  describe('isLive', () => {
    it('should return true for in_progress status', () => {
      const sessions = [createMockSession({ sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
        status: 'in_progress',
      });
      expect(raceEvent.isLive()).toBe(true);
    });

    it('should return false for other statuses', () => {
      const sessions = [createMockSession({ sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
        status: 'scheduled',
      });
      expect(raceEvent.isLive()).toBe(false);
    });
  });

  describe('isAwaitingStewarding', () => {
    it('should return true for awaiting_stewarding status', () => {
      const sessions = [createMockSession({ sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
        status: 'awaiting_stewarding',
      });
      expect(raceEvent.isAwaitingStewarding()).toBe(true);
    });
  });

  describe('isClosed', () => {
    it('should return true for closed status', () => {
      const sessions = [createMockSession({ sessionType: SessionType.main() })];
      const raceEvent = RaceEvent.create({
        id: 'race-event-1',
        seasonId: 'season-1',
        leagueId: 'league-1',
        name: 'Monza Grand Prix',
        sessions,
        status: 'closed',
      });
      expect(raceEvent.isClosed()).toBe(true);
    });
  });
});