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

namespace SportsDivision.Application.Tests;

public class RegistrationEligibilityTests
{
    private readonly Mock<IUnitOfWork> _mockUow;
    private readonly RegistrationService _service;

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

    private void SetupTournamentEventLevel(TournamentEventLevel tel)
    {
        _mockUow.Setup(u => u.TournamentEventLevels.GetWithRegistrationsAsync(It.IsAny<int>()))
            .ReturnsAsync(tel);
    }

    private void SetupStudent(Student student)
    {
        _mockUow.Setup(u => u.Students.GetByIdAsync(student.StudentId))
            .ReturnsAsync(student);
    }

    private void SetupSchool(School school)
    {
        _mockUow.Setup(u => u.Schools.GetByIdAsync(school.SchoolId))
            .ReturnsAsync(school);
    }

    private void SetupNotAlreadyRegistered()
    {
        _mockUow.Setup(u => u.EventRegistrations.IsStudentRegisteredAsync(It.IsAny<int>(), It.IsAny<int>()))
            .ReturnsAsync(false);
    }

    private void SetupAlreadyRegistered()
    {
        _mockUow.Setup(u => u.EventRegistrations.IsStudentRegisteredAsync(It.IsAny<int>(), It.IsAny<int>()))
            .ReturnsAsync(true);
    }

    private TournamentEventLevel CreateTel(Sex sex = Sex.Male, SchoolLevel schoolLevel = SchoolLevel.Secondary,
        int? maxAge = null, bool isAgeBased = false, bool ageWaived = false)
    {
        return new TournamentEventLevel
        {
            TournamentEventLevelId = 1,
            EventLevel = new EventLevel
            {
                Sex = sex,
                SchoolLevel = schoolLevel,
                MaxAge = maxAge,
                IsAgeBased = isAgeBased
            },
            AgeRestrictionWaived = ageWaived,
            Tournament = new Tournament
            {
                StartDate = new DateOnly(2024, 6, 1)
            },
            Registrations = new List<EventRegistration>()
        };
    }

    private Student CreateStudent(Sex sex = Sex.Male, int schoolId = 1, DateOnly? dob = null)
    {
        return new Student
        {
            StudentId = 1,
            FirstName = "Test",
            LastName = "Student",
            Sex = sex,
            SchoolId = schoolId,
            DateOfBirth = dob ?? new DateOnly(2010, 1, 1)
        };
    }

    private School CreateSchool(SchoolLevel level = SchoolLevel.Secondary)
    {
        return new School
        {
            SchoolId = 1,
            Name = "Test School",
            SchoolLevel = level,
            ZoneId = 1
        };
    }

    [Fact]
    public async Task CheckEligibility_EligibleStudent_ReturnsTrue()
    {
        var tel = CreateTel(Sex.Male, SchoolLevel.Secondary);
        var student = CreateStudent(Sex.Male);
        var school = CreateSchool(SchoolLevel.Secondary);

        SetupTournamentEventLevel(tel);
        SetupStudent(student);
        SetupSchool(school);
        SetupNotAlreadyRegistered();

        var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);

        Assert.True(isEligible);
        Assert.Null(reason);
    }

    [Fact]
    public async Task CheckEligibility_SexMismatch_ReturnsFalse()
    {
        var tel = CreateTel(Sex.Male, SchoolLevel.Secondary);
        var student = CreateStudent(Sex.Female);

        SetupTournamentEventLevel(tel);
        SetupStudent(student);

        var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);

        Assert.False(isEligible);
        Assert.Contains("sex", reason!, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public async Task CheckEligibility_SecondaryStudentInPrimaryEvent_ReturnsFalse()
    {
        var tel = CreateTel(Sex.Male, SchoolLevel.Primary);
        var student = CreateStudent(Sex.Male);
        var school = CreateSchool(SchoolLevel.Secondary);

        SetupTournamentEventLevel(tel);
        SetupStudent(student);
        SetupSchool(school);

        var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);

        Assert.False(isEligible);
        Assert.Contains("level", reason!, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public async Task CheckEligibility_PrimaryStudentInSecondaryEvent_CompeteUp_ReturnsTrue()
    {
        var tel = CreateTel(Sex.Male, SchoolLevel.Secondary);
        var student = CreateStudent(Sex.Male);
        var school = CreateSchool(SchoolLevel.Primary);

        SetupTournamentEventLevel(tel);
        SetupStudent(student);
        SetupSchool(school);
        SetupNotAlreadyRegistered();

        var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);

        Assert.True(isEligible);
    }

    [Fact]
    public async Task CheckEligibility_AgeExceedsMax_ReturnsFalse()
    {
        var tel = CreateTel(Sex.Male, SchoolLevel.Secondary, maxAge: 13, isAgeBased: true);
        // Student born 2010-01-01, tournament June 2024 → age 14
        var student = CreateStudent(Sex.Male, dob: new DateOnly(2010, 1, 1));
        var school = CreateSchool(SchoolLevel.Secondary);

        SetupTournamentEventLevel(tel);
        SetupStudent(student);
        SetupSchool(school);

        var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);

        Assert.False(isEligible);
        Assert.Contains("age", reason!, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public async Task CheckEligibility_AgeWithinLimit_ReturnsTrue()
    {
        var tel = CreateTel(Sex.Male, SchoolLevel.Secondary, maxAge: 15, isAgeBased: true);
        // Student born 2010-01-01, tournament June 2024 → age 14
        var student = CreateStudent(Sex.Male, dob: new DateOnly(2010, 1, 1));
        var school = CreateSchool(SchoolLevel.Secondary);

        SetupTournamentEventLevel(tel);
        SetupStudent(student);
        SetupSchool(school);
        SetupNotAlreadyRegistered();

        var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);

        Assert.True(isEligible);
    }

    [Fact]
    public async Task CheckEligibility_AgeExceedsButWaived_ReturnsTrue()
    {
        var tel = CreateTel(Sex.Male, SchoolLevel.Secondary, maxAge: 13, isAgeBased: true, ageWaived: true);
        // Student age 14, max is 13, but waived
        var student = CreateStudent(Sex.Male, dob: new DateOnly(2010, 1, 1));
        var school = CreateSchool(SchoolLevel.Secondary);

        SetupTournamentEventLevel(tel);
        SetupStudent(student);
        SetupSchool(school);
        SetupNotAlreadyRegistered();

        var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);

        Assert.True(isEligible);
    }

    [Fact]
    public async Task CheckEligibility_AlreadyRegistered_ReturnsFalse()
    {
        var tel = CreateTel(Sex.Male, SchoolLevel.Secondary);
        var student = CreateStudent(Sex.Male);
        var school = CreateSchool(SchoolLevel.Secondary);

        SetupTournamentEventLevel(tel);
        SetupStudent(student);
        SetupSchool(school);
        SetupAlreadyRegistered();

        var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 1);

        Assert.False(isEligible);
        Assert.Contains("already registered", reason!, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public async Task CheckEligibility_TelNotFound_ReturnsFalse()
    {
        _mockUow.Setup(u => u.TournamentEventLevels.GetWithRegistrationsAsync(It.IsAny<int>()))
            .ReturnsAsync((TournamentEventLevel?)null);

        var (isEligible, reason) = await _service.CheckEligibilityAsync(999, 1);

        Assert.False(isEligible);
        Assert.Contains("not found", reason!, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public async Task CheckEligibility_StudentNotFound_ReturnsFalse()
    {
        var tel = CreateTel();
        SetupTournamentEventLevel(tel);

        _mockUow.Setup(u => u.Students.GetByIdAsync(It.IsAny<int>()))
            .ReturnsAsync((Student?)null);

        var (isEligible, reason) = await _service.CheckEligibilityAsync(1, 999);

        Assert.False(isEligible);
        Assert.Contains("not found", reason!, StringComparison.OrdinalIgnoreCase);
    }
}
