using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Domain.Entities.Donations.ValueObject;
using Domain.Entities.Members;

namespace Domain.Entities.Donations;

public class DonationAlert
{
    [ForeignKey(nameof(DonationID))]
    public virtual Donation Donation { get; private set; } = default!;

    [ForeignKey(nameof(SponsorMemberID))]
    public virtual Member? Sponsor { get; private set; }

    [ForeignKey(nameof(ReceiverMemberID))]
    public virtual Member? Receiver { get; private set; }

    private readonly List<DonationAlertAttempt> _attempts = [];
    public IReadOnlyCollection<DonationAlertAttempt> Attempts => _attempts;

    [Key]
    public int ID { get; private set; }

    public int DonationID { get; private set; }

    public int SponsorMemberID { get; private set; }

    public int ReceiverMemberID { get; private set; }

    public AlertStatus Status { get; private set; } = AlertStatus.Queued;

    public Guid CorrelationID { get; private set; } = Guid.NewGuid();

    public DateTime StartAt { get; private set; } = DateTime.UtcNow;

    public DateTime? ArrivedAt { get; private set; }

    public double? TotalDelayMs { get; private set; }

    public string? IpAddress { get; private set; }

    public string? UserAgent { get; private set; }

    public DateTime CreatedAt { get; private set; } = DateTime.UtcNow;

    private DonationAlert() { }

    public static DonationAlert Create(
        int donationID,
        int sponsorMemberID,
        int receiverMemberID,
        string? ipAddress = null,
        string? userAgent = null
    )
    {
        return new DonationAlert
        {
            DonationID = donationID,
            SponsorMemberID = sponsorMemberID,
            ReceiverMemberID = receiverMemberID,
            IpAddress = ipAddress,
            UserAgent = userAgent
        };
    }

    public void MarkPlaying()
    {
        Status = AlertStatus.Playing;
    }

    public void MarkDelivered()
    {
        Status = AlertStatus.Delivered;
        ArrivedAt = DateTime.UtcNow;
        TotalDelayMs = (ArrivedAt.Value - StartAt).TotalMilliseconds;
    }

    public void MarkSkipped()
    {
        if (Status != AlertStatus.Playing)
        {
            throw new InvalidOperationException("Only playing alerts can be skipped.");
        }

        Status = AlertStatus.Skipped;
    }

    public void MarkIgnored()
    {
        if (Status == AlertStatus.Playing)
        {
            throw new InvalidOperationException("Cannot ignore a playing alert.");
        }

        Status = AlertStatus.Ignored;
    }

    public void MarkFailed()
    {
        Status = AlertStatus.Failed;
    }

    public void Resend()
    {
        if (Status is not (AlertStatus.Failed or AlertStatus.Skipped))
        {
            throw new InvalidOperationException("Only failed or skipped alerts can be resent.");
        }

        Status = AlertStatus.Queued;
        ArrivedAt = null;
        TotalDelayMs = null;
    }
}