using MktXService.Domain.Events;
using MktXService.Domain.Exceptions;
using MktXService.Domain.SeedWork;

namespace MktXService.Domain.AggregatesModel.CampaignAggregate;

/// <summary>
/// EN: Campaign aggregate root for bulk messaging campaigns with scheduling.
/// VI: Aggregate root chiến dịch cho các chiến dịch gửi tin nhắn hàng loạt với lịch trình.
/// </summary>
public class Campaign : Entity, IAggregateRoot
{
    private Guid _merchantId;
    private string _name = null!;
    private string _type = null!;
    private CampaignStatus _status = null!;
    private Guid? _templateId;
    private List<Guid> _segmentIds = new();
    private CampaignSchedule? _schedule;
    private CampaignMetrics _metrics = null!;
    private DateTime _createdAt;
    private DateTime? _updatedAt;

    public Guid MerchantId => _merchantId;
    public string Name => _name;
    public string Type => _type;
    public CampaignStatus Status => _status;
    public int StatusId { get; private set; }
    public Guid? TemplateId => _templateId;
    public IReadOnlyList<Guid> SegmentIds => _segmentIds.AsReadOnly();
    public CampaignSchedule? Schedule => _schedule;
    public CampaignMetrics Metrics => _metrics;
    public DateTime CreatedAt => _createdAt;
    public DateTime? UpdatedAt => _updatedAt;

    protected Campaign() { }

    public Campaign(Guid merchantId, string name, string type, Guid? templateId = null) : this()
    {
        if (merchantId == Guid.Empty)
            throw new CampaignDomainException("Merchant ID cannot be empty");
        if (string.IsNullOrWhiteSpace(name))
            throw new CampaignDomainException("Campaign name cannot be empty");
        if (string.IsNullOrWhiteSpace(type))
            throw new CampaignDomainException("Campaign type cannot be empty");

        Id = Guid.NewGuid();
        _merchantId = merchantId;
        _name = name;
        _type = type;
        _templateId = templateId;
        _status = CampaignStatus.Draft;
        StatusId = CampaignStatus.Draft.Id;
        _metrics = new CampaignMetrics(0, 0, 0, 0, 0, 0);
        _createdAt = DateTime.UtcNow;
    }

    public void SetTemplate(Guid templateId)
    {
        if (_status != CampaignStatus.Draft)
            throw new CampaignDomainException("Cannot modify campaign after it has started");

        _templateId = templateId;
        _updatedAt = DateTime.UtcNow;
    }

    public void AddSegment(Guid segmentId)
    {
        if (_status != CampaignStatus.Draft && _status != CampaignStatus.Scheduled)
            throw new CampaignDomainException("Cannot modify campaign after it has started");

        if (!_segmentIds.Contains(segmentId))
        {
            _segmentIds.Add(segmentId);
            _updatedAt = DateTime.UtcNow;
        }
    }

    public void RemoveSegment(Guid segmentId)
    {
        if (_status != CampaignStatus.Draft)
            throw new CampaignDomainException("Cannot modify campaign after it has started");

        _segmentIds.Remove(segmentId);
        _updatedAt = DateTime.UtcNow;
    }

    public void SetSchedule(CampaignSchedule schedule)
    {
        if (_status != CampaignStatus.Draft)
            throw new CampaignDomainException("Cannot modify campaign after it has started");

        _schedule = schedule;
        _status = CampaignStatus.Scheduled;
        StatusId = CampaignStatus.Scheduled.Id;
        _updatedAt = DateTime.UtcNow;
    }

    public void Start()
    {
        if (_status != CampaignStatus.Draft && _status != CampaignStatus.Scheduled)
            throw new CampaignDomainException("Only draft or scheduled campaigns can be started");

        if (!_templateId.HasValue)
            throw new CampaignDomainException("Campaign must have a template");

        if (_segmentIds.Count == 0)
            throw new CampaignDomainException("Campaign must have at least one segment");

        var previousStatus = _status;
        _status = CampaignStatus.Running;
        StatusId = CampaignStatus.Running.Id;
        _updatedAt = DateTime.UtcNow;

        AddDomainEvent(new CampaignStartedDomainEvent(Id, _segmentIds.Count));
    }

    public void Pause()
    {
        if (_status != CampaignStatus.Running)
            throw new CampaignDomainException("Only running campaigns can be paused");

        _status = CampaignStatus.Paused;
        StatusId = CampaignStatus.Paused.Id;
        _updatedAt = DateTime.UtcNow;
    }

    public void Resume()
    {
        if (_status != CampaignStatus.Paused)
            throw new CampaignDomainException("Only paused campaigns can be resumed");

        _status = CampaignStatus.Running;
        StatusId = CampaignStatus.Running.Id;
        _updatedAt = DateTime.UtcNow;
    }

    public void Complete(CampaignMetrics finalMetrics)
    {
        _status = CampaignStatus.Completed;
        StatusId = CampaignStatus.Completed.Id;
        _metrics = finalMetrics;
        _updatedAt = DateTime.UtcNow;

        AddDomainEvent(new CampaignCompletedDomainEvent(Id, finalMetrics));
    }

    public void Cancel()
    {
        if (_status == CampaignStatus.Completed || _status == CampaignStatus.Cancelled)
            throw new CampaignDomainException("Cannot cancel a completed or already cancelled campaign");

        _status = CampaignStatus.Cancelled;
        StatusId = CampaignStatus.Cancelled.Id;
        _updatedAt = DateTime.UtcNow;
    }

    public void UpdateMetrics(CampaignMetrics metrics)
    {
        _metrics = metrics;
        _updatedAt = DateTime.UtcNow;
    }
}

/// <summary>
/// EN: Campaign scheduling configuration value object.
/// VI: Value object cấu hình lịch trình chiến dịch.
/// </summary>
public record CampaignSchedule(DateTime? StartAt, DateTime? EndAt, string? Recurrence);

/// <summary>
/// EN: Campaign metrics value object.
/// VI: Value object chỉ số chiến dịch.
/// </summary>
public record CampaignMetrics(int TotalSent, int Delivered, int Opened, int Clicked, int Replied, int Failed);
