using Moq;
using SportsDivision.Application.Services;
using SportsDivision.Domain.Entities;
using SportsDivision.Domain.Enums;
using SportsDivision.Domain.Interfaces;

namespace SportsDivision.Application.Tests;

public class HeatAdvancementTests
{
    private readonly Mock<IUnitOfWork> _mockUow;
    private readonly HeatManagementService _service;

    public HeatAdvancementTests()
    {
        _mockUow = new Mock<IUnitOfWork>();
        _service = new HeatManagementService(_mockUow.Object, null!);
    }

    private Round CreateRoundWithHeats(int topN, int fastestLosers, List<Heat> heats)
    {
        return new Round
        {
            RoundId = 1,
            TournamentEventLevelId = 1,
            RoundOrder = 1,
            AdvanceTopN = topN,
            AdvanceFastestLosers = fastestLosers,
            Heats = heats
        };
    }

    private Heat CreateHeat(int heatId, int heatNumber, List<HeatLane> lanes)
    {
        var heat = new Heat
        {
            HeatId = heatId,
            HeatNumber = heatNumber,
            Status = HeatStatus.Completed,
            HeatLanes = lanes
        };
        foreach (var lane in lanes)
            lane.Heat = heat;
        return heat;
    }

    private HeatLane CreateLane(int laneId, int regId, decimal? time, bool isDNS = false, bool isDNF = false, bool isDQ = false)
    {
        return new HeatLane
        {
            HeatLaneId = laneId,
            EventRegistrationId = regId,
            LaneNumber = laneId,
            Time = time,
            IsDNS = isDNS,
            IsDNF = isDNF,
            IsDQ = isDQ,
            IsAdvanced = false
        };
    }

    [Fact]
    public async Task CalculateAdvancement_TopNFromEachHeat()
    {
        var heat1 = CreateHeat(1, 1, new List<HeatLane>
        {
            CreateLane(1, 101, 10.5m),
            CreateLane(2, 102, 10.8m),
            CreateLane(3, 103, 11.2m),
            CreateLane(4, 104, 11.5m)
        });

        var heat2 = CreateHeat(2, 2, new List<HeatLane>
        {
            CreateLane(5, 201, 10.6m),
            CreateLane(6, 202, 10.9m),
            CreateLane(7, 203, 11.3m),
            CreateLane(8, 204, 11.6m)
        });

        var round = CreateRoundWithHeats(topN: 2, fastestLosers: 0, new List<Heat> { heat1, heat2 });

        _mockUow.Setup(u => u.Rounds.GetWithHeatsAsync(1)).ReturnsAsync(round);
        _mockUow.Setup(u => u.HeatLanes.Update(It.IsAny<HeatLane>()));
        _mockUow.Setup(u => u.SaveChangesAsync()).ReturnsAsync(1);

        await _service.CalculateAdvancementAsync(1);

        // Top 2 from heat 1: lanes 1 (10.5) and 2 (10.8)
        Assert.True(heat1.HeatLanes.ElementAt(0).IsAdvanced);
        Assert.True(heat1.HeatLanes.ElementAt(1).IsAdvanced);
        Assert.False(heat1.HeatLanes.ElementAt(2).IsAdvanced);
        Assert.False(heat1.HeatLanes.ElementAt(3).IsAdvanced);

        // Top 2 from heat 2: lanes 5 (10.6) and 6 (10.9)
        Assert.True(heat2.HeatLanes.ElementAt(0).IsAdvanced);
        Assert.True(heat2.HeatLanes.ElementAt(1).IsAdvanced);
        Assert.False(heat2.HeatLanes.ElementAt(2).IsAdvanced);
        Assert.False(heat2.HeatLanes.ElementAt(3).IsAdvanced);
    }

    [Fact]
    public async Task CalculateAdvancement_TopNPlusFastestLosers()
    {
        var heat1 = CreateHeat(1, 1, new List<HeatLane>
        {
            CreateLane(1, 101, 10.5m),
            CreateLane(2, 102, 10.8m),
            CreateLane(3, 103, 11.0m),  // 3rd in heat 1 → fastest loser candidate
            CreateLane(4, 104, 11.5m)
        });

        var heat2 = CreateHeat(2, 2, new List<HeatLane>
        {
            CreateLane(5, 201, 10.6m),
            CreateLane(6, 202, 10.9m),
            CreateLane(7, 203, 11.1m),  // 3rd in heat 2 → fastest loser candidate
            CreateLane(8, 204, 11.8m)
        });

        var round = CreateRoundWithHeats(topN: 2, fastestLosers: 2, new List<Heat> { heat1, heat2 });

        _mockUow.Setup(u => u.Rounds.GetWithHeatsAsync(1)).ReturnsAsync(round);
        _mockUow.Setup(u => u.HeatLanes.Update(It.IsAny<HeatLane>()));
        _mockUow.Setup(u => u.SaveChangesAsync()).ReturnsAsync(1);

        await _service.CalculateAdvancementAsync(1);

        // Top 2 from each heat advance as TopN
        Assert.Equal(AdvanceReason.TopN, heat1.HeatLanes.ElementAt(0).AdvanceReason);
        Assert.Equal(AdvanceReason.TopN, heat1.HeatLanes.ElementAt(1).AdvanceReason);
        Assert.Equal(AdvanceReason.TopN, heat2.HeatLanes.ElementAt(0).AdvanceReason);
        Assert.Equal(AdvanceReason.TopN, heat2.HeatLanes.ElementAt(1).AdvanceReason);

        // Fastest 2 losers: lane 3 (11.0) and lane 7 (11.1)
        Assert.True(heat1.HeatLanes.ElementAt(2).IsAdvanced);
        Assert.Equal(AdvanceReason.FastestLoser, heat1.HeatLanes.ElementAt(2).AdvanceReason);
        Assert.True(heat2.HeatLanes.ElementAt(2).IsAdvanced);
        Assert.Equal(AdvanceReason.FastestLoser, heat2.HeatLanes.ElementAt(2).AdvanceReason);

        // Last in each heat should NOT advance
        Assert.False(heat1.HeatLanes.ElementAt(3).IsAdvanced);
        Assert.False(heat2.HeatLanes.ElementAt(3).IsAdvanced);
    }

    [Fact]
    public async Task CalculateAdvancement_DNSExcluded()
    {
        var heat1 = CreateHeat(1, 1, new List<HeatLane>
        {
            CreateLane(1, 101, 10.5m),
            CreateLane(2, 102, null, isDNS: true),  // DNS
            CreateLane(3, 103, 11.0m),
        });

        var round = CreateRoundWithHeats(topN: 2, fastestLosers: 0, new List<Heat> { heat1 });

        _mockUow.Setup(u => u.Rounds.GetWithHeatsAsync(1)).ReturnsAsync(round);
        _mockUow.Setup(u => u.HeatLanes.Update(It.IsAny<HeatLane>()));
        _mockUow.Setup(u => u.SaveChangesAsync()).ReturnsAsync(1);

        await _service.CalculateAdvancementAsync(1);

        Assert.True(heat1.HeatLanes.ElementAt(0).IsAdvanced);
        Assert.False(heat1.HeatLanes.ElementAt(1).IsAdvanced); // DNS not advanced
        Assert.True(heat1.HeatLanes.ElementAt(2).IsAdvanced);
    }

    [Fact]
    public async Task CalculateAdvancement_DNFExcluded()
    {
        var heat1 = CreateHeat(1, 1, new List<HeatLane>
        {
            CreateLane(1, 101, 10.5m),
            CreateLane(2, 102, 10.8m, isDNF: true),  // DNF
            CreateLane(3, 103, 11.0m),
        });

        var round = CreateRoundWithHeats(topN: 2, fastestLosers: 0, new List<Heat> { heat1 });

        _mockUow.Setup(u => u.Rounds.GetWithHeatsAsync(1)).ReturnsAsync(round);
        _mockUow.Setup(u => u.HeatLanes.Update(It.IsAny<HeatLane>()));
        _mockUow.Setup(u => u.SaveChangesAsync()).ReturnsAsync(1);

        await _service.CalculateAdvancementAsync(1);

        Assert.True(heat1.HeatLanes.ElementAt(0).IsAdvanced);
        Assert.False(heat1.HeatLanes.ElementAt(1).IsAdvanced); // DNF not advanced
        Assert.True(heat1.HeatLanes.ElementAt(2).IsAdvanced);
    }

    [Fact]
    public async Task CalculateAdvancement_DQExcluded()
    {
        var heat1 = CreateHeat(1, 1, new List<HeatLane>
        {
            CreateLane(1, 101, 10.5m),
            CreateLane(2, 102, 10.2m, isDQ: true),  // DQ - fastest time but disqualified
            CreateLane(3, 103, 11.0m),
        });

        var round = CreateRoundWithHeats(topN: 2, fastestLosers: 0, new List<Heat> { heat1 });

        _mockUow.Setup(u => u.Rounds.GetWithHeatsAsync(1)).ReturnsAsync(round);
        _mockUow.Setup(u => u.HeatLanes.Update(It.IsAny<HeatLane>()));
        _mockUow.Setup(u => u.SaveChangesAsync()).ReturnsAsync(1);

        await _service.CalculateAdvancementAsync(1);

        Assert.True(heat1.HeatLanes.ElementAt(0).IsAdvanced);
        Assert.False(heat1.HeatLanes.ElementAt(1).IsAdvanced); // DQ not advanced
        Assert.True(heat1.HeatLanes.ElementAt(2).IsAdvanced);
    }
}
