using CashLineERP.Domain.Common;
using CashLineERP.SharedKernel.IslamicFinance;
using System.ComponentModel.DataAnnotations.Schema;

namespace CashLineERP.Domain.IslamicFinance.Requests;

public sealed class IslamicFinanceDecision : Entity
{
    public long Id { get; private set; }
    public long FinancingRequestId { get; private set; }
    public string DecisionCode { get; private set; } = IslamicFinanceDecisionCatalog.ToCode(IslamicFinanceDecisionCode.Pending);
    public string? DecisionNotes { get; private set; }
    public long? DecidingPartyId { get; private set; }
    public long? PartnerLenderId { get; private set; }
    public string OfferProductTypeCode { get; private set; } = string.Empty;
    public string OfferFundingSourceMode { get; private set; } = string.Empty;
    public string OfferCurrencyCode { get; private set; } = string.Empty;
    public decimal? OfferedAmount { get; private set; }
    public int? TenorMonths { get; private set; }
    public decimal? ProfitRatePercent { get; private set; }
    public decimal? ProcessingFeeAmount { get; private set; }
    public string? SecurityRequirementText { get; private set; }
    public int? OfferRank { get; private set; }
    public string StatusCode { get; private set; } = IslamicFinanceWorkflowCatalog.Draft;
    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? ReturnedAtUtc { get; private set; }
    public long? ReturnedByUserId { get; private set; }
    public DateTime? RejectedAtUtc { get; private set; }
    public long? RejectedByUserId { get; private set; }
    [NotMapped]
    public IslamicFinanceDecisionCode Decision => IslamicFinanceDecisionCatalog.ParseDecisionCode(DecisionCode);
    [NotMapped]
    public IslamicFinanceLifecycleState LifecycleState => IslamicFinanceWorkflowCatalog.ParseStatusCode(StatusCode);

    public IslamicFinanceDecision(
        long financingRequestId,
        IslamicFinanceDecisionCode decisionCode,
        string? decisionNotes,
        long? decidingPartyId,
        long? partnerLenderId,
        string offerProductTypeCode,
        string offerFundingSourceMode,
        string offerCurrencyCode,
        decimal? offeredAmount,
        int? tenorMonths,
        decimal? profitRatePercent,
        decimal? processingFeeAmount,
        string? securityRequirementText,
        int? offerRank)
    {
        if (financingRequestId <= 0)
        {
            throw new InvalidOperationException("Islamic Finance decision requires a valid financing request identifier.");
        }

        if (decidingPartyId.HasValue && decidingPartyId.Value <= 0)
        {
            throw new InvalidOperationException("Islamic Finance decision deciding party identifier must be a valid positive identifier when provided.");
        }

        if (partnerLenderId.HasValue && partnerLenderId.Value <= 0)
        {
            throw new InvalidOperationException("Islamic Finance decision partner-lender identifier must be a valid positive identifier when provided.");
        }

        FinancingRequestId = financingRequestId;
        DecisionCode = IslamicFinanceDecisionCatalog.ToCode(decisionCode);
        DecisionNotes = string.IsNullOrWhiteSpace(decisionNotes) ? null : decisionNotes.Trim();
        DecidingPartyId = decidingPartyId;
        PartnerLenderId = partnerLenderId;
        OfferProductTypeCode = IslamicFinanceProductCatalog.NormalizeProductType(offerProductTypeCode);
        OfferFundingSourceMode = IslamicFinanceProductCatalog.NormalizeFundingSourceMode(offerFundingSourceMode);
        OfferCurrencyCode = string.IsNullOrWhiteSpace(offerCurrencyCode) ? string.Empty : offerCurrencyCode.Trim().ToUpperInvariant();
        OfferedAmount = offeredAmount;
        TenorMonths = tenorMonths;
        ProfitRatePercent = profitRatePercent;
        ProcessingFeeAmount = processingFeeAmount;
        SecurityRequirementText = string.IsNullOrWhiteSpace(securityRequirementText) ? null : securityRequirementText.Trim();
        OfferRank = offerRank;
    }

    private IslamicFinanceDecision()
    {
    }

    public void Submit(long actorUserId)
    {
        if (!string.Equals(StatusCode, IslamicFinanceWorkflowCatalog.Draft, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(StatusCode, IslamicFinanceWorkflowCatalog.Returned, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Only draft or returned Islamic Finance decisions can be submitted.");
        }

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

    public void Approve(long actorUserId)
    {
        if (!string.Equals(StatusCode, IslamicFinanceWorkflowCatalog.Submitted, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Only submitted Islamic Finance decisions can be approved.");
        }

        StatusCode = IslamicFinanceWorkflowCatalog.Approved;
        ApprovedAtUtc = DateTime.UtcNow;
        ApprovedByUserId = actorUserId;
        Touch(actorUserId);
    }

    public void Return(long actorUserId)
    {
        if (!string.Equals(StatusCode, IslamicFinanceWorkflowCatalog.Submitted, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Only submitted Islamic Finance decisions can be returned.");
        }

        StatusCode = IslamicFinanceWorkflowCatalog.Returned;
        ReturnedAtUtc = DateTime.UtcNow;
        ReturnedByUserId = actorUserId;
        Touch(actorUserId);
    }

    public void Reject(long actorUserId)
    {
        if (!string.Equals(StatusCode, IslamicFinanceWorkflowCatalog.Submitted, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Only submitted Islamic Finance decisions can be rejected.");
        }

        StatusCode = IslamicFinanceWorkflowCatalog.Rejected;
        RejectedAtUtc = DateTime.UtcNow;
        RejectedByUserId = actorUserId;
        Touch(actorUserId);
    }
}
