using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using ColaFlow.Modules.ProjectManagement.Application.Common.Interfaces;
using ColaFlow.Modules.ProjectManagement.Domain.Repositories;
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;

namespace ColaFlow.Modules.ProjectManagement.Application.Queries.GetSprintBurndown;

/// <summary>
/// Handler for GetSprintBurndownQuery
/// Calculates ideal and actual burndown data for sprint progress visualization
/// </summary>
public sealed class GetSprintBurndownQueryHandler : IRequestHandler<GetSprintBurndownQuery, BurndownChartDto?>
{
    private readonly IProjectRepository _projectRepository;
    private readonly IApplicationDbContext _context;
    private readonly ILogger<GetSprintBurndownQueryHandler> _logger;

    public GetSprintBurndownQueryHandler(
        IProjectRepository projectRepository,
        IApplicationDbContext context,
        ILogger<GetSprintBurndownQueryHandler> logger)
    {
        _projectRepository = projectRepository ?? throw new ArgumentNullException(nameof(projectRepository));
        _context = context ?? throw new ArgumentNullException(nameof(context));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public async Task<BurndownChartDto?> Handle(GetSprintBurndownQuery request, CancellationToken cancellationToken)
    {
        // 1. Get Sprint
        var sprintId = SprintId.From(request.SprintId);
        var sprint = await _projectRepository.GetSprintByIdReadOnlyAsync(sprintId, cancellationToken);

        if (sprint == null)
        {
            _logger.LogWarning("Sprint not found: {SprintId}", request.SprintId);
            return null;
        }

        // 2. Get all tasks in this sprint
        var taskIds = sprint.TaskIds.Select(t => t.Value).ToList();
        var tasks = await _context.Tasks
            .Where(t => taskIds.Contains(t.Id.Value))
            .ToListAsync(cancellationToken);

        // 3. Calculate total story points (simplified: use EstimatedHours as story points for MVP)
        // In Phase 2, add StoryPoints property to WorkTask
        var totalPoints = tasks.Count; // Simple count for MVP
        var completedTasks = tasks.Where(t => t.Status.Name == "Done").ToList();
        var remainingPoints = totalPoints - completedTasks.Count;

        // 4. Calculate ideal burndown (linear)
        var idealBurndown = CalculateIdealBurndown(sprint.StartDate, sprint.EndDate, totalPoints);

        // 5. Calculate actual burndown (based on task completion dates)
        var actualBurndown = CalculateActualBurndown(sprint.StartDate, sprint.EndDate, tasks, totalPoints);

        // 6. Calculate completion percentage
        var completionPercentage = totalPoints > 0
            ? Math.Round((double)(totalPoints - remainingPoints) / totalPoints * 100, 2)
            : 0;

        _logger.LogInformation(
            "Calculated burndown for Sprint {SprintId}: Total={Total}, Remaining={Remaining}, Completion={Completion}%",
            request.SprintId, totalPoints, remainingPoints, completionPercentage);

        return new BurndownChartDto(
            sprint.Id.Value,
            sprint.Name,
            sprint.StartDate,
            sprint.EndDate,
            totalPoints,
            remainingPoints,
            completionPercentage,
            idealBurndown,
            actualBurndown
        );
    }

    /// <summary>
    /// Calculate ideal burndown - linear decrease from total to 0
    /// </summary>
    private List<BurndownDataPoint> CalculateIdealBurndown(DateTime startDate, DateTime endDate, int totalPoints)
    {
        var dataPoints = new List<BurndownDataPoint>();
        var totalDays = (endDate.Date - startDate.Date).Days;

        if (totalDays <= 0)
        {
            dataPoints.Add(new BurndownDataPoint(startDate.Date, totalPoints));
            return dataPoints;
        }

        var pointsPerDay = (double)totalPoints / totalDays;

        for (int day = 0; day <= totalDays; day++)
        {
            var date = startDate.Date.AddDays(day);
            var remaining = totalPoints - (int)Math.Round(pointsPerDay * day);
            dataPoints.Add(new BurndownDataPoint(date, Math.Max(0, remaining)));
        }

        return dataPoints;
    }

    /// <summary>
    /// Calculate actual burndown based on task completion dates
    /// MVP: Uses UpdatedAt timestamp as completion date approximation
    /// Phase 2: Use audit logs for exact completion dates
    /// </summary>
    private List<BurndownDataPoint> CalculateActualBurndown(
        DateTime startDate,
        DateTime endDate,
        List<Domain.Aggregates.ProjectAggregate.WorkTask> tasks,
        int totalPoints)
    {
        var dataPoints = new List<BurndownDataPoint>();
        var currentDate = startDate.Date;
        var finalDate = DateTime.UtcNow.Date < endDate.Date ? DateTime.UtcNow.Date : endDate.Date;

        // Get completed tasks with their completion dates (approximated by UpdatedAt)
        var completedTasks = tasks
            .Where(t => t.Status.Name == "Done" && t.UpdatedAt.HasValue)
            .Select(t => new
            {
                Task = t,
                CompletedDate = t.UpdatedAt!.Value.Date
            })
            .OrderBy(t => t.CompletedDate)
            .ToList();

        var remainingPoints = totalPoints;

        // Generate daily data points
        while (currentDate <= finalDate)
        {
            // Count tasks completed by this date (before end of day)
            var completedByDate = completedTasks
                .Count(tc => tc.CompletedDate <= currentDate);

            remainingPoints = totalPoints - completedByDate;

            dataPoints.Add(new BurndownDataPoint(currentDate, Math.Max(0, remainingPoints)));
            currentDate = currentDate.AddDays(1);
        }

        // If no data points, add at least start point
        if (dataPoints.Count == 0)
        {
            dataPoints.Add(new BurndownDataPoint(startDate.Date, totalPoints));
        }

        return dataPoints;
    }
}
