using AutoMapper;
using SportsDivision.Application.DTOs;
using SportsDivision.Application.Interfaces;
using SportsDivision.Domain.Entities;
using SportsDivision.Domain.Enums;
using SportsDivision.Domain.Interfaces;

namespace SportsDivision.Application.Services;

public class HeatManagementService : IHeatManagementService
{
    private readonly IUnitOfWork _uow;
    private readonly IMapper _mapper;

    public HeatManagementService(IUnitOfWork uow, IMapper mapper)
    {
        _uow = uow;
        _mapper = mapper;
    }

    public async Task<RoundDto> CreateRoundAsync(RoundCreateDto dto)
    {
        var round = _mapper.Map<Round>(dto);
        await _uow.Rounds.AddAsync(round);
        await _uow.SaveChangesAsync();
        return _mapper.Map<RoundDto>(round);
    }

    public async Task<IEnumerable<RoundDto>> GetRoundsAsync(int tournamentEventLevelId)
    {
        var rounds = await _uow.Rounds.GetByTournamentEventLevelAsync(tournamentEventLevelId);
        return _mapper.Map<IEnumerable<RoundDto>>(rounds);
    }

    public async Task<RoundDto?> GetRoundWithHeatsAsync(int roundId)
    {
        var round = await _uow.Rounds.GetWithHeatsAsync(roundId);
        return round == null ? null : _mapper.Map<RoundDto>(round);
    }

    public async Task SeedHeatsAsync(int roundId, SeedingMethod method, int lanesPerHeat = 8)
    {
        var round = await _uow.Rounds.GetWithHeatsAsync(roundId)
            ?? throw new KeyNotFoundException("Round not found.");

        var tel = await _uow.TournamentEventLevels.GetWithRegistrationsAsync(round.TournamentEventLevelId)
            ?? throw new KeyNotFoundException("Tournament event level not found.");

        var registrations = tel.Registrations.ToList();

        if (method == SeedingMethod.Random)
        {
            var rng = new Random();
            registrations = registrations.OrderBy(_ => rng.Next()).ToList();
        }

        // Clear existing heats and lanes
        foreach (var heat in round.Heats.ToList())
        {
            var lanes = await _uow.HeatLanes.GetByHeatAsync(heat.HeatId);
            foreach (var lane in lanes)
                _uow.HeatLanes.Remove(lane);
            _uow.Heats.Remove(heat);
        }
        await _uow.SaveChangesAsync();

        int heatCount = (int)Math.Ceiling((double)registrations.Count / lanesPerHeat);
        for (int h = 0; h < heatCount; h++)
        {
            var heat = new Heat
            {
                RoundId = roundId,
                HeatNumber = h + 1,
                Status = HeatStatus.Pending
            };
            await _uow.Heats.AddAsync(heat);
            await _uow.SaveChangesAsync();

            var heatRegs = registrations.Skip(h * lanesPerHeat).Take(lanesPerHeat).ToList();
            for (int l = 0; l < heatRegs.Count; l++)
            {
                var lane = new HeatLane
                {
                    HeatId = heat.HeatId,
                    EventRegistrationId = heatRegs[l].EventRegistrationId,
                    LaneNumber = l + 1
                };
                await _uow.HeatLanes.AddAsync(lane);
            }
            await _uow.SaveChangesAsync();
        }
    }

    public async Task SaveHeatTimesAsync(int heatId, List<HeatLaneUpdateDto> lanes, string recordedBy)
    {
        foreach (var laneDto in lanes)
        {
            var lane = await _uow.HeatLanes.GetByIdAsync(laneDto.HeatLaneId);
            if (lane == null) continue;

            lane.Time = laneDto.Time;
            lane.IsDNS = laneDto.IsDNS;
            lane.IsDNF = laneDto.IsDNF;
            lane.IsDQ = laneDto.IsDQ;
            lane.RecordedBy = recordedBy;
            lane.RecordedAt = DateTime.UtcNow;
            _uow.HeatLanes.Update(lane);
        }
        await _uow.SaveChangesAsync();
    }

    public async Task CalculateAdvancementAsync(int roundId)
    {
        var round = await _uow.Rounds.GetWithHeatsAsync(roundId)
            ?? throw new KeyNotFoundException("Round not found.");

        var allLanes = round.Heats.SelectMany(h => h.HeatLanes)
            .Where(l => l.Time.HasValue && !l.IsDNS && !l.IsDNF && !l.IsDQ)
            .OrderBy(l => l.Time)
            .ToList();

        // Top N from each heat
        if (round.AdvanceTopN.HasValue)
        {
            foreach (var heat in round.Heats)
            {
                var topN = heat.HeatLanes
                    .Where(l => l.Time.HasValue && !l.IsDNS && !l.IsDNF && !l.IsDQ)
                    .OrderBy(l => l.Time)
                    .Take(round.AdvanceTopN.Value);

                foreach (var lane in topN)
                {
                    lane.IsAdvanced = true;
                    lane.AdvanceReason = AdvanceReason.TopN;
                    _uow.HeatLanes.Update(lane);
                }
            }
        }

        // Fastest losers
        if (round.AdvanceFastestLosers.HasValue && round.AdvanceFastestLosers.Value > 0)
        {
            var nonAdvanced = allLanes.Where(l => !l.IsAdvanced)
                .OrderBy(l => l.Time)
                .Take(round.AdvanceFastestLosers.Value);

            foreach (var lane in nonAdvanced)
            {
                lane.IsAdvanced = true;
                lane.AdvanceReason = AdvanceReason.FastestLoser;
                _uow.HeatLanes.Update(lane);
            }
        }

        await _uow.SaveChangesAsync();
    }

    public async Task PopulateNextRoundAsync(int roundId)
    {
        var round = await _uow.Rounds.GetWithHeatsAsync(roundId)
            ?? throw new KeyNotFoundException("Round not found.");

        var advancedLanes = round.Heats.SelectMany(h => h.HeatLanes)
            .Where(l => l.IsAdvanced)
            .OrderBy(l => l.Time)
            .ToList();

        var nextRounds = await _uow.Rounds.GetByTournamentEventLevelAsync(round.TournamentEventLevelId);
        var nextRound = nextRounds.FirstOrDefault(r => r.RoundOrder == round.RoundOrder + 1)
            ?? throw new InvalidOperationException("No next round exists.");

        int lanesPerHeat = 8;
        int heatCount = (int)Math.Ceiling((double)advancedLanes.Count / lanesPerHeat);
        for (int h = 0; h < heatCount; h++)
        {
            var heat = new Heat
            {
                RoundId = nextRound.RoundId,
                HeatNumber = h + 1,
                Status = HeatStatus.Pending
            };
            await _uow.Heats.AddAsync(heat);
            await _uow.SaveChangesAsync();

            var heatLanes = advancedLanes.Skip(h * lanesPerHeat).Take(lanesPerHeat).ToList();
            for (int l = 0; l < heatLanes.Count; l++)
            {
                var lane = new HeatLane
                {
                    HeatId = heat.HeatId,
                    EventRegistrationId = heatLanes[l].EventRegistrationId,
                    LaneNumber = l + 1
                };
                await _uow.HeatLanes.AddAsync(lane);
            }
            await _uow.SaveChangesAsync();
        }
    }

    public async Task CompleteHeatAsync(int heatId)
    {
        var heat = await _uow.Heats.GetByIdAsync(heatId)
            ?? throw new KeyNotFoundException("Heat not found.");
        heat.Status = HeatStatus.Completed;
        _uow.Heats.Update(heat);
        await _uow.SaveChangesAsync();
    }

    public async Task CompleteRoundAsync(int roundId)
    {
        var round = await _uow.Rounds.GetByIdAsync(roundId)
            ?? throw new KeyNotFoundException("Round not found.");
        round.Status = RoundStatus.Completed;
        _uow.Rounds.Update(round);
        await _uow.SaveChangesAsync();
    }
}
