using CashLineERP.Domain.Common;

namespace CashLineERP.Domain.Identity;

public sealed class AccountingOfficeClientRequest : Entity
{
    public long Id { get; private set; }
    public long ClientPartyId { get; private set; }
    public long? AssignedAccountingOfficeUserId { get; private set; }
    public string RequestNumber { get; private set; } = string.Empty;
    public string RequestTypeCode { get; private set; } = string.Empty;
    public string SubjectText { get; private set; } = string.Empty;
    public string DetailsText { get; private set; } = string.Empty;
    public string StatusCode { get; private set; } = SubmittedStatusCode;

    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? CompletedAtUtc { get; private set; }
    public long? CompletedByUserId { get; private set; }
    public string? CompletionNotesText { get; private set; }
    public DateTime? RejectedAtUtc { get; private set; }
    public long? RejectedByUserId { get; private set; }
    public string? RejectionReasonText { get; private set; }

    public const string DraftStatusCode = "DRAFT";
    public const string SubmittedStatusCode = "SUBMITTED";
    public const string InReviewStatusCode = "IN_REVIEW";
    public const string CompletedStatusCode = "COMPLETED";
    public const string RejectedStatusCode = "REJECTED";

    public AccountingOfficeClientRequest(
        long clientPartyId,
        long? assignedAccountingOfficeUserId,
        string requestNumber,
        string requestTypeCode,
        string subjectText,
        string detailsText,
        long actorUserId)
    {
        if (clientPartyId <= 0)
        {
            throw new InvalidOperationException("Accounting office client request requires a valid client party identifier.");
        }

        if (assignedAccountingOfficeUserId.HasValue && assignedAccountingOfficeUserId.Value <= 0)
        {
            throw new InvalidOperationException("Assigned accounting office user identifier must be valid when provided.");
        }

        ClientPartyId = clientPartyId;
        AssignedAccountingOfficeUserId = assignedAccountingOfficeUserId;
        RequestNumber = NormalizeRequired(requestNumber, "request number");
        RequestTypeCode = NormalizeRequired(requestTypeCode, "request type");
        SubjectText = NormalizeRequiredText(subjectText, "subject");
        DetailsText = NormalizeRequiredText(detailsText, "details");
        StatusCode = SubmittedStatusCode;
        SubmittedAtUtc = DateTime.UtcNow;
        SubmittedByUserId = actorUserId;
        CreatedByUserId = actorUserId;
    }

    private AccountingOfficeClientRequest()
    {
    }

    public void StartReview(long actorUserId, string? reviewNotesText)
    {
        if (!string.Equals(StatusCode, SubmittedStatusCode, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(StatusCode, InReviewStatusCode, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Only submitted or in-review accounting office client requests can be reviewed.");
        }

        ReviewedAtUtc = DateTime.UtcNow;
        ReviewedByUserId = actorUserId;
        if (!string.Equals(StatusCode, InReviewStatusCode, StringComparison.OrdinalIgnoreCase))
        {
            StatusCode = InReviewStatusCode;
        }

        if (!string.IsNullOrWhiteSpace(reviewNotesText))
        {
            CompletionNotesText = reviewNotesText.Trim();
        }

        Touch(actorUserId);
    }

    public void Complete(long actorUserId, string completionNotesText)
    {
        if (!string.Equals(StatusCode, SubmittedStatusCode, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(StatusCode, InReviewStatusCode, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Only submitted or in-review accounting office client requests can be completed.");
        }

        CompletionNotesText = NormalizeRequiredText(completionNotesText, "completion notes");
        CompletedAtUtc = DateTime.UtcNow;
        CompletedByUserId = actorUserId;
        StatusCode = CompletedStatusCode;
        Touch(actorUserId);
    }

    public void Reject(long actorUserId, string rejectionReasonText)
    {
        if (!string.Equals(StatusCode, SubmittedStatusCode, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(StatusCode, InReviewStatusCode, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Only submitted or in-review accounting office client requests can be rejected.");
        }

        RejectionReasonText = NormalizeRequiredText(rejectionReasonText, "rejection reason");
        RejectedAtUtc = DateTime.UtcNow;
        RejectedByUserId = actorUserId;
        StatusCode = RejectedStatusCode;
        Touch(actorUserId);
    }

    public void SetAssignedOfficeUser(long? accountingOfficeUserId, long actorUserId)
    {
        if (accountingOfficeUserId.HasValue && accountingOfficeUserId.Value <= 0)
        {
            throw new InvalidOperationException("Assigned accounting office user identifier must be valid when provided.");
        }

        AssignedAccountingOfficeUserId = accountingOfficeUserId;
        Touch(actorUserId);
    }

    private static string NormalizeRequired(string value, string fieldName)
    {
        var normalizedValue = NormalizeOptional(value);
        if (string.IsNullOrWhiteSpace(normalizedValue))
        {
            throw new InvalidOperationException($"Accounting office client request {fieldName} is required.");
        }

        return normalizedValue;
    }

    private static string NormalizeRequiredText(string value, string fieldName)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new InvalidOperationException($"Accounting office client request {fieldName} is required.");
        }

        return value.Trim();
    }

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

