using ColaFlow.Shared.Kernel.Common;
using ColaFlow.Modules.ProjectManagement.Domain.Exceptions;
using ColaFlow.Modules.ProjectManagement.Domain.ValueObjects;
using ColaFlow.Modules.ProjectManagement.Domain.Enums;
using ColaFlow.Modules.ProjectManagement.Domain.Events;

namespace ColaFlow.Modules.ProjectManagement.Domain.Aggregates.ProjectAggregate;

/// <summary>
/// Sprint Entity (part of Project aggregate)
/// </summary>
public class Sprint : Entity
{
    public new SprintId Id { get; private set; }
    public TenantId TenantId { get; private set; }
    public ProjectId ProjectId { get; private set; }
    public string Name { get; private set; }
    public string? Goal { get; private set; }
    public DateTime StartDate { get; private set; }
    public DateTime EndDate { get; private set; }
    public SprintStatus Status { get; private set; }

    private readonly List<TaskId> _taskIds = new();
    public IReadOnlyCollection<TaskId> TaskIds => _taskIds.AsReadOnly();

    public DateTime CreatedAt { get; private set; }
    public UserId CreatedBy { get; private set; }
    public DateTime? UpdatedAt { get; private set; }

    // EF Core constructor
    private Sprint()
    {
        Id = null!;
        TenantId = null!;
        ProjectId = null!;
        Name = null!;
        Status = null!;
        CreatedBy = null!;
    }

    /// <summary>
    /// Create a new Sprint
    /// </summary>
    public static Sprint Create(
        TenantId tenantId,
        ProjectId projectId,
        string name,
        string? goal,
        DateTime startDate,
        DateTime endDate,
        UserId createdBy)
    {
        ValidateName(name);
        ValidateDates(startDate, endDate);

        return new Sprint
        {
            Id = SprintId.Create(),
            TenantId = tenantId,
            ProjectId = projectId,
            Name = name,
            Goal = goal,
            StartDate = startDate,
            EndDate = endDate,
            Status = SprintStatus.Planned,
            CreatedAt = DateTime.UtcNow,
            CreatedBy = createdBy
        };
    }

    /// <summary>
    /// Update sprint details
    /// </summary>
    public void UpdateDetails(string name, string? goal, DateTime startDate, DateTime endDate)
    {
        if (Status.Name == SprintStatus.Completed.Name)
            throw new DomainException("Cannot update a completed sprint");

        ValidateName(name);
        ValidateDates(startDate, endDate);

        Name = name;
        Goal = goal;
        StartDate = startDate;
        EndDate = endDate;
        UpdatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// Start the sprint (Planned → Active)
    /// </summary>
    public void Start()
    {
        if (Status.Name != SprintStatus.Planned.Name)
            throw new DomainException($"Cannot start sprint in {Status.Name} status. Sprint must be in Planned status.");

        Status = SprintStatus.Active;
        UpdatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// Complete the sprint (Active → Completed)
    /// </summary>
    public void Complete()
    {
        if (Status.Name != SprintStatus.Active.Name)
            throw new DomainException($"Cannot complete sprint in {Status.Name} status. Sprint must be in Active status.");

        Status = SprintStatus.Completed;
        UpdatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// Add a task to the sprint
    /// </summary>
    public void AddTask(TaskId taskId)
    {
        if (Status.Name == SprintStatus.Completed.Name)
            throw new DomainException("Cannot add tasks to a completed sprint");

        if (_taskIds.Any(t => t.Value == taskId.Value))
            throw new DomainException("Task is already in this sprint");

        _taskIds.Add(taskId);
        UpdatedAt = DateTime.UtcNow;
    }

    /// <summary>
    /// Remove a task from the sprint
    /// </summary>
    public void RemoveTask(TaskId taskId)
    {
        if (Status.Name == SprintStatus.Completed.Name)
            throw new DomainException("Cannot remove tasks from a completed sprint");

        var task = _taskIds.FirstOrDefault(t => t.Value == taskId.Value);
        if (task == null)
            throw new DomainException("Task is not in this sprint");

        _taskIds.Remove(task);
        UpdatedAt = DateTime.UtcNow;
    }

    private static void ValidateName(string name)
    {
        if (string.IsNullOrWhiteSpace(name))
            throw new DomainException("Sprint name cannot be empty");

        if (name.Length > 200)
            throw new DomainException("Sprint name cannot exceed 200 characters");
    }

    private static void ValidateDates(DateTime startDate, DateTime endDate)
    {
        if (endDate <= startDate)
            throw new DomainException("Sprint end date must be after start date");

        var duration = (endDate - startDate).Days;
        if (duration > 30)
            throw new DomainException("Sprint duration cannot exceed 30 days");

        if (duration < 1)
            throw new DomainException("Sprint duration must be at least 1 day");
    }
}
