import { UserRatingRepository } from '../../domain/repositories/UserRatingRepository';
import { RatingEventFactory } from '../../domain/services/RatingEventFactory';
import { RecordRaceRatingEventsInput, RecordRaceRatingEventsOutput } from '../dtos/RecordRaceRatingEventsDto';
import { RaceResultsProvider } from '../ports/RaceResultsProvider';
import { AppendRatingEventsUseCase } from './AppendRatingEventsUseCase';

/**
 * Use Case: RecordRaceRatingEventsUseCase
 * 
 * Records rating events for a completed race.
 * Follows CQRS Light: command side operation.
 * 
 * Flow:
 * 1. Load race results from racing context
 * 2. Compute SoF if needed (platform-only)
 * 3. Factory creates rating events
 * 4. Append to ledger via AppendRatingEventsUseCase
 * 5. Recompute snapshots
 * 
 * Per plans section 7.1.1 and 10.1
 */
export class RecordRaceRatingEventsUseCase {
  constructor(
    private readonly raceResultsProvider: RaceResultsProvider,
    private readonly userRatingRepository: UserRatingRepository,
    private readonly appendRatingEventsUseCase: AppendRatingEventsUseCase,
  ) {}

  async execute(input: RecordRaceRatingEventsInput): Promise<RecordRaceRatingEventsOutput> {
    const errors: string[] = [];
    const driversUpdated: string[] = [];
    let totalEventsCreated = 0;

    try {
      // 1. Load race results
      const raceResults = await this.raceResultsProvider.getRaceResults(input.raceId);
      
      if (!raceResults) {
        return {
          success: false,
          raceId: input.raceId,
          eventsCreated: 0,
          driversUpdated: [],
          errors: ['Race results not found'],
        };
      }

      if (raceResults.results.length === 0) {
        return {
          success: false,
          raceId: input.raceId,
          eventsCreated: 0,
          driversUpdated: [],
          errors: ['No results found for race'],
        };
      }

      // 2. Compute SoF if not provided (platform-only approach)
      // Use existing user ratings as platform strength indicators
      const resultsWithSof = await this.computeSoFIfNeeded(raceResults);

      // 3. Create rating events using factory
      const eventsByUser = RatingEventFactory.createDrivingEventsFromRace({
        raceId: input.raceId,
        results: resultsWithSof,
      });

      if (eventsByUser.size === 0) {
        return {
          success: true,
          raceId: input.raceId,
          eventsCreated: 0,
          driversUpdated: [],
          errors: [],
        };
      }

      // 4. Process each driver's events
      for (const [userId, events] of eventsByUser) {
        try {
          // Use AppendRatingEventsUseCase to handle ledger and snapshot
          const result = await this.appendRatingEventsUseCase.execute({
            userId,
            events: events.map(event => ({
              userId: event.userId,
              dimension: event.dimension.value,
              delta: event.delta.value,
              weight: event.weight || 1,
              sourceType: event.source.type,
              sourceId: event.source.id,
              reasonCode: event.reason.code,
              reasonSummary: event.reason.summary,
              reasonDetails: event.reason.details,
              occurredAt: event.occurredAt.toISOString(),
            })),
          });

          if (result.snapshotUpdated) {
            driversUpdated.push(userId);
            totalEventsCreated += result.events.length;
          }
        } catch (error) {
          const errorMsg = `Failed to process events for user ${userId}: ${error instanceof Error ? error.message : 'Unknown error'}`;
          errors.push(errorMsg);
        }
      }

      return {
        success: errors.length === 0,
        raceId: input.raceId,
        eventsCreated: totalEventsCreated,
        driversUpdated,
        errors,
      };

    } catch (error) {
      const errorMsg = `Failed to record race rating events: ${error instanceof Error ? error.message : 'Unknown error'}`;
      errors.push(errorMsg);

      return {
        success: false,
        raceId: input.raceId,
        eventsCreated: 0,
        driversUpdated: [],
        errors,
      };
    }
  }

  /**
   * Compute Strength of Field if not provided in results
   * Uses platform driving ratings (existing user ratings)
   */
  private async computeSoFIfNeeded(raceResults: {
    raceId: string;
    results: Array<{
      userId: string;
      startPos: number;
      finishPos: number;
      incidents: number;
      status: 'finished' | 'dnf' | 'dns' | 'dsq' | 'afk';
      sof?: number;
    }>;
  }): Promise<Array<{
    userId: string;
    startPos: number;
    finishPos: number;
    incidents: number;
    status: 'finished' | 'dnf' | 'dns' | 'dsq' | 'afk';
    sof: number;
  }>> {
    // Check if all results have sof
    const hasAllSof = raceResults.results.every(r => r.sof !== undefined);
    
    if (hasAllSof) {
      return raceResults.results.map(r => ({ ...r, sof: r.sof! }));
    }

    // Need to compute SoF - get user ratings for all drivers
    const userIds = raceResults.results.map(r => r.userId);
    const ratingsMap = new Map<string, number>();

    // Get ratings for all drivers
    for (const userId of userIds) {
      const userRating = await this.userRatingRepository.findByUserId(userId);
      if (userRating) {
        ratingsMap.set(userId, userRating.driver.value);
      } else {
        // Default rating for new drivers
        ratingsMap.set(userId, 50);
      }
    }

    // Calculate average rating as SoF
    const ratings = Array.from(ratingsMap.values());
    const sof = ratings.length > 0 
      ? Math.round(ratings.reduce((sum, r) => sum + r, 0) / ratings.length)
      : 50;

    // Add SoF to each result
    return raceResults.results.map(r => ({
      ...r,
      sof: r.sof !== undefined ? r.sof : sof,
    }));
  }
}