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

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

/// <summary>
/// Project Aggregate Root
/// Enforces consistency boundary for Project -> Epic -> Story -> Task hierarchy
/// </summary>
public class Project : AggregateRoot
{
    public new ProjectId Id { get; private set; }
    public TenantId TenantId { get; private set; }
    public string Name { get; private set; }
    public string Description { get; private set; }
    public ProjectKey Key { get; private set; }
    public ProjectStatus Status { get; private set; }
    public UserId OwnerId { get; private set; }

    private readonly List<Epic> _epics = new();
    public IReadOnlyCollection<Epic> Epics => _epics.AsReadOnly();

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

    // EF Core constructor
    private Project()
    {
        Id = null!;
        TenantId = null!;
        Name = null!;
        Description = null!;
        Key = null!;
        Status = null!;
        OwnerId = null!;
    }

    // Factory method
    public static Project Create(TenantId tenantId, string name, string description, string key, UserId ownerId)
    {
        // Validation
        if (string.IsNullOrWhiteSpace(name))
            throw new DomainException("Project name cannot be empty");

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

        var project = new Project
        {
            Id = ProjectId.Create(),
            TenantId = tenantId,
            Name = name,
            Description = description ?? string.Empty,
            Key = ProjectKey.Create(key),
            Status = ProjectStatus.Active,
            OwnerId = ownerId,
            CreatedAt = DateTime.UtcNow
        };

        // Raise domain event
        project.AddDomainEvent(new ProjectCreatedEvent(project.Id, project.TenantId, project.Name, ownerId));

        return project;
    }

    // Business methods
    public void UpdateDetails(string name, string description)
    {
        if (string.IsNullOrWhiteSpace(name))
            throw new DomainException("Project name cannot be empty");

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

        Name = name;
        Description = description ?? string.Empty;
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new ProjectUpdatedEvent(Id, Name, Description));
    }

    public Epic CreateEpic(string name, string description, UserId createdBy)
    {
        if (Status == ProjectStatus.Archived)
            throw new DomainException("Cannot create epic in an archived project");

        var epic = Epic.Create(this.TenantId, name, description, this.Id, createdBy);
        _epics.Add(epic);

        AddDomainEvent(new EpicCreatedEvent(epic.Id, this.TenantId, this.Id, epic.Name));

        return epic;
    }

    public void UpdateEpic(EpicId epicId, string name, string description)
    {
        var epic = _epics.FirstOrDefault(e => e.Id == epicId);
        if (epic == null)
            throw new DomainException($"Epic with ID {epicId.Value} not found");

        epic.UpdateDetails(name, description);
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new EpicUpdatedEvent(epic.Id, this.TenantId, this.Id, epic.Name));
    }

    public void DeleteEpic(EpicId epicId)
    {
        var epic = _epics.FirstOrDefault(e => e.Id == epicId);
        if (epic == null)
            throw new DomainException($"Epic with ID {epicId.Value} not found");

        if (epic.Stories.Any())
            throw new DomainException($"Cannot delete epic with ID {epicId.Value}. The epic has {epic.Stories.Count} associated story/stories. Please delete or reassign the stories first.");

        _epics.Remove(epic);
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new EpicDeletedEvent(epicId, this.TenantId, this.Id));
    }

    public Story CreateStory(EpicId epicId, string title, string description, TaskPriority priority, UserId createdBy)
    {
        var epic = _epics.FirstOrDefault(e => e.Id == epicId);
        if (epic == null)
            throw new DomainException($"Epic with ID {epicId.Value} not found");

        var story = epic.CreateStory(title, description, priority, createdBy);
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new StoryCreatedEvent(story.Id, this.TenantId, this.Id, epicId, story.Title));

        return story;
    }

    public void UpdateStory(EpicId epicId, StoryId storyId, string title, string description)
    {
        var epic = _epics.FirstOrDefault(e => e.Id == epicId);
        if (epic == null)
            throw new DomainException($"Epic with ID {epicId.Value} not found");

        var story = epic.Stories.FirstOrDefault(s => s.Id == storyId);
        if (story == null)
            throw new DomainException($"Story with ID {storyId.Value} not found in epic");

        story.UpdateDetails(title, description);
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new StoryUpdatedEvent(storyId, this.TenantId, this.Id, epicId, story.Title));
    }

    public void DeleteStory(EpicId epicId, StoryId storyId)
    {
        var epic = _epics.FirstOrDefault(e => e.Id == epicId);
        if (epic == null)
            throw new DomainException($"Epic with ID {epicId.Value} not found");

        epic.RemoveStory(storyId);
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new StoryDeletedEvent(storyId, this.TenantId, this.Id, epicId));
    }

    public WorkTask CreateTask(EpicId epicId, StoryId storyId, string title, string description, TaskPriority priority, UserId createdBy)
    {
        var epic = _epics.FirstOrDefault(e => e.Id == epicId);
        if (epic == null)
            throw new DomainException($"Epic with ID {epicId.Value} not found");

        var story = epic.Stories.FirstOrDefault(s => s.Id == storyId);
        if (story == null)
            throw new DomainException($"Story with ID {storyId.Value} not found in epic");

        var task = story.CreateTask(title, description, priority, createdBy);
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new TaskCreatedEvent(task.Id, this.TenantId, this.Id, storyId, task.Title));

        return task;
    }

    public void UpdateTask(EpicId epicId, StoryId storyId, TaskId taskId, string title, string description)
    {
        var epic = _epics.FirstOrDefault(e => e.Id == epicId);
        if (epic == null)
            throw new DomainException($"Epic with ID {epicId.Value} not found");

        var story = epic.Stories.FirstOrDefault(s => s.Id == storyId);
        if (story == null)
            throw new DomainException($"Story with ID {storyId.Value} not found in epic");

        var task = story.Tasks.FirstOrDefault(t => t.Id == taskId);
        if (task == null)
            throw new DomainException($"Task with ID {taskId.Value} not found in story");

        task.UpdateDetails(title, description);
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new TaskUpdatedEvent(taskId, this.TenantId, this.Id, storyId, task.Title));
    }

    public void DeleteTask(EpicId epicId, StoryId storyId, TaskId taskId)
    {
        var epic = _epics.FirstOrDefault(e => e.Id == epicId);
        if (epic == null)
            throw new DomainException($"Epic with ID {epicId.Value} not found");

        var story = epic.Stories.FirstOrDefault(s => s.Id == storyId);
        if (story == null)
            throw new DomainException($"Story with ID {storyId.Value} not found in epic");

        story.RemoveTask(taskId);
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new TaskDeletedEvent(taskId, this.TenantId, this.Id, storyId));
    }

    public void AssignTask(EpicId epicId, StoryId storyId, TaskId taskId, UserId assigneeId)
    {
        var epic = _epics.FirstOrDefault(e => e.Id == epicId);
        if (epic == null)
            throw new DomainException($"Epic with ID {epicId.Value} not found");

        var story = epic.Stories.FirstOrDefault(s => s.Id == storyId);
        if (story == null)
            throw new DomainException($"Story with ID {storyId.Value} not found in epic");

        var task = story.Tasks.FirstOrDefault(t => t.Id == taskId);
        if (task == null)
            throw new DomainException($"Task with ID {taskId.Value} not found in story");

        task.AssignTo(assigneeId);
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new TaskAssignedEvent(taskId, this.TenantId, this.Id, storyId, assigneeId));
    }

    public void Archive()
    {
        if (Status == ProjectStatus.Archived)
            throw new DomainException("Project is already archived");

        Status = ProjectStatus.Archived;
        UpdatedAt = DateTime.UtcNow;

        AddDomainEvent(new ProjectArchivedEvent(Id));
    }

    public void Activate()
    {
        if (Status == ProjectStatus.Active)
            throw new DomainException("Project is already active");

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