import { Driver } from '@core/racing/domain/entities/Driver';
import { Race } from '@core/racing/domain/entities/Race';
import { Result as RaceResult } from '@core/racing/domain/entities/result/Result';
import { seedId } from './SeedIdHelper';

export class RacingResultFactory {
  constructor(private readonly persistence: 'postgres' | 'inmemory' = 'inmemory') {}

  create(drivers: Driver[], races: Race[]): RaceResult[] {
    const results: RaceResult[] = [];
    const completed = races.filter((r) => r.status.toString() === 'completed');

    for (const race of completed) {
      if (drivers.length === 0) continue;

      const rng = this.mulberry32(this.hashToSeed(`${race.id}:${race.leagueId}:${race.trackId}`));

      const minSize = Math.min(6, drivers.length);
      const maxSize = Math.min(26, drivers.length);
      const participantCount = Math.max(
        minSize,
        Math.min(maxSize, minSize + Math.floor(rng() * (maxSize - minSize + 1))),
      );

      const offset = Math.floor(rng() * drivers.length);
      const participants = Array.from({ length: participantCount }, (_, idx) => drivers[(offset + idx) % drivers.length]!);

      const finishOrder = [...participants];
      this.shuffleInPlace(finishOrder, rng);

      const gridOrder = [...participants];
      this.shuffleInPlace(gridOrder, rng);

      const baseLap = 82_000 + Math.floor(rng() * 12_000); // 1:22.000 - 1:34.000-ish

      for (let idx = 0; idx < finishOrder.length; idx++) {
        const driver = finishOrder[idx]!;
        const position = idx + 1;

        const startPosition = gridOrder.findIndex(d => d.id.toString() === driver.id.toString()) + 1;

        const lapJitter = Math.floor(rng() * 900);
        const fastestLap = baseLap + lapJitter + (position - 1) * 35;

        const incidents =
          rng() < 0.55
            ? 0
            : rng() < 0.85
              ? 1
              : rng() < 0.95
                ? 2
                : 3 + Math.floor(rng() * 6);

        results.push(
          RaceResult.create({
            id: seedId(`${race.id}:${driver.id}`, this.persistence),
            raceId: race.id,
            driverId: driver.id,
            position,
            startPosition: Math.max(1, startPosition),
            fastestLap,
            incidents,
          }),
        );
      }
    }

    return results;
  }

  private shuffleInPlace<T>(items: T[], rng: () => number): void {
    for (let i = items.length - 1; i > 0; i--) {
      const j = Math.floor(rng() * (i + 1));
      const tmp = items[i]!;
      items[i] = items[j]!;
      items[j] = tmp;
    }
  }

  private hashToSeed(input: string): number {
    let hash = 2166136261;
    for (let i = 0; i < input.length; i++) {
      hash ^= input.charCodeAt(i);
      hash = Math.imul(hash, 16777619);
    }
    return hash >>> 0;
  }

  private mulberry32(seed: number): () => number {
    let a = seed >>> 0;
    return () => {
      a |= 0;
      a = (a + 0x6D2B79F5) | 0;
      let t = Math.imul(a ^ (a >>> 15), 1 | a);
      t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
      return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
    };
  }
}