using CashLineERP.Domain.Common;

namespace CashLineERP.Domain.Parties;

public sealed class CounterpartyDocumentWorkflow : Entity
{
    public long Id { get; private set; }
    public long PartyId { get; private set; }
    public string PartyTypeCode { get; private set; } = string.Empty;
    public long CurrentDocumentLinkId { get; private set; }
    public string DocumentTypeCode { get; private set; } = string.Empty;
    public string StatusCode { get; private set; } = DraftStatusCode;
    public string? WorkflowNotesText { get; private set; }
    public string? ReviewNotesText { get; private set; }
    public DateTime? SubmittedAtUtc { get; private set; }
    public long? SubmittedByUserId { get; private set; }
    public DateTime? ReviewedAtUtc { get; private set; }
    public long? ReviewedByUserId { get; private set; }
    public DateTime? ReturnedAtUtc { get; private set; }
    public long? ReturnedByUserId { get; private set; }
    public DateTime? RejectedAtUtc { get; private set; }
    public long? RejectedByUserId { get; private set; }

    public const string DraftStatusCode = "DRAFT";
    public const string SubmittedStatusCode = "SUBMITTED";
    public const string ReturnedStatusCode = "RETURNED";
    public const string ApprovedStatusCode = "APPROVED";
    public const string RejectedStatusCode = "REJECTED";

    public CounterpartyDocumentWorkflow(
        long partyId,
        string partyTypeCode,
        long currentDocumentLinkId,
        string documentTypeCode,
        string? workflowNotesText,
        long? actorUserId)
    {
        if (partyId <= 0)
        {
            throw new InvalidOperationException("Counterparty document workflow requires a valid party identifier.");
        }

        if (currentDocumentLinkId <= 0)
        {
            throw new InvalidOperationException("Counterparty document workflow requires a valid document link identifier.");
        }

        PartyId = partyId;
        PartyTypeCode = NormalizeRequired(partyTypeCode, "party type");
        CurrentDocumentLinkId = currentDocumentLinkId;
        DocumentTypeCode = NormalizeRequired(documentTypeCode, "document type");
        WorkflowNotesText = NormalizeOptionalText(workflowNotesText);
        CreatedByUserId = actorUserId;
    }

    private CounterpartyDocumentWorkflow()
    {
    }

    public void ReplaceDocument(long actorUserId, long replacementDocumentLinkId, string? workflowNotesText)
    {
        if (replacementDocumentLinkId <= 0)
        {
            throw new InvalidOperationException("Replacement document link identifier must be valid.");
        }

        if (!string.Equals(StatusCode, DraftStatusCode, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(StatusCode, ReturnedStatusCode, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Only draft or returned counterparty document workflows can replace the current document.");
        }

        CurrentDocumentLinkId = replacementDocumentLinkId;
        WorkflowNotesText = NormalizeOptionalText(workflowNotesText) ?? WorkflowNotesText;
        StatusCode = DraftStatusCode;
        ReviewNotesText = null;
        ReturnedAtUtc = null;
        ReturnedByUserId = null;
        RejectedAtUtc = null;
        RejectedByUserId = null;
        Touch(actorUserId);
    }

    public void Submit(long actorUserId)
    {
        if (!string.Equals(StatusCode, DraftStatusCode, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(StatusCode, ReturnedStatusCode, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Only draft or returned counterparty document workflows can be submitted.");
        }

        StatusCode = SubmittedStatusCode;
        SubmittedAtUtc = DateTime.UtcNow;
        SubmittedByUserId = actorUserId;
        ReviewedAtUtc = null;
        ReviewedByUserId = null;
        ReviewNotesText = null;
        RejectedAtUtc = null;
        RejectedByUserId = null;
        ReturnedAtUtc = null;
        ReturnedByUserId = null;
        Touch(actorUserId);
    }

    public void Approve(long actorUserId, string? reviewNotesText)
    {
        EnsureSubmitted();
        StatusCode = ApprovedStatusCode;
        ReviewedAtUtc = DateTime.UtcNow;
        ReviewedByUserId = actorUserId;
        ReviewNotesText = NormalizeOptionalText(reviewNotesText);
        ReturnedAtUtc = null;
        ReturnedByUserId = null;
        RejectedAtUtc = null;
        RejectedByUserId = null;
        Touch(actorUserId);
    }

    public void Return(long actorUserId, string? reviewNotesText)
    {
        EnsureSubmitted();
        StatusCode = ReturnedStatusCode;
        ReviewedAtUtc = DateTime.UtcNow;
        ReviewedByUserId = actorUserId;
        ReturnedAtUtc = ReviewedAtUtc;
        ReturnedByUserId = actorUserId;
        ReviewNotesText = NormalizeOptionalText(reviewNotesText);
        RejectedAtUtc = null;
        RejectedByUserId = null;
        Touch(actorUserId);
    }

    public void Reject(long actorUserId, string? reviewNotesText)
    {
        EnsureSubmitted();
        StatusCode = RejectedStatusCode;
        ReviewedAtUtc = DateTime.UtcNow;
        ReviewedByUserId = actorUserId;
        RejectedAtUtc = ReviewedAtUtc;
        RejectedByUserId = actorUserId;
        ReviewNotesText = NormalizeOptionalText(reviewNotesText);
        ReturnedAtUtc = null;
        ReturnedByUserId = null;
        Touch(actorUserId);
    }

    private void EnsureSubmitted()
    {
        if (!string.Equals(StatusCode, SubmittedStatusCode, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Counterparty document workflow must be submitted before review.");
        }
    }

    private static string NormalizeRequired(string value, string fieldName)
    {
        var normalizedValue = NormalizeOptionalCode(value);
        if (string.IsNullOrWhiteSpace(normalizedValue))
        {
            throw new InvalidOperationException($"Counterparty document workflow {fieldName} is required.");
        }

        return normalizedValue;
    }

    private static string? NormalizeOptionalCode(string? value) =>
        string.IsNullOrWhiteSpace(value) ? null : value.Trim().ToUpperInvariant();

    private static string? NormalizeOptionalText(string? value) =>
        string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}
