import { DomainEventPublisher } from '@core/shared/domain/DomainEvent';
import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import type { RaceEvent } from '../../domain/entities/RaceEvent';
import { RaceEventStewardingClosedEvent } from '../../domain/events/RaceEventStewardingClosed';
import { PenaltyRepository } from '../../domain/repositories/PenaltyRepository';
import { RaceEventRepository } from '../../domain/repositories/RaceEventRepository';
import { RaceRegistrationRepository } from '../../domain/repositories/RaceRegistrationRepository';

export type CloseRaceEventStewardingInput = {
  raceId: string;
  closedById: string;
};

export type CloseRaceEventStewardingResult = {
  race: RaceEvent;
};

/**
 * Use Case: CloseRaceEventStewardingUseCase
 *
 * Scheduled job that checks for race events with expired stewarding windows
 * and closes them, triggering final results notifications.
 *
 * This would typically be run by a scheduled job (e.g., every 5 minutes)
 * to automatically close stewarding windows based on league configuration.
 */
export class CloseRaceEventStewardingUseCase {
  constructor(
    private readonly logger: Logger,
    private readonly raceEventRepository: RaceEventRepository,
    private readonly raceRegistrationRepository: RaceRegistrationRepository,
    private readonly penaltyRepository: PenaltyRepository,
    private readonly domainEventPublisher: DomainEventPublisher,
  ) {}

  async execute(
    input: CloseRaceEventStewardingInput,
  ): Promise<
    Result<
      CloseRaceEventStewardingResult,
      ApplicationErrorCode<'RACE_NOT_FOUND' | 'STEWARDING_ALREADY_CLOSED' | 'REPOSITORY_ERROR'>
    >
  > {
    void input;
    try {
      // Find all race events awaiting stewarding that have expired windows
      const expiredEvents = await this.raceEventRepository.findAwaitingStewardingClose();

      const closedRaceEventIds: string[] = [];

      for (const raceEvent of expiredEvents) {
        await this.closeStewardingForRaceEvent(raceEvent);
        closedRaceEventIds.push(raceEvent.id);
      }

      // When multiple race events are processed, we return the last closed event for simplicity
      const lastClosedEventId = closedRaceEventIds[closedRaceEventIds.length - 1];
      if (lastClosedEventId) {
        const lastClosedEvent = await this.raceEventRepository.findById(lastClosedEventId);
        if (lastClosedEvent) {
          const result: CloseRaceEventStewardingResult = {
            race: lastClosedEvent,
          };
          return Result.ok(result);
        }
      }

      // If no events were closed, return an error
      return Result.err({
        code: 'RACE_NOT_FOUND',
        details: { message: 'No race events found to close stewarding for' },
      });
    } catch (error) {
      this.logger.error('Failed to close race event stewarding', error instanceof Error ? error : new Error(String(error)));
      return Result.err({
        code: 'REPOSITORY_ERROR',
        details: {
          message: error instanceof Error ? error.message : String(error),
        },
      });
    }
  }

  private async closeStewardingForRaceEvent(raceEvent: RaceEvent): Promise<void> {
    try {
      // Close the stewarding window
      const closedRaceEvent = raceEvent.closeStewarding();
      await this.raceEventRepository.update(closedRaceEvent);

      // Get list of participating driver IDs
      const driverIds = await this.getParticipatingDriverIds(raceEvent);

      // Check if any penalties were applied during stewarding
      const hadPenaltiesApplied = await this.checkForAppliedPenalties(raceEvent);

      // Publish domain event to trigger final results notifications
      const event = new RaceEventStewardingClosedEvent({
        raceEventId: raceEvent.id,
        leagueId: raceEvent.leagueId,
        seasonId: raceEvent.seasonId,
        closedAt: new Date(),
        driverIds,
        hadPenaltiesApplied,
      });

      await this.domainEventPublisher.publish(event);

    } catch (error) {
      this.logger.error(`Failed to close stewarding for race event ${raceEvent.id}`, error instanceof Error ? error : new Error(String(error)));
      // In production, this would trigger alerts/monitoring
      throw error;
    }
  }

  private async getParticipatingDriverIds(raceEvent: RaceEvent): Promise<string[]> {
    return await this.raceRegistrationRepository.getRegisteredDrivers(raceEvent.id);
  }

  private async checkForAppliedPenalties(raceEvent: RaceEvent): Promise<boolean> {
    const penalties = await this.penaltyRepository.findByRaceId(raceEvent.id);
    return penalties.length > 0;
  }
}