import { describe, it, expect, beforeEach, vi, Mock } from 'vitest';
import { CloseRaceEventStewardingUseCase, type CloseRaceEventStewardingResult } from './CloseRaceEventStewardingUseCase';
import type { IRaceEventRepository } from '../../domain/repositories/IRaceEventRepository';
import type { IRaceRegistrationRepository } from '../../domain/repositories/IRaceRegistrationRepository';
import type { IPenaltyRepository } from '../../domain/repositories/IPenaltyRepository';
import type { DomainEventPublisher } from '@core/shared/domain/DomainEvent';
import type { Logger } from '@core/shared/application';
import { RaceEvent } from '../../domain/entities/RaceEvent';
import { Session } from '../../domain/entities/Session';
import { SessionType } from '../../domain/value-objects/SessionType';
import type { UseCaseOutputPort } from '@core/shared/application/UseCaseOutputPort';

describe('CloseRaceEventStewardingUseCase', () => {
  let useCase: CloseRaceEventStewardingUseCase;
  let raceEventRepository: {
    findAwaitingStewardingClose: Mock;
    findById: Mock;
    update: Mock;
  };
  let raceRegistrationRepository: {
    getRegisteredDrivers: Mock;
  };
  let penaltyRepository: {
    findByRaceId: Mock;
  };
  let domainEventPublisher: {
    publish: Mock;
  };
  let logger: {
    error: Mock;
  };
  let output: UseCaseOutputPort<CloseRaceEventStewardingResult> & { present: Mock };

  beforeEach(() => {
    raceEventRepository = {
      findAwaitingStewardingClose: vi.fn(),
      findById: vi.fn(),
      update: vi.fn(),
    };
    raceRegistrationRepository = {
      getRegisteredDrivers: vi.fn(),
    };
    penaltyRepository = {
      findByRaceId: vi.fn(),
    };
    domainEventPublisher = {
      publish: vi.fn(),
    };
    logger = {
      error: vi.fn(),
    };
    output = { present: vi.fn() } as unknown as UseCaseOutputPort<CloseRaceEventStewardingResult> & { present: Mock };
    useCase = new CloseRaceEventStewardingUseCase(
      logger as unknown as Logger,
      raceEventRepository as unknown as IRaceEventRepository,
      raceRegistrationRepository as unknown as IRaceRegistrationRepository,
      penaltyRepository as unknown as IPenaltyRepository,
      domainEventPublisher as unknown as DomainEventPublisher,
      output,
    );
  });

  it('should close stewarding for expired events successfully', async () => {
    const raceEvent = RaceEvent.create({
      id: 'event-1',
      seasonId: 'season-1',
      leagueId: 'league-1',
      name: 'Test Event',
      sessions: [
        Session.create({
          id: 'session-1',
          raceEventId: 'event-1',
          sessionType: SessionType.main(),
          scheduledAt: new Date(),
          track: 'Test Track',
          car: 'Test Car',
          status: 'completed',
        }),
      ],
      status: 'awaiting_stewarding',
      stewardingClosesAt: new Date(Date.now() - 1000), // expired
    });

    raceEventRepository.findAwaitingStewardingClose.mockResolvedValue([raceEvent]);
    raceEventRepository.findById.mockResolvedValue(raceEvent.closeStewarding());
    raceRegistrationRepository.getRegisteredDrivers.mockResolvedValue(['driver-1', 'driver-2']);
    penaltyRepository.findByRaceId.mockResolvedValue([]);
    domainEventPublisher.publish.mockResolvedValue(undefined);

    const result = await useCase.execute({ raceId: 'event-1', closedById: 'admin-1' });

    expect(result.isOk()).toBe(true);
    expect(result.unwrap()).toBeUndefined();
    expect(raceEventRepository.findAwaitingStewardingClose).toHaveBeenCalled();
    expect(raceEventRepository.update).toHaveBeenCalledWith(
      expect.objectContaining({ status: 'closed' })
    );
    expect(domainEventPublisher.publish).toHaveBeenCalled();
    expect(output.present).toHaveBeenCalledTimes(1);

    const presentedRace = (output.present as Mock).mock.calls[0]?.[0]?.race as unknown as {
      id?: unknown;
      status?: unknown;
    };
    const presentedId =
      presentedRace?.id && typeof presentedRace.id === 'object' && typeof presentedRace.id.toString === 'function'
        ? presentedRace.id.toString()
        : presentedRace?.id;

    expect(presentedId).toBe('event-1');
    expect(presentedRace?.status).toBe('closed');
  });

  it('should handle no expired events', async () => {
    raceEventRepository.findAwaitingStewardingClose.mockResolvedValue([]);

    const result = await useCase.execute({ raceId: 'event-1', closedById: 'admin-1' });

    expect(result.isOk()).toBe(true);
    expect(result.unwrap()).toBeUndefined();
    expect(raceEventRepository.update).not.toHaveBeenCalled();
    expect(domainEventPublisher.publish).not.toHaveBeenCalled();
    expect(output.present).not.toHaveBeenCalled();
  });

  it('should return error when repository throws', async () => {
    raceEventRepository.findAwaitingStewardingClose.mockRejectedValue(new Error('DB error'));

    const result = await useCase.execute({ raceId: 'event-1', closedById: 'admin-1' });

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr();
    expect(err.code).toBe('REPOSITORY_ERROR');
    if ('details' in err && err.details && typeof err.details === 'object' && 'message' in err.details) {
      expect(err.details.message).toContain('DB error');
    }
    expect(output.present).not.toHaveBeenCalled();
  });
});