import type { DataSource } from 'typeorm';
import { In } from 'typeorm';

import { Standing } from '@core/racing/domain/entities/Standing';
import type { IStandingRepository } from '@core/racing/domain/repositories/IStandingRepository';

import { LeagueOrmEntity } from '../entities/LeagueOrmEntity';
import { RaceOrmEntity } from '../entities/RaceOrmEntity';
import { ResultOrmEntity } from '../entities/ResultOrmEntity';
import { StandingOrmEntity } from '../entities/StandingOrmEntity';
import { LeagueOrmMapper } from '../mappers/LeagueOrmMapper';
import { ResultOrmMapper } from '../mappers/ResultOrmMapper';
import { StandingOrmMapper } from '../mappers/StandingOrmMapper';

export class TypeOrmStandingRepository implements IStandingRepository {
  constructor(
    private readonly dataSource: DataSource,
    private readonly standingMapper: StandingOrmMapper,
    private readonly resultMapper: ResultOrmMapper,
    private readonly leagueMapper: LeagueOrmMapper,
    private readonly pointsSystems: Record<string, Record<number, number>>,
  ) {}

  async findByLeagueId(leagueId: string): Promise<Standing[]> {
    const repo = this.dataSource.getRepository(StandingOrmEntity);
    const entities = await repo.find({ where: { leagueId }, order: { position: 'ASC' } });
    return entities.map((e) => this.standingMapper.toDomain(e));
  }

  async findByDriverIdAndLeagueId(driverId: string, leagueId: string): Promise<Standing | null> {
    const repo = this.dataSource.getRepository(StandingOrmEntity);
    const entity = await repo.findOne({ where: { leagueId, driverId } });
    return entity ? this.standingMapper.toDomain(entity) : null;
  }

  async findAll(): Promise<Standing[]> {
    const repo = this.dataSource.getRepository(StandingOrmEntity);
    const entities = await repo.find();
    return entities.map((e) => this.standingMapper.toDomain(e));
  }

  async save(standing: Standing): Promise<Standing> {
    const repo = this.dataSource.getRepository(StandingOrmEntity);
    await repo.save(this.standingMapper.toOrmEntity(standing));
    return standing;
  }

  async saveMany(standings: Standing[]): Promise<Standing[]> {
    const repo = this.dataSource.getRepository(StandingOrmEntity);
    await repo.save(standings.map((s) => this.standingMapper.toOrmEntity(s)));
    return standings;
  }

  async delete(leagueId: string, driverId: string): Promise<void> {
    const repo = this.dataSource.getRepository(StandingOrmEntity);
    await repo.delete({ leagueId, driverId });
  }

  async deleteByLeagueId(leagueId: string): Promise<void> {
    const repo = this.dataSource.getRepository(StandingOrmEntity);
    await repo.delete({ leagueId });
  }

  async exists(leagueId: string, driverId: string): Promise<boolean> {
    const repo = this.dataSource.getRepository(StandingOrmEntity);
    const count = await repo.count({ where: { leagueId, driverId } });
    return count > 0;
  }

  async recalculate(leagueId: string): Promise<Standing[]> {
    const leagueRepo = this.dataSource.getRepository(LeagueOrmEntity);
    const leagueEntity = await leagueRepo.findOne({ where: { id: leagueId } });

    if (!leagueEntity) {
      throw new Error(`League with ID ${leagueId} not found`);
    }

    const league = this.leagueMapper.toDomain(leagueEntity);

    const resolvedPointsSystem =
      league.settings.customPoints ??
      this.pointsSystems[league.settings.pointsSystem] ??
      this.pointsSystems['f1-2024'];

    if (!resolvedPointsSystem) {
      throw new Error('No points system configured for league');
    }

    const raceRepo = this.dataSource.getRepository(RaceOrmEntity);
    const races = await raceRepo.find({ where: { leagueId, status: 'completed' }, select: { id: true } });
    const raceIds = races.map((r) => r.id);

    if (raceIds.length === 0) {
      await this.deleteByLeagueId(leagueId);
      return [];
    }

    const resultRepo = this.dataSource.getRepository(ResultOrmEntity);
    const resultEntities = await resultRepo.find({
      where: { raceId: In(raceIds) },
      order: { position: 'ASC' },
    });

    const standingsByDriver = new Map<string, Standing>();

    for (const entity of resultEntities) {
      const result = this.resultMapper.toDomain(entity);
      const driverId = result.driverId.toString();

      const current =
        standingsByDriver.get(driverId) ??
        Standing.create({
          leagueId,
          driverId,
        });

      const next = current.addRaceResult(result.position.toNumber(), resolvedPointsSystem);
      standingsByDriver.set(driverId, next);
    }

    const sorted = Array.from(standingsByDriver.values()).sort((a, b) => {
      if (b.points.toNumber() !== a.points.toNumber()) return b.points.toNumber() - a.points.toNumber();
      if (b.wins !== a.wins) return b.wins - a.wins;
      return b.racesCompleted - a.racesCompleted;
    });

    const updated = sorted.map((standing, index) => standing.updatePosition(index + 1));

    await this.deleteByLeagueId(leagueId);
    await this.saveMany(updated);

    return updated;
  }
}