/**
 * Infrastructure Adapter: InMemoryStandingRepository
 * 
 * In-memory implementation of IStandingRepository.
 * Stores data in Map structure and calculates standings from race results.
 */

import { Standing } from '@core/racing/domain/entities/Standing';
import type { IStandingRepository } from '@core/racing/domain/repositories/IStandingRepository';
import type { IResultRepository } from '@core/racing/domain/repositories/IResultRepository';
import type { IRaceRepository } from '@core/racing/domain/repositories/IRaceRepository';
import type { ILeagueRepository } from '@core/racing/domain/repositories/ILeagueRepository';
import type { Logger } from '@core/shared/application';

export class InMemoryStandingRepository implements IStandingRepository {
  private standings: Map<string, Standing>;
  private resultRepository: IResultRepository | null;
  private raceRepository: IRaceRepository | null;
  private leagueRepository: ILeagueRepository | null;
  private readonly logger: Logger;
  private readonly pointsSystems: Record<string, Record<number, number>>;

  constructor(
    logger: Logger,
    pointsSystems: Record<string, Record<number, number>>,
    resultRepository?: IResultRepository | null,
    raceRepository?: IRaceRepository | null,
    leagueRepository?: ILeagueRepository | null
  ) {
    this.logger = logger;
    this.pointsSystems = pointsSystems;
    this.logger.info('InMemoryStandingRepository initialized.');
    this.standings = new Map();
    this.resultRepository = resultRepository ?? null;
    this.raceRepository = raceRepository ?? null;
    this.leagueRepository = leagueRepository ?? null;
  }

  private getKey(leagueId: string, driverId: string): string {
    return `${leagueId}:${driverId}`;
  }

  async findByLeagueId(leagueId: string): Promise<Standing[]> {
    this.logger.debug(`Finding standings for league id: ${leagueId}`);
    try {
      const standings = Array.from(this.standings.values())
        .filter(standing => standing.leagueId.toString() === leagueId)
        .sort((a, b) => {
          // Sort by position (lower is better)
          if (a.position.toNumber() !== b.position.toNumber()) {
            return a.position.toNumber() - b.position.toNumber();
          }
          // If positions are equal, sort by points (higher is better)
          return b.points.toNumber() - a.points.toNumber();
        });
      this.logger.info(`Found ${standings.length} standings for league id: ${leagueId}.`);
      return standings;
    } catch (error) {
      this.logger.error(`Error finding standings for league id ${leagueId}:`, error instanceof Error ? error : new Error(String(error)));
      throw error;
    }
  }

  async findByDriverIdAndLeagueId(driverId: string, leagueId: string): Promise<Standing | null> {
    this.logger.debug(`Finding standing for driver: ${driverId}, league: ${leagueId}`);
    try {
      const key = this.getKey(leagueId, driverId);
      const standing = this.standings.get(key) ?? null;
      if (standing) {
        this.logger.info(`Found standing for driver: ${driverId}, league: ${leagueId}.`);
      } else {
        this.logger.warn(`Standing for driver ${driverId}, league ${leagueId} not found.`);
      }
      return standing;
    } catch (error) {
      this.logger.error(`Error finding standing for driver ${driverId}, league ${leagueId}:`, error);
      throw error;
    }
  }

  async findAll(): Promise<Standing[]> {
    this.logger.debug('Finding all standings.');
    try {
      const standings = Array.from(this.standings.values());
      this.logger.info(`Found ${standings.length} standings.`);
      return standings;
    } catch (error) {
      this.logger.error('Error finding all standings:', error instanceof Error ? error : new Error(String(error)));
      throw error;
    }
  }

  async save(standing: Standing): Promise<Standing> {
    this.logger.debug(`Saving standing for league: ${standing.leagueId}, driver: ${standing.driverId}`);
    try {
      const key = this.getKey(standing.leagueId, standing.driverId);
      if (this.standings.has(key)) {
        this.logger.debug(`Updating existing standing for league: ${standing.leagueId}, driver: ${standing.driverId}.`);
      } else {
        this.logger.debug(`Creating new standing for league: ${standing.leagueId}, driver: ${standing.driverId}.`);
      }
      this.standings.set(key, standing);
      this.logger.info(`Standing for league ${standing.leagueId}, driver ${standing.driverId} saved successfully.`);
      return standing;
    } catch (error) {
      this.logger.error(`Error saving standing for league ${standing.leagueId}, driver ${standing.driverId}:`, error);
      throw error;
    }
  }

  async saveMany(standings: Standing[]): Promise<Standing[]> {
    this.logger.debug(`Saving ${standings.length} standings.`);
    try {
      standings.forEach(standing => {
        const key = this.getKey(standing.leagueId, standing.driverId);
        this.standings.set(key, standing);
      });
      this.logger.info(`${standings.length} standings saved successfully.`);
      return standings;
    } catch (error) {
      this.logger.error(`Error saving many standings:`, error instanceof Error ? error : new Error(String(error)));
      throw error;
    }
  }

  async delete(leagueId: string, driverId: string): Promise<void> {
    this.logger.debug(`Deleting standing for league: ${leagueId}, driver: ${driverId}`);
    try {
      const key = this.getKey(leagueId, driverId);
      if (this.standings.delete(key)) {
        this.logger.info(`Standing for league ${leagueId}, driver ${driverId} deleted successfully.`);
      } else {
        this.logger.warn(`Standing for league ${leagueId}, driver ${driverId} not found for deletion.`);
      }
    } catch (error) {
      this.logger.error(`Error deleting standing for league ${leagueId}, driver ${driverId}:`, error);
      throw error;
    }
  }

  async deleteByLeagueId(leagueId: string): Promise<void> {
    this.logger.debug(`Deleting all standings for league id: ${leagueId}`);
    try {
      const toDelete = Array.from(this.standings.values())
        .filter(standing => standing.leagueId.toString() === leagueId);

      toDelete.forEach(standing => {
        const key = this.getKey(standing.leagueId.toString(), standing.driverId.toString());
        this.standings.delete(key);
      });
      this.logger.info(`Deleted ${toDelete.length} standings for league id: ${leagueId}.`);
    } catch (error) {
      this.logger.error(`Error deleting standings by league id ${leagueId}:`, error instanceof Error ? error : new Error(String(error)));
      throw error;
    }
  }

  async exists(leagueId: string, driverId: string): Promise<boolean> {
    this.logger.debug(`Checking existence of standing for league: ${leagueId}, driver: ${driverId}`);
    try {
      const key = this.getKey(leagueId, driverId);
      const exists = this.standings.has(key);
      this.logger.debug(`Standing for league ${leagueId}, driver ${driverId} exists: ${exists}.`);
      return exists;
    } catch (error) {
      this.logger.error(`Error checking existence of standing for league ${leagueId}, driver ${driverId}:`, error);
      throw error;
    }
  }

  async recalculate(leagueId: string): Promise<Standing[]> {
    this.logger.debug(`Recalculating standings for league id: ${leagueId}`);
    try {
      if (!this.resultRepository || !this.raceRepository || !this.leagueRepository) {
        this.logger.error('Cannot recalculate standings: missing required repositories.');
        throw new Error('Cannot recalculate standings: missing required repositories');
      }

      // Get league to determine points system
      const league = await this.leagueRepository.findById(leagueId);
      if (!league) {
        this.logger.warn(`League with ID ${leagueId} not found during recalculation.`);
        throw new Error(`League with ID ${leagueId} not found`);
      }
      this.logger.debug(`League ${leagueId} found for recalculation.`);

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

      if (!resolvedPointsSystem) {
        this.logger.error(`No points system configured for league ${leagueId}.`);
        throw new Error('No points system configured for league');
      }
      this.logger.debug(`Resolved points system for league ${leagueId}.`);

      // Get all completed races for the league
      const races = await this.raceRepository.findCompletedByLeagueId(leagueId);
      this.logger.debug(`Found ${races.length} completed races for league ${leagueId}.`);
      
      if (races.length === 0) {
        this.logger.warn(`No completed races found for league ${leagueId}. Standings will be empty.`);
        return [];
      }

      // Get all results for these races
      const allResults = await Promise.all(
        races.map(async race => {
          this.logger.debug(`Fetching results for race ${race.id}.`);
          const results = await this.resultRepository!.findByRaceId(race.id);
          this.logger.debug(`Found ${results.length} results for race ${race.id}.`);
          return results;
        })
      );
      const results = allResults.flat();
      this.logger.debug(`Collected ${results.length} results from all completed races.`);

      // Calculate standings per driver
      const standingsMap = new Map<string, Standing>();

      results.forEach(result => {
        let standing = standingsMap.get(result.driverId);
        
        if (!standing) {
          standing = Standing.create({
            leagueId,
            driverId: result.driverId,
          });
          this.logger.debug(`Created new standing for driver ${result.driverId} in league ${leagueId}.`);
        }

        // Add points from this result
        standing = standing.addRaceResult(result.position, resolvedPointsSystem);
        standingsMap.set(result.driverId, standing);
        this.logger.debug(`Driver ${result.driverId} in league ${leagueId} accumulated ${standing.points} points.`);
      });
      this.logger.debug(`Calculated initial standings for ${standingsMap.size} drivers.`);

      // Sort by points and assign positions
      const sortedStandings = Array.from(standingsMap.values())
        .sort((a, b) => {
          if (b.points !== a.points) {
            return b.points - a.points;
          }
          // Tie-breaker: most wins
          if (b.wins !== a.wins) {
            return b.wins - a.wins;
          }
          // Tie-breaker: most races completed
          return b.racesCompleted - a.racesCompleted;
        });
      this.logger.debug(`Sorted standings for ${sortedStandings.length} drivers.`);

      // Assign positions
      const updatedStandings = sortedStandings.map((standing, index) => {
        const newStanding = standing.updatePosition(index + 1);
        this.logger.debug(`Assigned position ${newStanding.position} to driver ${newStanding.driverId}.`);
        return newStanding;
      });

      // Save all standings
      await this.saveMany(updatedStandings);
      this.logger.info(`Successfully recalculated and saved standings for league ${leagueId}.`);

      return updatedStandings;
    } catch (error) {
      this.logger.error(`Error recalculating standings for league ${leagueId}:`, error instanceof Error ? error : new Error(String(error)));
      throw error;
    }
  }
}