using CashLineERP.Contracts.IslamicFinance;
using CashLineERP.Contracts.Notifications;
using CashLineERP.Application.Notifications;
using CashLineERP.SharedKernel.IslamicFinance;

namespace CashLineERP.Application.IslamicFinance.Requests;

public sealed class IslamicFinanceRequestService : IIslamicFinanceRequestService
{
    private const string PartnerRoutingTemplateCode = "NTTMP-000006";
    private const string OfficerRoutingTemplateCode = "NTTMP-000007";
    private const string OfficerRecipientReference = "ROLE:ISLAMIC_FINANCE_OFFICER";
    private readonly IIslamicFinanceRequestRepository _repository;
    private readonly INotificationTemplateService? _notificationTemplateService;
    private readonly INotificationOrchestrationService? _notificationOrchestrationService;

    public IslamicFinanceRequestService(
        IIslamicFinanceRequestRepository repository,
        INotificationTemplateService? notificationTemplateService = null,
        INotificationOrchestrationService? notificationOrchestrationService = null)
    {
        _repository = repository;
        _notificationTemplateService = notificationTemplateService;
        _notificationOrchestrationService = notificationOrchestrationService;
    }

    public IReadOnlyCollection<IslamicFinanceFinancingRequestResponse> GetRequests(
        string? statusCode = null,
        long? customerPartyId = null,
        long? productProfileId = null) =>
        _repository.GetRequests(statusCode, customerPartyId, productProfileId).Select(Map).ToArray();

    public IslamicFinanceFinancingRequestResponse? GetRequest(long financingRequestId)
    {
        var entry = _repository.GetRequest(financingRequestId);
        return entry is null ? null : Map(entry);
    }

    public IReadOnlyCollection<IslamicFinanceRequestHistoryEntryResponse> GetRequestHistory(long financingRequestId) =>
        _repository.GetRequestHistory(financingRequestId).Select(Map).ToArray();

    public IslamicFinanceRequestAuditResponse? GetRequestAudit(long financingRequestId)
    {
        var entry = _repository.GetRequestAudit(financingRequestId);
        return entry is null ? null : new IslamicFinanceRequestAuditResponse(
            Map(entry.Request),
            entry.Decisions.Select(Map).ToArray(),
            entry.Events.Select(Map).ToArray());
    }

    public IReadOnlyCollection<IslamicFinanceDecisionResponse> GetDecisions(long financingRequestId) =>
        _repository.GetDecisions(financingRequestId).Select(Map).ToArray();

    public IslamicFinanceDecisionResponse? GetDecision(long financingDecisionId)
    {
        var entry = _repository.GetDecision(financingDecisionId);
        return entry is null ? null : Map(entry);
    }

    public IslamicFinanceDecisionAuditResponse? GetDecisionAudit(long financingDecisionId)
    {
        var entry = _repository.GetDecisionAudit(financingDecisionId);
        return entry is null ? null : new IslamicFinanceDecisionAuditResponse(
            Map(entry.Decision),
            entry.Events.Select(Map).ToArray());
    }

    public IslamicFinanceFinancingRequestResponse CreateRequest(CreateIslamicFinanceFinancingRequestRequest request) =>
        Map(_repository.Create(new CreateIslamicFinanceRequestCommand(
            request.RequestNumber,
            request.CustomerPartyId,
            request.ApplicantPartyId > 0 ? request.ApplicantPartyId : request.CustomerPartyId,
            string.IsNullOrWhiteSpace(request.ApplicantPartyTypeCode) ? "CUSTOMER" : request.ApplicantPartyTypeCode,
            request.ProductProfileId,
            request.FacilityId,
            IslamicFinanceProductCatalog.NormalizeFundingSourceMode(request.FundingSourceMode),
            request.SourceDocumentType,
            request.SourceDocumentId,
            request.TransactionCurrencyCode,
            request.RequestedAmount)));

    public IslamicFinanceFinancingRequestResponse SubmitRequest(long financingRequestId, long actorUserId) =>
        Map(_repository.Submit(financingRequestId, actorUserId));

    public IslamicFinanceFinancingRequestResponse ApproveRequest(long financingRequestId, long actorUserId) =>
        Map(_repository.Approve(financingRequestId, actorUserId));

    public IslamicFinanceFinancingRequestResponse ReturnRequest(long financingRequestId, long actorUserId) =>
        Map(_repository.Return(financingRequestId, actorUserId));

    public IslamicFinanceFinancingRequestResponse RejectRequest(long financingRequestId, long actorUserId) =>
        Map(_repository.Reject(financingRequestId, actorUserId));

    public IslamicFinanceFinancingRequestResponse AssignPartnerLender(
        long financingRequestId,
        AssignIslamicFinancePartnerLenderRequest request,
        long actorUserId) =>
        Map(_repository.AssignPartnerLender(financingRequestId, request.PartnerLenderId, actorUserId));

    public IslamicFinancePartnerOrchestrationResponse RoutePartnerLenders(long financingRequestId, long actorUserId)
    {
        var request = GetRequest(financingRequestId)
            ?? throw new InvalidOperationException("Islamic Finance financing request was not found.");
        var orchestration = _repository.GetPartnerOrchestration(financingRequestId)
            ?? throw new InvalidOperationException("Islamic Finance partner orchestration was not found for the selected request.");

        if (!string.Equals(request.StatusCode, IslamicFinanceWorkflowCatalog.Approved, StringComparison.OrdinalIgnoreCase)
            && !string.Equals(request.StatusCode, IslamicFinanceWorkflowCatalog.Assigned, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Islamic Finance partner routing can only be started after the financing request is approved.");
        }

        var compatiblePartners = orchestration.CandidatePartners
            .Where(item => item.SupportsProductType && item.SupportsFundingMode)
            .OrderByDescending(item => string.Equals(item.DefaultCurrencyCode, request.TransactionCurrencyCode, StringComparison.OrdinalIgnoreCase))
            .ThenByDescending(item => item.IsAssignedPartner)
            .ThenBy(item => item.PartnerLenderName, StringComparer.OrdinalIgnoreCase)
            .ToArray();

        if (compatiblePartners.Length == 0)
        {
            throw new InvalidOperationException("Islamic Finance partner routing cannot continue because no compatible partner lenders are currently active for this financing request.");
        }

        var primaryPartner = compatiblePartners[0];
        if (request.AssignedPartnerLenderId != primaryPartner.PartnerLenderId)
        {
            _repository.AssignPartnerLender(financingRequestId, primaryPartner.PartnerLenderId, actorUserId);
        }

        EnqueuePartnerRoutingNotifications(actorUserId, request, primaryPartner, compatiblePartners);

        return GetPartnerOrchestration(financingRequestId)
            ?? throw new InvalidOperationException("Islamic Finance partner orchestration could not be rebuilt after partner routing.");
    }

    public IslamicFinanceFinancingRequestResponse SelectOffer(long financingRequestId, long financingDecisionId, long actorUserId) =>
        Map(_repository.SelectOffer(financingRequestId, financingDecisionId, actorUserId));

    public IslamicFinanceOfficerWorkbenchResponse GetOfficerWorkbench()
    {
        var items = _repository.GetOfficerWorkbenchItems();
        return new IslamicFinanceOfficerWorkbenchResponse(
            items.Count,
            items.Count(item => string.Equals(item.StatusCode, IslamicFinanceWorkflowCatalog.Submitted, StringComparison.OrdinalIgnoreCase)),
            items.Count(item => string.Equals(item.StatusCode, IslamicFinanceWorkflowCatalog.Returned, StringComparison.OrdinalIgnoreCase)),
            items.Count(item => string.Equals(item.StatusCode, IslamicFinanceWorkflowCatalog.Approved, StringComparison.OrdinalIgnoreCase)),
            items.Count(item => string.Equals(item.PendingActionCode, "AWAIT_EXTERNAL_OFFER_SELECTION", StringComparison.OrdinalIgnoreCase)),
            items.Count(item => string.Equals(item.StatusCode, IslamicFinanceWorkflowCatalog.Assigned, StringComparison.OrdinalIgnoreCase)),
            items.Count(item => string.Equals(item.SlaStatusCode, "AT_RISK", StringComparison.OrdinalIgnoreCase)),
            items.Count(item => string.Equals(item.SlaStatusCode, "BREACHED", StringComparison.OrdinalIgnoreCase)),
            items.Select(item => new IslamicFinanceOfficerWorkbenchItemResponse(
                item.FinancingRequestId,
                item.RequestNumber,
                item.ApplicantPartyId,
                item.ApplicantPartyTypeCode,
                item.ApplicantPartyName,
                item.CustomerPartyId,
                item.CustomerPartyName,
                item.ProductProfileId,
                item.ProductCode,
                item.ProductName,
                item.FundingSourceMode,
                item.StatusCode,
                item.RequestedAmount,
                item.TransactionCurrencyCode,
                item.AvailableOfferCount,
                item.SelectedFinancingDecisionId,
                item.SelectedPartnerLenderId,
                item.SelectedPartnerLenderName,
                item.SelectedOfferProductTypeCode,
                item.SelectedOfferFundingSourceMode,
                item.SelectedOfferAmount,
                item.SelectedOfferTenorMonths,
                item.PendingActionCode,
                item.OpenAgeDays,
                item.AttentionStatusCode,
                item.SlaStatusCode,
                item.SlaDueAtUtc,
                item.CreatedAtUtc,
                item.SubmittedAtUtc,
                item.ApprovedAtUtc)).ToArray());
    }

    public IslamicFinanceExecutionWorkbenchResponse GetExecutionWorkbench()
    {
        var items = _repository.GetExecutionWorkbenchItems();
        return new IslamicFinanceExecutionWorkbenchResponse(
            items.Count,
            items.Count(item => string.Equals(item.ExecutionStageCode, "OFFER_SELECTED", StringComparison.OrdinalIgnoreCase)),
            items.Count(item => string.Equals(item.ExecutionStageCode, "MURABAHA_CASE", StringComparison.OrdinalIgnoreCase)
                || string.Equals(item.ExecutionStageCode, "RECEIVABLES_CASE", StringComparison.OrdinalIgnoreCase)
                || string.Equals(item.ExecutionStageCode, "DISBURSEMENT", StringComparison.OrdinalIgnoreCase)),
            items.Count(item => string.Equals(item.ExecutionStageCode, "SETTLEMENT", StringComparison.OrdinalIgnoreCase)),
            items.Count(item => string.Equals(item.ExecutionStageCode, "COMPLETED", StringComparison.OrdinalIgnoreCase)),
            items.Select(item => new IslamicFinanceExecutionWorkbenchItemResponse(
                item.FinancingRequestId,
                item.RequestNumber,
                item.ApplicantPartyId,
                item.ApplicantPartyTypeCode,
                item.ApplicantPartyName,
                item.ProductProfileId,
                item.ProductTypeCode,
                item.ProductName,
                item.FundingSourceMode,
                item.StatusCode,
                item.SelectedFinancingDecisionId,
                item.SelectedPartnerLenderId,
                item.SelectedPartnerLenderName,
                item.SelectedOfferAmount,
                item.SelectedOfferTenorMonths,
                item.ExecutionStageCode,
                item.PendingExecutionActionCode,
                item.ExternalNotificationCount,
                item.PartnerNotificationCount,
                item.MurabahaCaseId,
                item.ReceivablesCaseId,
                item.DisbursementId,
                item.SettlementId,
                item.SelectedOfferAtUtc,
                item.CreatedAtUtc)).ToArray());
    }

    public IslamicFinancePartnerOrchestrationResponse? GetPartnerOrchestration(long financingRequestId)
    {
        var item = _repository.GetPartnerOrchestration(financingRequestId);
        return item is null
            ? null
            : new IslamicFinancePartnerOrchestrationResponse(
                item.FinancingRequestId,
                item.RequestNumber,
                item.ApplicantPartyTypeCode,
                item.ProductTypeCode,
                item.FundingSourceMode,
                item.StatusCode,
                item.OrchestrationStageCode,
                item.NextInternalActionCode,
                item.AssignedPartnerLenderId,
                item.AssignedPartnerLenderName,
                item.CompatiblePartnerCount,
                item.ApprovedOfferCount,
                item.OfficerNotificationCount,
                item.SelectedFinancingDecisionId,
                item.CandidatePartners.Select(entry => new IslamicFinancePartnerOrchestrationPartnerResponse(
                    entry.PartnerLenderId,
                    entry.PartnerLenderName,
                    entry.DefaultCurrencyCode,
                    entry.SupportsProductType,
                    entry.SupportsFundingMode,
                    entry.IsAssignedPartner,
                    entry.HasApprovedOffer,
                    entry.IsSelectedOffer)).ToArray());
    }

    public IslamicFinanceDecisionResponse CreateDecision(long financingRequestId, CreateIslamicFinanceDecisionRequest request) =>
        Map(_repository.CreateDecision(new CreateIslamicFinanceDecisionCommand(
            financingRequestId,
            request.DecisionCode,
            request.DecisionNotes,
            request.DecidingPartyId,
            request.PartnerLenderId,
            request.OfferProductTypeCode,
            request.OfferFundingSourceMode,
            request.OfferCurrencyCode,
            request.OfferedAmount,
            request.TenorMonths,
            request.ProfitRatePercent,
            request.ProcessingFeeAmount,
            request.SecurityRequirementText,
            request.OfferRank)));

    public IslamicFinanceDecisionResponse SubmitDecision(long financingDecisionId, long actorUserId) =>
        Map(_repository.SubmitDecision(financingDecisionId, actorUserId));

    public IslamicFinanceDecisionResponse ApproveDecision(long financingDecisionId, long actorUserId) =>
        Map(_repository.ApproveDecision(financingDecisionId, actorUserId));

    public IslamicFinanceDecisionResponse ReturnDecision(long financingDecisionId, long actorUserId) =>
        Map(_repository.ReturnDecision(financingDecisionId, actorUserId));

    public IslamicFinanceDecisionResponse RejectDecision(long financingDecisionId, long actorUserId) =>
        Map(_repository.RejectDecision(financingDecisionId, actorUserId));

    public IslamicFinanceFinancingRequestResponse AttachShariahReview(
        long financingRequestId,
        AttachIslamicFinanceShariahReviewRequest request,
        long actorUserId) =>
        Map(_repository.AttachShariahReview(financingRequestId, request.ShariahReviewCaseId, actorUserId));

    private void EnqueuePartnerRoutingNotifications(
        long actorUserId,
        IslamicFinanceFinancingRequestResponse request,
        IslamicFinancePartnerOrchestrationPartnerView primaryPartner,
        IReadOnlyCollection<IslamicFinancePartnerOrchestrationPartnerView> compatiblePartners)
    {
        if (_notificationTemplateService is null || _notificationOrchestrationService is null)
        {
            return;
        }

        var workbenchEntry = _repository.GetOfficerWorkbenchItems()
            .SingleOrDefault(item => item.FinancingRequestId == request.Id);

        var applicantName = workbenchEntry?.ApplicantPartyName ?? $"Party {request.ApplicantPartyId}";
        var productName = workbenchEntry?.ProductName ?? $"Product {request.ProductProfileId}";
        var variables = new Dictionary<string, string?>
        {
            ["financingRequestId"] = request.Id.ToString(),
            ["requestNumber"] = request.RequestNumber,
            ["applicantName"] = applicantName,
            ["applicantType"] = request.ApplicantPartyTypeCode,
            ["productName"] = productName,
            ["fundingMode"] = request.FundingSourceMode,
            ["requestedAmount"] = request.RequestedAmount.ToString("0.00"),
            ["currencyCode"] = request.TransactionCurrencyCode,
            ["primaryPartnerName"] = primaryPartner.PartnerLenderName,
            ["compatiblePartnerCount"] = compatiblePartners.Count.ToString()
        };

        foreach (var partner in compatiblePartners)
        {
            EnqueueNotificationIfMissing(
                actorUserId,
                PartnerRoutingTemplateCode,
                $"PARTNER_LENDER:{partner.PartnerLenderId}",
                request.Id,
                variables);
        }

        EnqueueNotificationIfMissing(
            actorUserId,
            OfficerRoutingTemplateCode,
            OfficerRecipientReference,
            request.Id,
            variables);
    }

    private void EnqueueNotificationIfMissing(
        long actorUserId,
        string templateCode,
        string recipientReference,
        long financingRequestId,
        Dictionary<string, string?> variables)
    {
        if (_notificationTemplateService is null || _notificationOrchestrationService is null)
        {
            return;
        }

        var existing = _notificationOrchestrationService.GetQueueItems()
            .Any(item => string.Equals(item.TemplateCode, templateCode, StringComparison.OrdinalIgnoreCase)
                && string.Equals(item.RecipientReference, recipientReference, StringComparison.OrdinalIgnoreCase)
                && string.Equals(item.RelatedEntityType, "FINANCING_REQUEST", StringComparison.OrdinalIgnoreCase)
                && item.RelatedEntityId == financingRequestId
                && (string.Equals(item.StatusCode, "PENDING", StringComparison.OrdinalIgnoreCase)
                    || string.Equals(item.StatusCode, "RETRY", StringComparison.OrdinalIgnoreCase)));

        if (existing)
        {
            return;
        }

        var template = _notificationTemplateService.GetTemplates(activeOnly: true)
            .SingleOrDefault(item => string.Equals(item.TemplateCode, templateCode, StringComparison.OrdinalIgnoreCase));
        if (template is null)
        {
            return;
        }

        _notificationOrchestrationService.EnqueueNotification(actorUserId, new EnqueueNotificationRequest(
            template.NotificationTemplateId,
            recipientReference,
            "ISLAMIC_FINANCE",
            "FINANCING_REQUEST",
            financingRequestId,
            variables));
    }

    private static IslamicFinanceFinancingRequestResponse Map(IslamicFinanceRequestDetailView entry) =>
        new()
        {
            Id = entry.FinancingRequestId,
            RequestNumber = entry.RequestNumber,
            CustomerPartyId = entry.CustomerPartyId,
            ApplicantPartyId = entry.ApplicantPartyId,
            ApplicantPartyTypeCode = entry.ApplicantPartyTypeCode,
            ProductProfileId = entry.ProductProfileId,
            FacilityId = entry.FacilityId,
            FundingSourceMode = entry.FundingSourceMode,
            SourceDocumentType = entry.SourceDocumentType,
            SourceDocumentId = entry.SourceDocumentId,
            TransactionCurrencyCode = entry.TransactionCurrencyCode,
            RequestedAmount = entry.RequestedAmount,
            StatusCode = entry.StatusCode,
            WorkflowInstanceId = entry.WorkflowInstanceId,
            ShariahReviewCaseId = entry.ShariahReviewCaseId,
            AssignedPartnerLenderId = entry.AssignedPartnerLenderId,
            SelectedFinancingDecisionId = entry.SelectedFinancingDecisionId,
            SubmittedAtUtc = entry.SubmittedAtUtc,
            SubmittedByUserId = entry.SubmittedByUserId,
            ApprovedAtUtc = entry.ApprovedAtUtc,
            ApprovedByUserId = entry.ApprovedByUserId,
            SelectedOfferAtUtc = entry.SelectedOfferAtUtc,
            SelectedOfferByUserId = entry.SelectedOfferByUserId
        };

    private static IslamicFinanceDecisionResponse Map(IslamicFinanceDecisionDetailView entry) =>
        new()
        {
            Id = entry.FinancingDecisionId,
            FinancingRequestId = entry.FinancingRequestId,
            DecisionCode = entry.DecisionCode,
            DecisionNotes = entry.DecisionNotes,
            DecidingPartyId = entry.DecidingPartyId,
            PartnerLenderId = entry.PartnerLenderId,
            OfferProductTypeCode = entry.OfferProductTypeCode,
            OfferFundingSourceMode = entry.OfferFundingSourceMode,
            OfferCurrencyCode = entry.OfferCurrencyCode,
            OfferedAmount = entry.OfferedAmount,
            TenorMonths = entry.TenorMonths,
            ProfitRatePercent = entry.ProfitRatePercent,
            ProcessingFeeAmount = entry.ProcessingFeeAmount,
            SecurityRequirementText = entry.SecurityRequirementText,
            OfferRank = entry.OfferRank,
            StatusCode = entry.StatusCode,
            CreatedAtUtc = entry.CreatedAtUtc,
            CreatedByUserId = entry.CreatedByUserId,
            SubmittedAtUtc = entry.SubmittedAtUtc,
            SubmittedByUserId = entry.SubmittedByUserId,
            ApprovedAtUtc = entry.ApprovedAtUtc,
            ApprovedByUserId = entry.ApprovedByUserId,
            ReturnedAtUtc = entry.ReturnedAtUtc,
            ReturnedByUserId = entry.ReturnedByUserId,
            RejectedAtUtc = entry.RejectedAtUtc,
            RejectedByUserId = entry.RejectedByUserId
        };

    private static IslamicFinanceRequestHistoryEntryResponse Map(IslamicFinanceRequestHistoryEntryView entry) =>
        new(
            entry.FinancingRequestId,
            entry.EventCode,
            entry.SourceType,
            entry.SourceId,
            entry.ActorUserId,
            entry.Notes,
            entry.OccurredAtUtc);
}
