using CashLineERP.Domain.Common;

namespace CashLineERP.Domain.Customers;

public sealed class CustomerServiceRequest : Entity
{
    public long Id { get; private set; }
    public long CustomerPartyId { 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 PriorityCode { get; private set; } = "NORMAL";
    public string StatusCode { get; private set; } = DraftStatusCode;
    public string CommercialImpactCode { get; private set; } = "SERVICE";
    public string RequestedResolutionCode { get; private set; } = "CLARIFICATION";
    public string AssignedBackofficeLaneCode { get; private set; } = "COMMERCIAL";
    public int EscalationLevel { get; private set; }
    public string? RelatedDocumentType { get; private set; }
    public long? RelatedDocumentId { get; private set; }
    public string? InternalResponseText { get; private set; }
    public string? EscalationReasonText { 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? EscalatedAtUtc { get; private set; }
    public long? EscalatedByUserId { get; private set; }
    public DateTime? RespondedAtUtc { get; private set; }
    public long? RespondedByUserId { get; private set; }
    public DateTime? RejectedAtUtc { get; private set; }
    public long? RejectedByUserId { get; private set; }
    public DateTime? ClosedAtUtc { get; private set; }
    public long? ClosedByUserId { get; private set; }

    public const string DraftStatusCode = "DRAFT";
    public const string SubmittedStatusCode = "SUBMITTED";
    public const string InReviewStatusCode = "IN_REVIEW";
    public const string EscalatedStatusCode = "ESCALATED";
    public const string RespondedStatusCode = "RESPONDED";
    public const string RejectedStatusCode = "REJECTED";
    public const string ClosedStatusCode = "CLOSED";

    public CustomerServiceRequest(
        long customerPartyId,
        string requestNumber,
        string requestTypeCode,
        string subjectText,
        string detailsText,
        string priorityCode,
        string commercialImpactCode,
        string requestedResolutionCode,
        string assignedBackofficeLaneCode,
        string? relatedDocumentType,
        long? relatedDocumentId,
        long? actorUserId)
    {
        if (customerPartyId <= 0)
        {
            throw new InvalidOperationException("Customer service request requires a valid customer party identifier.");
        }

        if (relatedDocumentId.HasValue && relatedDocumentId.Value <= 0)
        {
            throw new InvalidOperationException("Customer service request related document identifier must be valid when provided.");
        }

        CustomerPartyId = customerPartyId;
        RequestNumber = NormalizeRequired(requestNumber, "request number");
        RequestTypeCode = NormalizeRequired(requestTypeCode, "request type");
        SubjectText = NormalizeRequiredText(subjectText, "subject");
        DetailsText = NormalizeRequiredText(detailsText, "details");
        PriorityCode = NormalizeRequired(priorityCode, "priority");
        CommercialImpactCode = NormalizeRequired(commercialImpactCode, "commercial impact");
        RequestedResolutionCode = NormalizeRequired(requestedResolutionCode, "requested resolution");
        AssignedBackofficeLaneCode = NormalizeRequired(assignedBackofficeLaneCode, "assigned back-office lane");
        RelatedDocumentType = NormalizeOptional(relatedDocumentType);
        RelatedDocumentId = relatedDocumentId;
        CreatedByUserId = actorUserId;
    }

    private CustomerServiceRequest()
    {
    }

    public void Submit(long actorUserId)
    {
        if (!string.Equals(StatusCode, DraftStatusCode, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Only draft customer service requests can be submitted.");
        }

        StatusCode = SubmittedStatusCode;
        SubmittedAtUtc = DateTime.UtcNow;
        SubmittedByUserId = actorUserId;
        Touch(actorUserId);
    }

    public void StartReview(long actorUserId)
    {
        if (!string.Equals(StatusCode, SubmittedStatusCode, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(StatusCode, RespondedStatusCode, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(StatusCode, EscalatedStatusCode, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Only submitted, responded, or escalated customer service requests can enter review.");
        }

        StatusCode = InReviewStatusCode;
        ReviewedAtUtc = DateTime.UtcNow;
        ReviewedByUserId = actorUserId;
        Touch(actorUserId);
    }

    public void Escalate(long actorUserId, string escalationReason, string? assignedBackofficeLaneCode, string? requestedResolutionCode)
    {
        if (!string.Equals(StatusCode, SubmittedStatusCode, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(StatusCode, InReviewStatusCode, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(StatusCode, RespondedStatusCode, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Only submitted, in-review, or responded customer service requests can be escalated.");
        }

        EscalationReasonText = NormalizeRequiredText(escalationReason, "escalation reason");
        if (!string.IsNullOrWhiteSpace(assignedBackofficeLaneCode))
        {
            AssignedBackofficeLaneCode = NormalizeRequired(assignedBackofficeLaneCode, "assigned back-office lane");
        }

        if (!string.IsNullOrWhiteSpace(requestedResolutionCode))
        {
            RequestedResolutionCode = NormalizeRequired(requestedResolutionCode, "requested resolution");
        }

        EscalationLevel += 1;
        StatusCode = EscalatedStatusCode;
        EscalatedAtUtc = DateTime.UtcNow;
        EscalatedByUserId = actorUserId;
        Touch(actorUserId);
    }

    public void RouteCommercialEscalation(long actorUserId, string assignedBackofficeLaneCode, string? requestedResolutionCode)
    {
        if (!string.Equals(StatusCode, SubmittedStatusCode, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(StatusCode, InReviewStatusCode, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(StatusCode, EscalatedStatusCode, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(StatusCode, RespondedStatusCode, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Only submitted, in-review, escalated, or responded customer service requests can be routed for deeper commercial handling.");
        }

        AssignedBackofficeLaneCode = NormalizeRequired(assignedBackofficeLaneCode, "assigned back-office lane");
        if (!string.IsNullOrWhiteSpace(requestedResolutionCode))
        {
            RequestedResolutionCode = NormalizeRequired(requestedResolutionCode, "requested resolution");
        }

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

        Touch(actorUserId);
    }

    public void Respond(long actorUserId, string responseText)
    {
        if (!string.Equals(StatusCode, SubmittedStatusCode, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(StatusCode, InReviewStatusCode, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(StatusCode, EscalatedStatusCode, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Only submitted, in-review, or escalated customer service requests can be responded to.");
        }

        InternalResponseText = NormalizeRequiredText(responseText, "response");
        StatusCode = RespondedStatusCode;
        RespondedAtUtc = DateTime.UtcNow;
        RespondedByUserId = actorUserId;
        ReviewedAtUtc ??= RespondedAtUtc;
        ReviewedByUserId ??= actorUserId;
        Touch(actorUserId);
    }

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

        InternalResponseText = NormalizeRequiredText(responseText, "response");
        StatusCode = RejectedStatusCode;
        RejectedAtUtc = DateTime.UtcNow;
        RejectedByUserId = actorUserId;
        ReviewedAtUtc ??= RejectedAtUtc;
        ReviewedByUserId ??= actorUserId;
        Touch(actorUserId);
    }

    public void Close(long actorUserId, string? responseText)
    {
        if (!string.Equals(StatusCode, RespondedStatusCode, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(StatusCode, InReviewStatusCode, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(StatusCode, EscalatedStatusCode, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Only responded, in-review, or escalated customer service requests can be closed.");
        }

        if (!string.IsNullOrWhiteSpace(responseText))
        {
            InternalResponseText = responseText.Trim();
        }

        StatusCode = ClosedStatusCode;
        ClosedAtUtc = DateTime.UtcNow;
        ClosedByUserId = actorUserId;
        Touch(actorUserId);
    }

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

        return normalizedValue;
    }

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

        return value.Trim();
    }

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