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 { NotificationService } from '../../../notifications/application/ports/NotificationService';
import type { NotificationType } from '../../../notifications/domain/types/NotificationTypes';
import type { RaceEvent } from '../../domain/entities/RaceEvent';
import { IncidentCount } from '../../domain/entities/result/IncidentCount';
import { Position } from '../../domain/entities/result/Position';
import type { Result as RaceResult } from '../../domain/entities/result/Result';
import { LeagueMembershipRepository } from '../../domain/repositories/LeagueMembershipRepository';
import { LeagueRepository } from '../../domain/repositories/LeagueRepository';
import { RaceEventRepository } from '../../domain/repositories/RaceEventRepository';
import { ResultRepository } from '../../domain/repositories/ResultRepository';
import { isLeagueStewardOrHigherRole } from '../../domain/types/LeagueRoles';

export type SendFinalResultsInput = {
  leagueId: string;
  raceId: string;
  triggeredById: string;
};

export type SendFinalResultsResult = {
  leagueId: string;
  raceId: string;
  notificationsSent: number;
};

export type SendFinalResultsErrorCode =
  | 'LEAGUE_NOT_FOUND'
  | 'RACE_NOT_FOUND'
  | 'INSUFFICIENT_PERMISSIONS'
  | 'RESULTS_NOT_FINAL'
  | 'REPOSITORY_ERROR';

/**
 * Use Case: SendFinalResultsUseCase
 *
 * Sends final results notifications to all drivers who participated
 * in the main race session for a given race event.
 */
export class SendFinalResultsUseCase {
  constructor(private readonly notificationService: NotificationService,
    private readonly raceEventRepository: RaceEventRepository,
    private readonly resultRepository: ResultRepository,
    private readonly leagueRepository: LeagueRepository,
    private readonly membershipRepository: LeagueMembershipRepository,
    private readonly logger: Logger) {}

  async execute(
    input: SendFinalResultsInput,
  ): Promise<Result<SendFinalResultsResult, ApplicationErrorCode<SendFinalResultsErrorCode, { message: string }>>> {
    try {
      const league = await this.leagueRepository.findById(input.leagueId);
      if (!league) {
        return Result.err({ code: 'LEAGUE_NOT_FOUND', details: { message: 'League not found' } });
      }

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

      const membership = await this.membershipRepository.getMembership(
        league.id.toString(),
        input.triggeredById,
      );
      if (!membership || !isLeagueStewardOrHigherRole(membership.role.toString())) {
        return Result.err({
          code: 'INSUFFICIENT_PERMISSIONS',
          details: { message: 'Insufficient permissions to send final results' },
        });
      }

      if (raceEvent.status !== 'closed') {
        return Result.err({
          code: 'RESULTS_NOT_FINAL',
          details: { message: 'Race results are not in a final state' },
        });
      }

      const mainRaceSession = raceEvent.getMainRaceSession();
      if (!mainRaceSession) {
        return Result.err({
          code: 'RESULTS_NOT_FINAL',
          details: { message: 'Main race session not found for race event' },
        });
      }

      const results = await this.resultRepository.findByRaceId(mainRaceSession.id);

      let notificationsSent = 0;

      for (const driverResult of results) {
        await this.sendFinalResultsNotification(
          driverResult.driverId.toString(),
          raceEvent,
          driverResult,
          league.id.toString(),
          false,
        );
        notificationsSent += 1;
      }

      const result: SendFinalResultsResult = {
        leagueId: league.id.toString(),
        raceId: raceEvent.id,
        notificationsSent,
      };

      return Result.ok(result);
    } catch (error: unknown) {
      const message = error instanceof Error ? error.message : 'Failed to send final results';
      this.logger.error('SendFinalResultsUseCase.execute failed', error instanceof Error ? error : undefined);
      return Result.err({ code: 'REPOSITORY_ERROR', details: { message } });
    }
  }

  private async sendFinalResultsNotification(
    driverId: string,
    raceEvent: RaceEvent,
    driverResult: RaceResult | undefined,
    leagueId: string,
    hadPenaltiesApplied: boolean,
  ): Promise<void> {
    const position = driverResult?.position ?? 'DNF';
    const positionChange = driverResult?.getPositionChange() ?? 0;
    const incidents = driverResult?.incidents ?? 0;

    const finalRatingChange = this.calculateFinalRatingChange(
      driverResult?.position?.toNumber(),
      driverResult?.incidents?.toNumber(),
      hadPenaltiesApplied,
    );

    const title = `Final Results: ${raceEvent.name}`;
    const positionValue = position instanceof Position ? position.toNumber() : position;
    const incidentValue = incidents instanceof IncidentCount ? incidents.toNumber() : incidents;
    const body = this.buildFinalResultsBody(
      positionValue,
      positionChange,
      incidentValue,
      finalRatingChange,
      hadPenaltiesApplied,
    );

    await this.notificationService.sendNotification({
      recipientId: driverId,
      type: 'race_final_results' as NotificationType,
      title,
      body,
      channel: 'in_app',
      urgency: 'modal',
      data: {
        raceEventId: raceEvent.id,
        sessionId: raceEvent.getMainRaceSession()?.id ?? '',
        leagueId,
        position: positionValue,
        positionChange,
        incidents: incidentValue,
        finalRatingChange,
        hadPenaltiesApplied,
      },
      actions: [
        {
          label: 'View Championship Standings',
          type: 'primary',
          href: `/leagues/${leagueId}/standings`,
        },
        {
          label: 'Race Details',
          type: 'secondary',
          href: `/leagues/${leagueId}/races/${raceEvent.id}`,
        },
      ],
      requiresResponse: false,
    });
  }

  private buildFinalResultsBody(
    position: number | 'DNF',
    positionChange: number,
    incidents: number,
    finalRatingChange: number,
    hadPenaltiesApplied: boolean,
  ): string {
    const positionText = position === 'DNF' ? 'DNF' : `P${position}`;
    const positionChangeText = positionChange > 0 ? `+${positionChange}` : positionChange < 0 ? `${positionChange}` : '±0';
    const incidentsText = incidents === 0 ? 'Clean race!' : `${incidents} incident${incidents > 1 ? 's' : ''}`;
    const ratingText = finalRatingChange >= 0 ? `+${finalRatingChange} rating` : `${finalRatingChange} rating`;
    const penaltyText = hadPenaltiesApplied ? ' (including stewarding adjustments)' : '';

    return `Final result: ${positionText} (${positionChangeText} positions). ${incidentsText} ${ratingText}${penaltyText}.`;
  }

  private calculateFinalRatingChange(
    position?: number,
    incidents?: number,
    hadPenaltiesApplied?: boolean,
  ): number {
    if (!position) return -10;

    const baseChange = position <= 3 ? 25 : position <= 10 ? 10 : -5;
    const positionBonus = Math.max(0, (20 - position) * 2);
    const incidentPenalty = (incidents ?? 0) * -5;

    let finalChange = baseChange + positionBonus + incidentPenalty;

    if (hadPenaltiesApplied) {
      finalChange = Math.max(finalChange - 5, -20);
    }

    return finalChange;
  }
}