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 HighJumpService : IHighJumpService
{
    private readonly IUnitOfWork _uow;
    private readonly IMapper _mapper;

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

    public async Task<IEnumerable<HighJumpHeightDto>> GetHeightsAsync(int tournamentEventLevelId)
    {
        var heights = await _uow.HighJumpHeights.GetByTournamentEventLevelAsync(tournamentEventLevelId);
        return _mapper.Map<IEnumerable<HighJumpHeightDto>>(heights);
    }

    public async Task<HighJumpHeightDto> AddHeightAsync(HighJumpHeightCreateDto dto)
    {
        var existingHeights = await _uow.HighJumpHeights.GetByTournamentEventLevelAsync(dto.TournamentEventLevelId);
        var maxOrder = existingHeights.Any() ? existingHeights.Max(h => h.SortOrder) : 0;

        var height = new HighJumpHeight
        {
            TournamentEventLevelId = dto.TournamentEventLevelId,
            Height = dto.Height,
            SortOrder = maxOrder + 1
        };

        await _uow.HighJumpHeights.AddAsync(height);
        await _uow.SaveChangesAsync();
        return _mapper.Map<HighJumpHeightDto>(height);
    }

    public async Task RemoveHeightAsync(int heightId)
    {
        var height = await _uow.HighJumpHeights.GetByIdAsync(heightId)
            ?? throw new KeyNotFoundException("Height not found.");
        _uow.HighJumpHeights.Remove(height);
        await _uow.SaveChangesAsync();
    }

    public async Task RecordAttemptAsync(HighJumpAttemptUpdateDto dto)
    {
        var height = await _uow.HighJumpHeights.GetWithAttemptsAsync(dto.HighJumpHeightId)
            ?? throw new KeyNotFoundException("Height not found.");

        var attempt = height.Attempts.FirstOrDefault(a => a.EventRegistrationId == dto.EventRegistrationId);
        if (attempt == null)
        {
            attempt = new HighJumpAttempt
            {
                HighJumpHeightId = dto.HighJumpHeightId,
                EventRegistrationId = dto.EventRegistrationId
            };
            height.Attempts.Add(attempt);
        }

        switch (dto.AttemptNumber)
        {
            case 1: attempt.Attempt1 = dto.Result; break;
            case 2: attempt.Attempt2 = dto.Result; break;
            case 3: attempt.Attempt3 = dto.Result; break;
            default: throw new ArgumentException("Attempt number must be 1, 2, or 3.");
        }

        await _uow.SaveChangesAsync();
    }

    public async Task<bool> IsEliminatedAsync(int tournamentEventLevelId, int eventRegistrationId)
    {
        var heights = await _uow.HighJumpHeights.GetByTournamentEventLevelAsync(tournamentEventLevelId);
        int consecutiveFails = 0;

        foreach (var height in heights.OrderBy(h => h.SortOrder))
        {
            var fullHeight = await _uow.HighJumpHeights.GetWithAttemptsAsync(height.HighJumpHeightId);
            if (fullHeight == null) continue;

            var attempt = fullHeight.Attempts.FirstOrDefault(a => a.EventRegistrationId == eventRegistrationId);
            if (attempt == null) continue;

            if (attempt.HasCleared)
                consecutiveFails = 0;
            else if (attempt.IsEliminated)
                consecutiveFails += 3;
            else
                consecutiveFails += attempt.FailCount;

            if (consecutiveFails >= 3)
                return true;
        }

        return false;
    }

    public async Task CalculateResultsAsync(int tournamentEventLevelId, string recordedBy)
    {
        var heights = (await _uow.HighJumpHeights.GetByTournamentEventLevelAsync(tournamentEventLevelId))
            .OrderBy(h => h.SortOrder).ToList();

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

        var results = new List<(int RegId, decimal HighestCleared, int TotalFails, int FailsAtHighest)>();

        foreach (var reg in tel.Registrations)
        {
            decimal highestCleared = 0;
            int totalFails = 0;
            int failsAtHighest = 0;

            foreach (var height in heights)
            {
                var fullHeight = await _uow.HighJumpHeights.GetWithAttemptsAsync(height.HighJumpHeightId);
                if (fullHeight == null) continue;

                var attempt = fullHeight.Attempts.FirstOrDefault(a => a.EventRegistrationId == reg.EventRegistrationId);
                if (attempt == null) continue;

                totalFails += attempt.FailCount;
                if (attempt.HasCleared)
                {
                    highestCleared = height.Height;
                    failsAtHighest = attempt.FailCount;
                }
            }

            if (highestCleared > 0)
                results.Add((reg.EventRegistrationId, highestCleared, totalFails, failsAtHighest));
        }

        // Sort: highest cleared DESC, then fewest fails at that height, then fewest total fails
        var sorted = results
            .OrderByDescending(r => r.HighestCleared)
            .ThenBy(r => r.FailsAtHighest)
            .ThenBy(r => r.TotalFails)
            .ToList();

        // Record scores: cleared height, placement, placement points, and World-Athletics points.
        var placementPoints = (await _uow.PlacementPointConfigs.GetAllAsync())
            .ToDictionary(p => p.Placement, p => p.Points);
        var constant = await _uow.ScoringConstants.GetByEventAsync(tel.EventId);

        for (int i = 0; i < sorted.Count; i++)
        {
            int place = i + 1;
            decimal cleared = sorted[i].HighestCleared;
            int calc = (constant != null && cleared > constant.B)
                ? (int)Math.Floor((double)constant.A * Math.Pow((double)(cleared - constant.B), (double)constant.C))
                : 0;

            var score = await _uow.Scores.GetByRegistrationAsync(sorted[i].RegId);
            var isNew = score == null;
            score ??= new Score { EventRegistrationId = sorted[i].RegId };

            score.RawPerformance = cleared;
            score.CalculatedPoints = calc;
            score.Placement = place;
            score.PlacementPoints = placementPoints.TryGetValue(place, out var pts) ? pts : 0;
            score.RecordedBy = recordedBy;
            score.RecordedAt = DateTime.UtcNow;

            if (isNew) await _uow.Scores.AddAsync(score);
            else _uow.Scores.Update(score);
        }
        await _uow.SaveChangesAsync();
    }
}
