using CashLineERP.Domain.Common;
using CashLineERP.SharedKernel.Workflow;

namespace CashLineERP.Domain.Suppliers;

public sealed class SupplierProfile : Entity
{
    public long Id { get; private set; }
    public string SupplierProfileCode { get; private set; } = string.Empty;
    public long BusinessPartyId { get; private set; }
    public string SupplierCategoryCode { get; private set; } = string.Empty;
    public string DefaultCurrencyCode { get; private set; } = string.Empty;
    public string PreferredPaymentMethodCode { get; private set; } = string.Empty;
    public int PaymentTermsDays { get; private set; }
    public int LeadTimeDays { get; private set; }
    public decimal? MinimumOrderAmount { get; private set; }
    public string BankName { get; private set; } = string.Empty;
    public string BankAccountNumber { get; private set; } = string.Empty;
    public string? BankSwiftCode { get; private set; }
    public string BankVerificationStatusCode { get; private set; } = BankVerificationStatus.Pending;
    public DateTime? BankVerifiedAtUtc { get; private set; }
    public long? BankVerifiedByUserId { get; private set; }
    public string? BankVerificationReference { get; private set; }
    public string RiskRatingCode { get; private set; } = RiskRating.Medium;
    public decimal? PerformanceScore { get; private set; }
    public decimal? OnTimeDeliveryScore { get; private set; }
    public decimal? QualityScore { get; private set; }
    public DateTime? LastPerformanceReviewAtUtc { get; private set; }
    public long? LastPerformanceReviewedByUserId { get; private set; }
    public string? PerformanceNotes { get; private set; }
    public string ComplianceStatusCode { get; private set; } = ComplianceStatus.Pending;
    public long StatusId { get; private set; } = WorkflowStatusSeedIds.Draft;
    public bool IsActive { get; private set; }
    public string? Notes { get; private set; }
    public DateTime? SubmittedAtUtc { get; private set; }
    public long? SubmittedByUserId { get; private set; }
    public DateTime? ApprovedAtUtc { get; private set; }
    public long? ApprovedByUserId { get; private set; }
    public DateTime? ActivatedAtUtc { get; private set; }
    public long? ActivatedByUserId { get; private set; }
    public bool IsOnHold { get; private set; }
    public string? HoldReason { get; private set; }
    public DateTime? HeldAtUtc { get; private set; }
    public long? HeldByUserId { get; private set; }
    public string? ReviewDecisionNotes { get; private set; }
    public DateTime? ReopenedAtUtc { get; private set; }
    public long? ReopenedByUserId { get; private set; }
    public string? ReopenReason { get; private set; }

    public SupplierProfile(
        string supplierProfileCode,
        long businessPartyId,
        string supplierCategoryCode,
        string defaultCurrencyCode,
        string preferredPaymentMethodCode,
        int paymentTermsDays,
        int leadTimeDays,
        decimal? minimumOrderAmount,
        string bankName,
        string bankAccountNumber,
        string? bankSwiftCode,
        string? notes)
    {
        if (string.IsNullOrWhiteSpace(supplierProfileCode))
        {
            throw new InvalidOperationException("Supplier profile code is required.");
        }

        if (businessPartyId <= 0)
        {
            throw new InvalidOperationException("Supplier profile requires a valid supplier business party.");
        }

        if (string.IsNullOrWhiteSpace(supplierCategoryCode))
        {
            throw new InvalidOperationException("Supplier category code is required.");
        }

        if (string.IsNullOrWhiteSpace(defaultCurrencyCode))
        {
            throw new InvalidOperationException("Supplier default currency code is required.");
        }

        if (string.IsNullOrWhiteSpace(preferredPaymentMethodCode))
        {
            throw new InvalidOperationException("Supplier preferred payment method code is required.");
        }

        if (paymentTermsDays is < 0 or > 365)
        {
            throw new InvalidOperationException("Supplier payment terms must be between 0 and 365 days.");
        }

        if (leadTimeDays is < 0 or > 365)
        {
            throw new InvalidOperationException("Supplier lead time must be between 0 and 365 days.");
        }

        if (minimumOrderAmount.HasValue && minimumOrderAmount.Value < 0m)
        {
            throw new InvalidOperationException("Supplier minimum order amount cannot be negative.");
        }

        if (string.IsNullOrWhiteSpace(bankName))
        {
            throw new InvalidOperationException("Supplier bank name is required.");
        }

        if (string.IsNullOrWhiteSpace(bankAccountNumber))
        {
            throw new InvalidOperationException("Supplier bank account number is required.");
        }

        SupplierProfileCode = supplierProfileCode.Trim().ToUpperInvariant();
        BusinessPartyId = businessPartyId;
        SupplierCategoryCode = supplierCategoryCode.Trim().ToUpperInvariant();
        DefaultCurrencyCode = defaultCurrencyCode.Trim().ToUpperInvariant();
        PreferredPaymentMethodCode = preferredPaymentMethodCode.Trim().ToUpperInvariant();
        PaymentTermsDays = paymentTermsDays;
        LeadTimeDays = leadTimeDays;
        MinimumOrderAmount = minimumOrderAmount;
        BankName = bankName.Trim();
        BankAccountNumber = bankAccountNumber.Trim();
        BankSwiftCode = string.IsNullOrWhiteSpace(bankSwiftCode) ? null : bankSwiftCode.Trim().ToUpperInvariant();
        ComplianceStatusCode = ComplianceStatus.Pending;
        Notes = string.IsNullOrWhiteSpace(notes) ? null : notes.Trim();
    }

    private SupplierProfile()
    {
    }

    public void Submit(long actorUserId)
    {
        if (StatusId != WorkflowStatusSeedIds.Draft && StatusId != WorkflowStatusSeedIds.Returned)
        {
            throw new InvalidOperationException("Only draft or returned supplier profiles can be submitted.");
        }

        StatusId = WorkflowStatusSeedIds.Submitted;
        SubmittedAtUtc = DateTime.UtcNow;
        SubmittedByUserId = actorUserId;
        ApprovedAtUtc = null;
        ApprovedByUserId = null;
        ActivatedAtUtc = null;
        ActivatedByUserId = null;
        IsOnHold = false;
        HoldReason = null;
        HeldAtUtc = null;
        HeldByUserId = null;
        ReviewDecisionNotes = null;
        Touch(actorUserId);
    }

    public void Approve(long actorUserId)
    {
        if (StatusId == WorkflowStatusSeedIds.Approved)
        {
            return;
        }

        if (StatusId != WorkflowStatusSeedIds.Submitted)
        {
            throw new InvalidOperationException("Only submitted supplier profiles can be approved.");
        }

        if (IsOnHold)
        {
            throw new InvalidOperationException("Supplier profile on hold cannot be approved.");
        }

        if (SubmittedByUserId == actorUserId)
        {
            throw new InvalidOperationException("Supplier profile maker-checker control prohibits self-approval.");
        }

        ComplianceStatusCode = ComplianceStatus.Approved;
        StatusId = WorkflowStatusSeedIds.Approved;
        ApprovedAtUtc = DateTime.UtcNow;
        ApprovedByUserId = actorUserId;
        ReviewDecisionNotes = null;
        Touch(actorUserId);
    }

    public void Return(long actorUserId, string reviewNotes)
    {
        if (StatusId != WorkflowStatusSeedIds.Submitted)
        {
            throw new InvalidOperationException("Only submitted supplier profiles can be returned.");
        }

        StatusId = WorkflowStatusSeedIds.Returned;
        ReviewDecisionNotes = NormalizeReviewNotes(reviewNotes, "return");
        IsOnHold = false;
        HoldReason = null;
        HeldAtUtc = null;
        HeldByUserId = null;
        Touch(actorUserId);
    }

    public void Reject(long actorUserId, string reviewNotes)
    {
        if (StatusId != WorkflowStatusSeedIds.Submitted)
        {
            throw new InvalidOperationException("Only submitted supplier profiles can be rejected.");
        }

        StatusId = WorkflowStatusSeedIds.Rejected;
        ReviewDecisionNotes = NormalizeReviewNotes(reviewNotes, "rejection");
        IsOnHold = false;
        HoldReason = null;
        HeldAtUtc = null;
        HeldByUserId = null;
        Touch(actorUserId);
    }

    public void Hold(long actorUserId, string holdReason)
    {
        if (StatusId != WorkflowStatusSeedIds.Submitted)
        {
            throw new InvalidOperationException("Only submitted supplier profiles can be placed on hold.");
        }

        IsOnHold = true;
        HoldReason = NormalizeReviewNotes(holdReason, "hold");
        HeldAtUtc = DateTime.UtcNow;
        HeldByUserId = actorUserId;
        Touch(actorUserId);
    }

    public void Reopen(long actorUserId, string reopenReason)
    {
        if (StatusId != WorkflowStatusSeedIds.Returned
            && StatusId != WorkflowStatusSeedIds.Rejected
            && !IsOnHold)
        {
            throw new InvalidOperationException("Only returned, rejected, or on-hold supplier profiles can be reopened.");
        }

        StatusId = WorkflowStatusSeedIds.Draft;
        IsOnHold = false;
        HoldReason = null;
        HeldAtUtc = null;
        HeldByUserId = null;
        ReopenedAtUtc = DateTime.UtcNow;
        ReopenedByUserId = actorUserId;
        ReopenReason = NormalizeReviewNotes(reopenReason, "reopen");
        Touch(actorUserId);
    }

    public void Activate(long actorUserId)
    {
        if (StatusId != WorkflowStatusSeedIds.Approved)
        {
            throw new InvalidOperationException("Only approved supplier profiles can be activated.");
        }

        if (!string.Equals(ComplianceStatusCode, ComplianceStatus.Approved, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Supplier activation requires approved compliance status.");
        }

        if (!string.Equals(BankVerificationStatusCode, BankVerificationStatus.Verified, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Supplier activation requires verified bank details.");
        }

        IsActive = true;
        ActivatedAtUtc = DateTime.UtcNow;
        ActivatedByUserId = actorUserId;
        Touch(actorUserId);
    }

    public void VerifyBank(long actorUserId, string verificationReference)
    {
        if (string.IsNullOrWhiteSpace(verificationReference))
        {
            throw new InvalidOperationException("Supplier bank verification reference is required.");
        }

        BankVerificationStatusCode = BankVerificationStatus.Verified;
        BankVerifiedAtUtc = DateTime.UtcNow;
        BankVerifiedByUserId = actorUserId;
        BankVerificationReference = verificationReference.Trim();
        Touch(actorUserId);
    }

    public void RecordPerformanceReview(
        long actorUserId,
        string riskRatingCode,
        decimal performanceScore,
        decimal onTimeDeliveryScore,
        decimal qualityScore,
        string? performanceNotes)
    {
        if (performanceScore is < 0m or > 100m)
        {
            throw new InvalidOperationException("Supplier performance score must be between 0 and 100.");
        }

        if (onTimeDeliveryScore is < 0m or > 100m)
        {
            throw new InvalidOperationException("Supplier on-time delivery score must be between 0 and 100.");
        }

        if (qualityScore is < 0m or > 100m)
        {
            throw new InvalidOperationException("Supplier quality score must be between 0 and 100.");
        }

        RiskRatingCode = NormalizeRiskRatingCode(riskRatingCode);
        PerformanceScore = performanceScore;
        OnTimeDeliveryScore = onTimeDeliveryScore;
        QualityScore = qualityScore;
        LastPerformanceReviewAtUtc = DateTime.UtcNow;
        LastPerformanceReviewedByUserId = actorUserId;
        PerformanceNotes = string.IsNullOrWhiteSpace(performanceNotes) ? null : performanceNotes.Trim();
        Touch(actorUserId);
    }

    private static string NormalizeRiskRatingCode(string riskRatingCode)
    {
        var normalized = riskRatingCode?.Trim().ToUpperInvariant();
        return normalized switch
        {
            RiskRating.Low => normalized,
            RiskRating.Medium => normalized,
            RiskRating.High => normalized,
            _ => throw new InvalidOperationException("Supplier risk rating must be LOW, MEDIUM, or HIGH.")
        };
    }

    private static string NormalizeReviewNotes(string reviewNotes, string actionName)
    {
        var normalized = string.IsNullOrWhiteSpace(reviewNotes) ? null : reviewNotes.Trim();
        if (string.IsNullOrWhiteSpace(normalized))
        {
            throw new InvalidOperationException($"Supplier profile {actionName} reason is required.");
        }

        return normalized;
    }

    private static class BankVerificationStatus
    {
        public const string Pending = "PENDING";
        public const string Verified = "VERIFIED";
    }

    private static class RiskRating
    {
        public const string Low = "LOW";
        public const string Medium = "MEDIUM";
        public const string High = "HIGH";
    }

    private static class ComplianceStatus
    {
        public const string Pending = "PENDING";
        public const string Approved = "APPROVED";
    }
}
