import type { RaceResultsProvider } from '@core/identity/application/ports/RaceResultsProvider';
import { RatingUpdateService } from '@core/identity/domain/services/RatingUpdateService';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { Result as RaceResult } from '../../domain/entities/result/Result';
import { Standing } from '../../domain/entities/Standing';
import type { RaceRegistrationRepository } from '../../domain/repositories/RaceRegistrationRepository';
import type { RaceRepository } from '../../domain/repositories/RaceRepository';
import type { ResultRepository } from '../../domain/repositories/ResultRepository';
import type { StandingRepository } from '../../domain/repositories/StandingRepository';
import { RaceResultGenerator } from '../utils/RaceResultGenerator';

export interface CompleteRaceWithRatingsInput {
  raceId: string;
}

export type CompleteRaceWithRatingsResult = {
  raceId: string;
  ratingsUpdatedForDriverIds: string[];
};

export type CompleteRaceWithRatingsErrorCode =
  | 'RACE_NOT_FOUND'
  | 'NO_REGISTERED_DRIVERS'
  | 'ALREADY_COMPLETED'
  | 'RATING_UPDATE_FAILED'
  | 'REPOSITORY_ERROR';

interface DriverRatingProvider {
  getRatings(driverIds: string[]): Map<string, number>;
}

/**
 * Enhanced CompleteRaceUseCase that includes rating updates.
 * EVOLVED (Slice 7): Now uses ledger-based rating updates for transparency and auditability.
 */
export class CompleteRaceUseCaseWithRatings {
  constructor(private readonly raceRepository: RaceRepository,
    private readonly raceRegistrationRepository: RaceRegistrationRepository,
    private readonly resultRepository: ResultRepository,
    private readonly standingRepository: StandingRepository,
    private readonly driverRatingProvider: DriverRatingProvider,
    private readonly ratingUpdateService: RatingUpdateService,
    private readonly raceResultsProvider?: RaceResultsProvider, // Optional: for new ledger flow
  ) {}

  async execute(command: CompleteRaceWithRatingsInput): Promise<
    Result<CompleteRaceWithRatingsResult, ApplicationErrorCode<CompleteRaceWithRatingsErrorCode, { message: string }>>
  > {
    try {
      const { raceId } = command;

      const race = await this.raceRepository.findById(raceId);
      if (!race) {
        return Result.err({
          code: 'RACE_NOT_FOUND',
          details: { message: 'Race not found' }
        });
      }

      const raceStatus = (race as unknown as { status?: unknown }).status;
      const isCompleted =
        typeof raceStatus === 'string'
          ? raceStatus === 'completed'
          : typeof (raceStatus as { isCompleted?: unknown })?.isCompleted === 'function'
            ? (raceStatus as { isCompleted: () => boolean }).isCompleted()
            : typeof (raceStatus as { toString?: unknown })?.toString === 'function'
              ? (raceStatus as { toString: () => string }).toString() === 'completed'
              : false;

      if (isCompleted) {
        return Result.err({
          code: 'ALREADY_COMPLETED',
          details: { message: 'Race already completed' }
        });
      }

      const registeredDriverIds = await this.raceRegistrationRepository.getRegisteredDrivers(raceId);
      if (registeredDriverIds.length === 0) {
        return Result.err({
          code: 'NO_REGISTERED_DRIVERS',
          details: { message: 'No registered drivers' }
        });
      }

      const driverRatings = this.driverRatingProvider.getRatings(registeredDriverIds);

      const results = RaceResultGenerator.generateRaceResults(raceId, registeredDriverIds, driverRatings);

      for (const result of results) {
        await this.resultRepository.create(result);
      }

      await this.updateStandings(race.leagueId, results);

      const ratingsUpdatedForDriverIds: string[] = [];

      // SLICE 7: Use new ledger-based approach if raceResultsProvider is available
      // This provides backward compatibility while evolving to event-driven architecture
      try {
        if (this.raceResultsProvider) {
          // NEW LEDGER APPROACH: Use RecordRaceRatingEventsUseCase via RatingUpdateService
          const raceResultsData = {
            raceId,
            results: results.map(result => ({
              userId: result.driverId.toString(),
              startPos: result.startPosition.toNumber(),
              finishPos: result.position.toNumber(),
              incidents: result.incidents.toNumber(),
              status: 'finished' as const, // RaceResultGenerator only generates finished results
            })),
          };

          try {
            const ratingResult = await this.ratingUpdateService.recordRaceRatingEvents(
              raceId,
              raceResultsData.results
            );

            if (!ratingResult.success) {
              console.warn(`[Slice 7] Ledger-based rating update failed for race ${raceId}, falling back to legacy method`);
              await this.updateDriverRatingsLegacy(results, registeredDriverIds.length);
              ratingsUpdatedForDriverIds.push(...registeredDriverIds);
            } else {
              ratingsUpdatedForDriverIds.push(...(ratingResult.driversUpdated || []));
            }
          } catch (error) {
            // If ledger approach throws error, fall back to legacy method
            console.warn(`[Slice 7] Ledger-based rating update threw error for race ${raceId}, falling back to legacy method: ${error instanceof Error ? error.message : 'Unknown error'}`);
            await this.updateDriverRatingsLegacy(results, registeredDriverIds.length);
            ratingsUpdatedForDriverIds.push(...registeredDriverIds);
          }
        } else {
          // BACKWARD COMPATIBLE: Use legacy direct update approach
          await this.updateDriverRatingsLegacy(results, registeredDriverIds.length);
          ratingsUpdatedForDriverIds.push(...registeredDriverIds);
        }
      } catch (error) {
        return Result.err({
          code: 'RATING_UPDATE_FAILED',
          details: {
            message: error instanceof Error ? error.message : 'Failed to update driver ratings',
          },
        });
      }

      const completedRace = race.complete();
      await this.raceRepository.update(completedRace);

      return Result.ok({
        raceId,
        ratingsUpdatedForDriverIds,
      });
    } catch (error) {
      return Result.err({
        code: 'REPOSITORY_ERROR',
        details: {
          message: error instanceof Error ? error.message : 'Unknown error',
        },
      });
    }
  }

  private async updateStandings(leagueId: string, results: RaceResult[]): Promise<void> {
    const resultsByDriver = new Map<string, RaceResult[]>();
    for (const result of results) {
      const driverIdStr = result.driverId.toString();
      const existing = resultsByDriver.get(driverIdStr) || [];
      existing.push(result);
      resultsByDriver.set(driverIdStr, existing);
    }

    for (const [driverIdStr, driverResults] of resultsByDriver) {
      let standing = await this.standingRepository.findByDriverIdAndLeagueId(driverIdStr, leagueId);

      if (!standing) {
        standing = Standing.create({
          leagueId,
          driverId: driverIdStr,
        });
      }

      for (const result of driverResults) {
        standing = standing.addRaceResult(result.position.toNumber(), {
          1: 25,
          2: 18,
          3: 15,
          4: 12,
          5: 10,
          6: 8,
          7: 6,
          8: 4,
          9: 2,
          10: 1,
        });
      }

      await this.standingRepository.save(standing);
    }
  }

  /**
   * Legacy rating update method (BACKWARD COMPATIBLE)
   * Uses direct updates via RatingUpdateService
   */
  private async updateDriverRatingsLegacy(results: RaceResult[], totalDrivers: number): Promise<void> {
    const driverResults = results.map((result) => ({
      driverId: result.driverId.toString(),
      position: result.position.toNumber(),
      totalDrivers,
      incidents: result.incidents.toNumber(),
      startPosition: result.startPosition.toNumber(),
    }));

    await this.ratingUpdateService.updateDriverRatingsAfterRace(driverResults);
  }
}
