using SportsDivision.Application.DTOs.ReportDtos;
using SportsDivision.Application.Interfaces;
using SportsDivision.Domain.Interfaces;

namespace SportsDivision.Application.Services;

public class ReportService : IReportService
{
    private readonly IUnitOfWork _uow;

    public ReportService(IUnitOfWork uow)
    {
        _uow = uow;
    }

    public async Task<IEnumerable<PopularEventsReportDto>> GetPopularEventsAsync(int tournamentId)
    {
        var tels = await _uow.TournamentEventLevels.GetByTournamentAsync(tournamentId);
        var report = new Dictionary<string, PopularEventsReportDto>();

        foreach (var tel in tels)
        {
            var regs = await _uow.EventRegistrations.GetByTournamentEventLevelAsync(tel.TournamentEventLevelId);
            var eventName = tel.Event?.Name ?? "Unknown";
            var category = tel.Event?.Category.ToString() ?? "Unknown";

            if (!report.ContainsKey(eventName))
                report[eventName] = new PopularEventsReportDto { EventName = eventName, Category = category };

            var entry = report[eventName];
            foreach (var reg in regs)
            {
                entry.RegistrationCount++;
                if (reg.Student?.Sex == Domain.Enums.Sex.Male) entry.MaleCount++;
                else if (reg.Student?.Sex == Domain.Enums.Sex.Female) entry.FemaleCount++;
            }
        }

        return report.Values.OrderByDescending(r => r.RegistrationCount);
    }

    public async Task<IEnumerable<RegistrationByGenderReportDto>> GetRegistrationByGenderAsync(int tournamentId, int? zoneId = null)
    {
        var tels = await _uow.TournamentEventLevels.GetByTournamentAsync(tournamentId);
        var report = new List<RegistrationByGenderReportDto>();

        foreach (var tel in tels)
        {
            var regs = await _uow.EventRegistrations.GetByTournamentEventLevelAsync(tel.TournamentEventLevelId);
            var filteredRegs = zoneId.HasValue
                ? regs.Where(r => r.Student?.School?.ZoneId == zoneId.Value)
                : regs;

            var dto = new RegistrationByGenderReportDto
            {
                EventName = tel.Event?.Name ?? "Unknown",
                EventLevelName = tel.EventLevel?.Name ?? "Unknown",
                MaleCount = filteredRegs.Count(r => r.Student?.Sex == Domain.Enums.Sex.Male),
                FemaleCount = filteredRegs.Count(r => r.Student?.Sex == Domain.Enums.Sex.Female)
            };
            dto.TotalCount = dto.MaleCount + dto.FemaleCount;
            if (dto.TotalCount > 0) report.Add(dto);
        }

        return report.OrderBy(r => r.EventName).ThenBy(r => r.EventLevelName);
    }

    public async Task<IEnumerable<EventSchoolReportDto>> GetEventSchoolReportAsync(int tournamentId, int? eventId = null, int? eventLevelId = null)
    {
        var tels = await _uow.TournamentEventLevels.GetByTournamentAsync(tournamentId);
        var filtered = tels.AsEnumerable();
        if (eventId.HasValue) filtered = filtered.Where(t => t.EventId == eventId.Value);
        if (eventLevelId.HasValue) filtered = filtered.Where(t => t.EventLevelId == eventLevelId.Value);

        var report = new List<EventSchoolReportDto>();
        foreach (var tel in filtered)
        {
            var regs = await _uow.EventRegistrations.GetByTournamentEventLevelAsync(tel.TournamentEventLevelId);
            var schoolGroups = regs
                .Where(r => r.Student?.School != null)
                .GroupBy(r => r.Student!.School!.Name);

            var dto = new EventSchoolReportDto
            {
                EventName = tel.Event?.Name ?? "Unknown",
                EventLevelName = tel.EventLevel?.Name ?? "Unknown"
            };

            foreach (var group in schoolGroups)
            {
                var schoolEntry = new EventSchoolEntryDto
                {
                    SchoolName = group.Key,
                    Students = group.Select(r => new EventSchoolStudentDto
                    {
                        StudentName = r.Student?.FullName ?? "Unknown",
                        RawPerformance = r.Score?.RawPerformance,
                        Placement = r.Score?.Placement,
                        PlacementPoints = r.Score?.PlacementPoints ?? 0
                    }).ToList()
                };
                dto.Schools.Add(schoolEntry);
            }
            report.Add(dto);
        }
        return report;
    }

    public async Task<IEnumerable<StudentsBySchoolReportDto>> GetStudentsBySchoolAsync(int tournamentId, int? schoolId = null, int? zoneId = null)
    {
        var tels = await _uow.TournamentEventLevels.GetByTournamentAsync(tournamentId);
        var schoolStudents = new Dictionary<string, StudentsBySchoolReportDto>();

        foreach (var tel in tels)
        {
            var regs = await _uow.EventRegistrations.GetByTournamentEventLevelAsync(tel.TournamentEventLevelId);
            foreach (var reg in regs)
            {
                if (reg.Student?.School == null) continue;
                if (schoolId.HasValue && reg.Student.SchoolId != schoolId.Value) continue;
                if (zoneId.HasValue && reg.Student.School.ZoneId != zoneId.Value) continue;

                var schoolName = reg.Student.School.Name;
                if (!schoolStudents.ContainsKey(schoolName))
                {
                    schoolStudents[schoolName] = new StudentsBySchoolReportDto
                    {
                        SchoolName = schoolName,
                        ZoneName = reg.Student.School.Zone?.Name ?? "Unknown"
                    };
                }

                var existingStudent = schoolStudents[schoolName].Students
                    .FirstOrDefault(s => s.StudentName == reg.Student.FullName);
                if (existingStudent == null)
                {
                    existingStudent = new StudentEventEntryDto
                    {
                        StudentName = reg.Student.FullName,
                        Sex = reg.Student.Sex.ToString()
                    };
                    schoolStudents[schoolName].Students.Add(existingStudent);
                }
                var eventDesc = $"{tel.Event?.Name} ({tel.EventLevel?.Name})";
                existingStudent.Events.Add(eventDesc);
            }
        }

        return schoolStudents.Values.OrderBy(s => s.SchoolName);
    }

    public async Task<IEnumerable<ScoresByEventReportDto>> GetScoresByEventAsync(int tournamentId, int? eventId = null, int? eventLevelId = null)
    {
        var tels = await _uow.TournamentEventLevels.GetByTournamentAsync(tournamentId);
        var filtered = tels.AsEnumerable();
        if (eventId.HasValue) filtered = filtered.Where(t => t.EventId == eventId.Value);
        if (eventLevelId.HasValue) filtered = filtered.Where(t => t.EventLevelId == eventLevelId.Value);

        var report = new List<ScoresByEventReportDto>();
        foreach (var tel in filtered)
        {
            var scores = await _uow.Scores.GetByTournamentEventLevelAsync(tel.TournamentEventLevelId);
            if (!scores.Any()) continue;

            var dto = new ScoresByEventReportDto
            {
                EventName = tel.Event?.Name ?? "Unknown",
                EventLevelName = tel.EventLevel?.Name ?? "Unknown",
                Category = tel.Event?.Category.ToString() ?? "Unknown",
                Scores = scores.OrderBy(s => s.Placement ?? int.MaxValue).Select(s => new ScoreEntryDto
                {
                    Placement = s.Placement,
                    StudentName = s.EventRegistration?.Student?.FullName ?? "Unknown",
                    SchoolName = s.EventRegistration?.Student?.School?.Name ?? "Unknown",
                    RawPerformance = s.RawPerformance,
                    CalculatedPoints = s.CalculatedPoints,
                    PlacementPoints = s.PlacementPoints
                }).ToList()
            };
            report.Add(dto);
        }
        return report;
    }

    public async Task<IEnumerable<StudentPointsReportDto>> GetStudentPointsAsync(int tournamentId, int? schoolId = null, int? zoneId = null)
    {
        var tels = await _uow.TournamentEventLevels.GetByTournamentAsync(tournamentId);
        var studentPoints = new Dictionary<int, StudentPointsReportDto>();

        foreach (var tel in tels)
        {
            var regs = await _uow.EventRegistrations.GetByTournamentEventLevelAsync(tel.TournamentEventLevelId);
            foreach (var reg in regs)
            {
                if (reg.Student == null || reg.Score == null) continue;
                if (schoolId.HasValue && reg.Student.SchoolId != schoolId.Value) continue;
                if (zoneId.HasValue && reg.Student.School?.ZoneId != zoneId.Value) continue;

                var studentId = reg.Student.StudentId;
                if (!studentPoints.ContainsKey(studentId))
                {
                    studentPoints[studentId] = new StudentPointsReportDto
                    {
                        StudentName = reg.Student.FullName,
                        SchoolName = reg.Student.School?.Name ?? "Unknown",
                        Sex = reg.Student.Sex.ToString()
                    };
                }

                var entry = studentPoints[studentId];
                entry.TotalPlacementPoints += reg.Score.PlacementPoints;
                entry.EventCount++;
                entry.EventScores.Add(new StudentEventScoreDto
                {
                    EventName = tel.Event?.Name ?? "Unknown",
                    EventLevelName = tel.EventLevel?.Name ?? "Unknown",
                    Placement = reg.Score.Placement,
                    PlacementPoints = reg.Score.PlacementPoints
                });
            }
        }

        return studentPoints.Values.OrderByDescending(s => s.TotalPlacementPoints);
    }
}
