import { faker } from '@faker-js/faker';
import type { Driver } from '@core/racing/domain/entities/Driver';
import type { LeagueMembership } from '@core/racing/domain/entities/LeagueMembership';
import { Protest } from '@core/racing/domain/entities/Protest';
import type { Race } from '@core/racing/domain/entities/Race';
import { Penalty } from '@core/racing/domain/entities/penalty/Penalty';
import { seedId } from './SeedIdHelper';

type StewardingSeed = {
  protests: Protest[];
  penalties: Penalty[];
};

export class RacingStewardingFactory {
  constructor(
    private readonly baseDate: Date,
    private readonly persistence: 'postgres' | 'inmemory' = 'inmemory',
  ) {}

  create(races: Race[], drivers: Driver[], leagueMemberships: LeagueMembership[]): StewardingSeed {
    const protests: Protest[] = [];
    const penalties: Penalty[] = [];

    const driversById = new Map<string, Driver>(drivers.map((d) => [d.id.toString(), d]));
    const activeMembersByLeague = new Map<string, string[]>();

    for (const membership of leagueMemberships) {
      if (membership.status.toString() !== 'active') continue;
      const leagueId = membership.leagueId.toString();
      const driverId = membership.driverId.toString();
      if (!driversById.has(driverId)) continue;

      const list = activeMembersByLeague.get(leagueId) ?? [];
      list.push(driverId);
      activeMembersByLeague.set(leagueId, list);
    }

    const racesByLeague = new Map<string, Race[]>();
    for (const race of races) {
      const leagueId = race.leagueId.toString();
      const list = racesByLeague.get(leagueId) ?? [];
      list.push(race);
      racesByLeague.set(leagueId, list);
    }

    // Make league-5 the "busy demo league" for stewarding UIs:
    // - at least one pending/under_review protest
    // - some resolved protests
    // - penalties with and without protest linkage
    const demoLeagueRaces = (racesByLeague.get('league-5') ?? []).slice().sort((a, b) => a.scheduledAt.getTime() - b.scheduledAt.getTime());
    const demoMembers = activeMembersByLeague.get('league-5') ?? [];

    if (demoLeagueRaces.length > 0 && demoMembers.length >= 4) {
      const [firstRace, secondRace, thirdRace] = demoLeagueRaces;

      const protester = demoMembers[0]!;
      const accused = demoMembers[1]!;
      const steward = demoMembers[2]!;
      const spare = demoMembers[3]!;

      if (firstRace) {
        protests.push(
          Protest.create({
            id: seedId('protest-1', this.persistence),
            raceId: firstRace.id.toString(),
            protestingDriverId: protester,
            accusedDriverId: accused,
            incident: {
              lap: 1,
              description: 'Avoidable contact into Turn 1 causing a spin and damage.',
              timeInRace: 85,
            },
            comment: 'I left a car width but got punted. Please review the onboard and external.',
            proofVideoUrl: 'https://example.com/videos/protest-1',
            status: 'pending',
            filedAt: faker.date.recent({ days: 6, refDate: this.baseDate }),
          }),
        );

        // No penalty yet (pending), but seed a direct steward warning on same race.
        penalties.push(
          Penalty.create({
            id: seedId('penalty-1', this.persistence),
            leagueId: 'league-5',
            raceId: firstRace.id.toString(),
            driverId: accused,
            type: 'warning',
            reason: 'Repeated track limits warnings (race director note).',
            issuedBy: steward,
            status: 'applied',
            issuedAt: faker.date.recent({ days: 5, refDate: this.baseDate }),
            appliedAt: faker.date.recent({ days: 5, refDate: this.baseDate }),
            notes: 'Keep it within track limits; further offenses will escalate.',
          }),
        );
      }

      if (secondRace) {
        protests.push(
          Protest.create({
            id: seedId('protest-2', this.persistence),
            raceId: secondRace.id.toString(),
            protestingDriverId: spare,
            accusedDriverId: accused,
            incident: {
              lap: 7,
              description: 'Unsafe rejoin after off-track caused a collision with following traffic.',
              timeInRace: 610,
            },
            proofVideoUrl: 'https://example.com/videos/protest-2',
            status: 'under_review',
            reviewedBy: steward,
            filedAt: faker.date.recent({ days: 12, refDate: this.baseDate }),
          }),
        );

        // Under review penalty still pending (linked to protest)
        penalties.push(
          Penalty.create({
            id: seedId('penalty-2', this.persistence),
            leagueId: 'league-5',
            raceId: secondRace.id.toString(),
            driverId: accused,
            type: 'time_penalty',
            value: 10,
            reason: 'Unsafe rejoin (protest pending review)',
            protestId: seedId('protest-2', this.persistence),
            issuedBy: steward,
            status: 'pending',
            issuedAt: faker.date.recent({ days: 10, refDate: this.baseDate }),
            notes: 'Will be applied to results if upheld.',
          }),
        );
      }

      if (thirdRace) {
        const upheld = Protest.create({
          id: seedId('protest-3', this.persistence),
          raceId: thirdRace.id.toString(),
          protestingDriverId: protester,
          accusedDriverId: spare,
          incident: {
            lap: 12,
            description: 'Brake check on the straight leading to rear-end contact.',
            timeInRace: 1280,
          },
          status: 'upheld',
          reviewedBy: steward,
          decisionNotes: 'Brake check is not acceptable. Penalty applied.',
          reviewedAt: faker.date.recent({ days: 20, refDate: this.baseDate }),
          filedAt: faker.date.recent({ days: 25, refDate: this.baseDate }),
        });

        protests.push(upheld);

        penalties.push(
          Penalty.create({
            id: seedId('penalty-3', this.persistence),
            leagueId: 'league-5',
            raceId: thirdRace.id.toString(),
            driverId: spare,
            type: 'points_deduction',
            value: 5,
            reason: upheld.incident.description.toString(),
            protestId: upheld.id,
            issuedBy: steward,
            status: 'applied',
            issuedAt: faker.date.recent({ days: 20, refDate: this.baseDate }),
            appliedAt: faker.date.recent({ days: 19, refDate: this.baseDate }),
            notes: 'Applied after stewarding decision.',
          }),
        );

        const dismissed = Protest.create({
          id: seedId('protest-4', this.persistence),
          raceId: thirdRace.id.toString(),
          protestingDriverId: accused,
          accusedDriverId: protester,
          incident: {
            lap: 3,
            description: 'Minor side-by-side contact with no loss of control.',
            timeInRace: 240,
          },
          status: 'dismissed',
          reviewedBy: steward,
          decisionNotes: 'Racing incident, no further action.',
          reviewedAt: faker.date.recent({ days: 18, refDate: this.baseDate }),
          filedAt: faker.date.recent({ days: 22, refDate: this.baseDate }),
        });

        protests.push(dismissed);
      }
    }

    // Fill other leagues lightly with variety (including awaiting_defense and withdrawn).
    for (const [leagueId, leagueRaces] of racesByLeague.entries()) {
      if (leagueId === 'league-5') continue;
      const members = activeMembersByLeague.get(leagueId) ?? [];
      if (members.length < 3) continue;

      const completedRaces = leagueRaces.filter((r) => r.status.toString() === 'completed').slice(0, 2);
      if (completedRaces.length === 0) continue;

      const [race] = completedRaces;
      if (!race) continue;

      const a = members[0]!;
      const b = members[1]!;
      const steward = members[2]!;

      const seedOne = faker.number.int({ min: 0, max: 2 }) === 0;
      if (!seedOne) continue;

      const status = faker.helpers.arrayElement(['awaiting_defense', 'withdrawn'] as const);

      const protest = Protest.create({
        id: seedId(`protest-${leagueId}-${race.id.toString()}`, this.persistence),
        raceId: race.id.toString(),
        protestingDriverId: a,
        accusedDriverId: b,
        incident: {
          lap: faker.number.int({ min: 1, max: 20 }),
          description: faker.helpers.arrayElement([
            'Divebomb attempt from too far back caused avoidable contact.',
            'Blocking under braking and changing line multiple times.',
            'Track limits abuse provided a sustained advantage.',
            'Blue flag was ignored causing unnecessary time loss.',
          ]),
        },
        status,
        ...(status === 'awaiting_defense'
          ? {
              defenseRequestedBy: steward,
              defenseRequestedAt: faker.date.recent({ days: 3, refDate: this.baseDate }),
            }
          : {}),
        filedAt: faker.date.recent({ days: 14, refDate: this.baseDate }),
      });

      protests.push(protest);

      if (status === 'withdrawn') {
        // A non-protest-linked penalty can still exist for the same race.
        penalties.push(
          Penalty.create({
            id: seedId(`penalty-${leagueId}-${race.id.toString()}`, this.persistence),
            leagueId,
            raceId: race.id.toString(),
            driverId: b,
            type: faker.helpers.arrayElement(['grid_penalty', 'license_points'] as const),
            value: faker.number.int({ min: 2, max: 5 }),
            reason: 'Steward discretion: repeated behavior across sessions.',
            issuedBy: steward,
            status: 'applied',
            issuedAt: faker.date.recent({ days: 9, refDate: this.baseDate }),
            appliedAt: faker.date.recent({ days: 9, refDate: this.baseDate }),
          }),
        );
      }
    }

    // Add comprehensive penalty coverage for all types and statuses
    // This ensures we have examples of every penalty type in every status
    const penaltyTypes = ['time_penalty', 'grid_penalty', 'points_deduction', 'disqualification', 'warning', 'license_points', 'probation', 'fine', 'race_ban'] as const;
    const penaltyStatuses = ['pending', 'applied', 'appealed', 'overturned'] as const;

    // Get some races and members for penalty seeding
    const allLeagueIds = Array.from(racesByLeague.keys());

    for (const leagueId of allLeagueIds) {
      const members = activeMembersByLeague.get(leagueId) ?? [];
      if (members.length < 2) continue;

      const leagueRaces = racesByLeague.get(leagueId) ?? [];
      const completedRaces = leagueRaces.filter((r) => r.status.toString() === 'completed');
      if (completedRaces.length === 0) continue;

      const steward = members[0]!;
      const targetDriver = members[1]!;

      // Create one penalty for each type/status combination
      let penaltyIndex = 0;
      for (const type of penaltyTypes) {
        for (const status of penaltyStatuses) {
          // Skip some combinations to avoid too many records
          if (faker.number.int({ min: 0, max: 2 }) > 0) continue;

          const race = faker.helpers.arrayElement(completedRaces);
          const value = type === 'time_penalty' ? faker.number.int({ min: 5, max: 30 }) :
                        type === 'grid_penalty' ? faker.number.int({ min: 1, max: 5 }) :
                        type === 'points_deduction' ? faker.number.int({ min: 2, max: 10 }) :
                        type === 'license_points' ? faker.number.int({ min: 1, max: 4 }) :
                        type === 'fine' ? faker.number.int({ min: 50, max: 500 }) :
                        type === 'race_ban' ? faker.number.int({ min: 1, max: 3 }) :
                        type === 'warning' ? 1 :
                        1; // disqualification, probation have no value

          const penaltyData: {
            id: string;
            leagueId: string;
            raceId: string;
            driverId: string;
            type: typeof penaltyTypes[number];
            value?: number;
            reason: string;
            issuedBy: string;
            status: typeof penaltyStatuses[number];
            issuedAt: Date;
            appliedAt?: Date;
            notes?: string;
          } = {
            id: seedId(`penalty-${leagueId}-${type}-${status}-${penaltyIndex}`, this.persistence),
            leagueId,
            raceId: race.id.toString(),
            driverId: targetDriver,
            type,
            reason: this.getPenaltyReason(type),
            issuedBy: steward,
            status,
            issuedAt: faker.date.recent({ days: faker.number.int({ min: 1, max: 30 }), refDate: this.baseDate }),
          };

          // Add value only for types that require it
          if (type !== 'warning') {
            penaltyData.value = value;
          }

          if (status === 'applied') {
            penaltyData.appliedAt = faker.date.recent({ days: faker.number.int({ min: 1, max: 20 }), refDate: this.baseDate });
          }

          if (type === 'race_ban') {
            penaltyData.notes = 'Multiple serious violations';
          }

          penalties.push(Penalty.create(penaltyData));
          penaltyIndex++;
        }
      }
    }

    return { protests, penalties };
  }

  private getPenaltyReason(type: string): string {
    const reasons = {
      time_penalty: ['Avoidable contact', 'Track limits abuse', 'Unsafe rejoin', 'Blocking'],
      grid_penalty: ['Qualifying infringement', 'Parc fermé violation', 'Practice session breach'],
      points_deduction: ['Serious breach of rules', 'Multiple incidents', 'Unsportsmanlike conduct'],
      disqualification: ['Severe dangerous driving', 'Gross misconduct', 'Multiple serious violations'],
      warning: ['Track limits reminder', 'Minor contact', 'Procedure reminder'],
      license_points: ['General misconduct', 'Minor incidents', 'Warning escalation'],
      probation: ['Pattern of minor violations', 'Behavioral concerns', 'Conditional status'],
      fine: ['Financial penalty for rule breach', 'Administrative violation', 'Late entry fee'],
      race_ban: ['Multiple race bans', 'Severe dangerous driving', 'Gross misconduct'],
    };
    return faker.helpers.arrayElement(reasons[type as keyof typeof reasons] || ['Rule violation']);
  }
}