using System.Linq;
using CashLineERP.Application.IslamicFinance.ContractTemplates;
using CashLineERP.Application.IslamicFinance.Disbursements;
using CashLineERP.Application.IslamicFinance.Facilities;
using CashLineERP.Application.IslamicFinance.Murabaha;
using CashLineERP.Application.IslamicFinance.PartnerLenders;
using CashLineERP.Application.IslamicFinance.ProductProfiles;
using CashLineERP.Application.IslamicFinance.Requests;
using CashLineERP.Application.IslamicFinance.Settlements;
using CashLineERP.Application.IslamicFinance.Shariah;
using CashLineERP.Contracts.IslamicFinance;
using CashLineERP.Domain.IslamicFinance.Murabaha;
using CashLineERP.Infrastructure.Persistence;
using CashLineERP.Infrastructure.Persistence.Repositories;
using CashLineERP.Infrastructure.Persistence.Repositories.IslamicFinance;

namespace CashLineERP.Api.Tests;

public sealed class IslamicFinanceSettlementServiceQaTests
{
    [Fact]
    public void Islamic_Finance_Settlement_Service_Supports_Create_Submit_Approve_And_Close_Linkage()
    {
        using var dbContext = QaDbContextFactory.CreateContext();

        var settlementService = new IslamicFinanceSettlementService(new IslamicFinanceSettlementRepository(dbContext));
        var disbursementService = new IslamicFinanceMurabahaDisbursementService(new IslamicFinanceMurabahaDisbursementRepository(dbContext));

        var prepared = SeedExecutedDisbursementReadyForSettlement(dbContext, "IFSET-SVC");

        var created = settlementService.CreateSettlement(new CreateIslamicFinanceSettlementRequest
        {
            SettlementNumber = "IFS-SVC-0001",
            MurabahaCaseId = prepared.MurabahaCaseId,
            SettlementDate = new DateOnly(2026, 4, 10),
            SettlementCurrencyCode = "EGP",
            Lines =
            [
                new CreateIslamicFinanceSettlementLineRequest
                {
                    MurabahaDisbursementId = prepared.DisbursementId,
                    SettledAmount = prepared.ExecutedAmount
                }
            ]
        });

        Assert.Equal("DRAFT", created.StatusCode);
        Assert.Single(created.Lines);
        Assert.Equal(prepared.DisbursementId, created.Lines.Single().MurabahaDisbursementId);

        settlementService.SubmitSettlement(created.Id, 20);
        settlementService.ApproveSettlement(created.Id, 21);
        var closed = settlementService.CloseSettlement(created.Id, 22);

        Assert.Equal("CLOSED", closed.StatusCode);
        Assert.Equal("SETTLED", closed.Lines.Single().StatusCode);
        Assert.NotNull(closed.ClosedAtUtc);

        var disbursement = disbursementService.GetDisbursement(prepared.DisbursementId);
        Assert.NotNull(disbursement);
        Assert.Equal("CLOSED", disbursement!.StatusCode);
    }

    [Fact]
    public void Islamic_Finance_Settlement_Service_Rejects_Duplicate_Settlement_Linkage_For_The_Same_Disbursement()
    {
        using var dbContext = QaDbContextFactory.CreateContext();

        var settlementService = new IslamicFinanceSettlementService(new IslamicFinanceSettlementRepository(dbContext));
        var prepared = SeedExecutedDisbursementReadyForSettlement(dbContext, "IFSET-DUP");

        settlementService.CreateSettlement(new CreateIslamicFinanceSettlementRequest
        {
            SettlementNumber = "IFS-DUP-0001",
            MurabahaCaseId = prepared.MurabahaCaseId,
            SettlementDate = new DateOnly(2026, 4, 10),
            SettlementCurrencyCode = "EGP",
            Lines =
            [
                new CreateIslamicFinanceSettlementLineRequest
                {
                    MurabahaDisbursementId = prepared.DisbursementId,
                    SettledAmount = prepared.ExecutedAmount
                }
            ]
        });

        var exception = Assert.Throws<InvalidOperationException>(() => settlementService.CreateSettlement(new CreateIslamicFinanceSettlementRequest
        {
            SettlementNumber = "IFS-DUP-0002",
            MurabahaCaseId = prepared.MurabahaCaseId,
            SettlementDate = new DateOnly(2026, 4, 11),
            SettlementCurrencyCode = "EGP",
            Lines =
            [
                new CreateIslamicFinanceSettlementLineRequest
                {
                    MurabahaDisbursementId = prepared.DisbursementId,
                    SettledAmount = prepared.ExecutedAmount
                }
            ]
        }));

        Assert.Contains("already linked", exception.Message, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void Islamic_Finance_Settlement_Service_Returns_History_And_Audit_Timeline()
    {
        using var dbContext = QaDbContextFactory.CreateContext();

        var settlementService = new IslamicFinanceSettlementService(new IslamicFinanceSettlementRepository(dbContext));
        var prepared = SeedExecutedDisbursementReadyForSettlement(dbContext, "IFSET-HIST");

        var created = settlementService.CreateSettlement(new CreateIslamicFinanceSettlementRequest
        {
            SettlementNumber = "IFS-HIST-0001",
            MurabahaCaseId = prepared.MurabahaCaseId,
            SettlementDate = new DateOnly(2026, 4, 10),
            SettlementCurrencyCode = "EGP",
            Lines =
            [
                new CreateIslamicFinanceSettlementLineRequest
                {
                    MurabahaDisbursementId = prepared.DisbursementId,
                    SettledAmount = prepared.ExecutedAmount
                }
            ]
        });

        settlementService.SubmitSettlement(created.Id, 20);
        settlementService.ApproveSettlement(created.Id, 21);
        settlementService.CloseSettlement(created.Id, 22);

        var history = settlementService.GetSettlementHistory(created.Id);
        Assert.Contains(history, entry => entry.EventCode == "SETTLEMENT_CREATED");
        Assert.Contains(history, entry => entry.EventCode == "SETTLEMENT_LINE_LINKED");
        Assert.Contains(history, entry => entry.EventCode == "SETTLEMENT_CLOSED");
        Assert.True(history.SequenceEqual(history.OrderBy(entry => entry.OccurredAtUtc)));

        var audit = settlementService.GetSettlementAudit(created.Id);
        Assert.NotNull(audit);
        Assert.Equal(created.Id, audit!.Settlement.Id);
        Assert.Contains(audit.Events, entry => entry.EventCode == "SETTLEMENT_CLOSED");
    }

    private static (long MurabahaCaseId, long DisbursementId, decimal ExecutedAmount) SeedExecutedDisbursementReadyForSettlement(
        CashLineErpDbContext dbContext,
        string prefix)
    {
        var partnerService = new IslamicFinancePartnerService(new IslamicFinancePartnerRepository(dbContext));
        var productService = new IslamicFinanceProductService(new IslamicFinanceProductRepository(dbContext));
        var facilityService = new IslamicFinanceFacilityService(new IslamicFinanceFacilityRepository(dbContext));
        var templateService = new IslamicFinanceContractTemplateService(new IslamicFinanceContractTemplateRepository(dbContext));
        var requestRepository = new IslamicFinanceRequestRepository(dbContext);
        var requestService = new IslamicFinanceRequestService(requestRepository);
        var shariahReviewService = new ShariahReviewService(new ShariahReviewRepository(dbContext));
        var murabahaCaseService = new MurabahaCaseService(new MurabahaCaseRepository(dbContext));
        var murabahaContractService = new IslamicFinanceMurabahaContractService(
            new IslamicFinanceMurabahaContractRepository(dbContext, new DocumentStorageRepository(dbContext)));
        var disbursementService = new IslamicFinanceMurabahaDisbursementService(new IslamicFinanceMurabahaDisbursementRepository(dbContext));

        var partner = CreateActivePartner(partnerService);
        var product = CreateActiveProduct(productService, prefix);
        var facility = CreateActiveFacility(facilityService, partner.Id, product.Id, prefix);
        var template = CreateActiveTemplate(templateService, product.Id, prefix);
        var request = CreateApprovedAssignedRequest(requestService, requestRepository, shariahReviewService, partner.Id, product.Id, facility.Id, prefix);

        var murabahaCase = murabahaCaseService.CreateCase(new CreateIslamicFinanceMurabahaCaseRequest
        {
            CaseNumber = $"{prefix}-{Guid.NewGuid():N}"[..12].ToUpperInvariant(),
            FinancingRequestId = request.Id,
            CustomerPartyId = 1,
            PartnerLenderId = partner.Id,
            SupplierPartyId = 2,
            ProductProfileId = product.Id,
            ContractTemplateId = template.Id,
            TransactionCurrencyCode = "EGP",
            PurchaseCostAmount = 100000m,
            ProfitAmount = 20000m
        });

        murabahaCaseService.SubmitCase(murabahaCase.Id, 3);
        murabahaCaseService.ApproveCase(murabahaCase.Id, 4);
        murabahaCaseService.ActivateCase(murabahaCase.Id, 4);
        murabahaContractService.GenerateContract(murabahaCase.Id, 4, new GenerateIslamicFinanceMurabahaContractRequest());

        dbContext.IslamicFinanceProfitSchedules.AddRange(
            new IslamicFinanceProfitSchedule(murabahaCase.Id, 1, new DateOnly(2026, 5, 1), 50000m, 10000m, 60000m),
            new IslamicFinanceProfitSchedule(murabahaCase.Id, 2, new DateOnly(2026, 6, 1), 50000m, 10000m, 0m));
        dbContext.SaveChanges();

        var disbursement = disbursementService.CreateDisbursement(new CreateIslamicFinanceMurabahaDisbursementRequest
        {
            MurabahaCaseId = murabahaCase.Id,
            DisbursementNumber = $"{prefix}-DISB",
            TargetType = "SUPPLIER_PAYMENT",
            InstructedAmount = 60000m,
            PlannedExecutionDate = new DateOnly(2026, 4, 7)
        });

        disbursementService.SubmitDisbursement(disbursement.Id, 10);
        disbursementService.ApproveDisbursement(disbursement.Id, 11);
        var executed = disbursementService.ExecuteDisbursement(disbursement.Id, new ExecuteIslamicFinanceMurabahaDisbursementRequest
        {
            ExecutionReferenceNumber = $"{prefix}-PAY"
        }, 12);

        return (murabahaCase.Id, executed.Id, executed.ExecutedAmount);
    }

    private static IslamicFinancePartnerLenderResponse CreateActivePartner(IslamicFinancePartnerService partnerService)
    {
        var created = partnerService.CreatePartnerLender(new CreateIslamicFinancePartnerLenderRequest
        {
            PartnerLenderPartyId = 1,
            DefaultCurrencyCode = "EGP",
            SupportsMurabaha = true,
            SupportsReceivablesFinance = false,
            SupportsTawarruq = false
        });

        partnerService.SubmitPartnerLender(created.Id, 2);
        partnerService.ApprovePartnerLender(created.Id, 3);
        return partnerService.ActivatePartnerLender(created.Id, 3);
    }

    private static IslamicFinanceProductProfileResponse CreateActiveProduct(IslamicFinanceProductService productService, string prefix)
    {
        var created = productService.CreateProductProfile(new CreateIslamicFinanceProductProfileRequest
        {
            ProductType = "MURABAHA",
            ProductCode = $"{prefix}-PROD",
            ProductName = $"{prefix} Product",
            RequiresShariahReview = true,
            AllowsPartnerFunding = true,
            AllowsCashLineFunding = false
        });

        productService.SubmitProductProfile(created.Id, 2);
        productService.ApproveProductProfile(created.Id, 3);
        return productService.ActivateProductProfile(created.Id, 3);
    }

    private static IslamicFinanceFacilityResponse CreateActiveFacility(IslamicFinanceFacilityService facilityService, long partnerId, long productId, string prefix)
    {
        var created = facilityService.CreateFacility(new CreateIslamicFinanceFacilityRequest
        {
            PartnerLenderId = partnerId,
            CustomerPartyId = 1,
            ProductProfileId = productId,
            FacilityNumber = $"{prefix}-FAC",
            CurrencyCode = "EGP",
            ApprovedLimitAmount = 250000m,
            UtilizedAmount = 0m
        });

        facilityService.SubmitFacility(created.Id, 2);
        facilityService.ApproveFacility(created.Id, 3);
        return facilityService.ActivateFacility(created.Id, 3);
    }

    private static IslamicFinanceContractTemplateResponse CreateActiveTemplate(IslamicFinanceContractTemplateService templateService, long productId, string prefix)
    {
        var created = templateService.CreateContractTemplate(new CreateIslamicFinanceContractTemplateRequest
        {
            ProductProfileId = productId,
            TemplateCode = $"{prefix}-TPL",
            TemplateName = $"{prefix} Template",
            LanguageCode = "EN"
        });

        templateService.SubmitContractTemplate(created.Id, 2);
        templateService.ApproveContractTemplate(created.Id, 3);
        return templateService.ActivateContractTemplate(created.Id, 3);
    }

    private static IslamicFinanceFinancingRequestResponse CreateApprovedAssignedRequest(
        IslamicFinanceRequestService requestService,
        IslamicFinanceRequestRepository requestRepository,
        ShariahReviewService shariahReviewService,
        long partnerId,
        long productId,
        long facilityId,
        string prefix)
    {
        var request = requestService.CreateRequest(new CreateIslamicFinanceFinancingRequestRequest
        {
            RequestNumber = $"{prefix}-REQ",
            CustomerPartyId = 1,
            ProductProfileId = productId,
            FacilityId = facilityId,
            FundingSourceMode = "PARTNER",
            SourceDocumentType = "SALES_ORDER",
            SourceDocumentId = 1001,
            TransactionCurrencyCode = "EGP",
            RequestedAmount = 100000m
        });

        requestService.SubmitRequest(request.Id, 2);
        requestService.ApproveRequest(request.Id, 3);
        requestService.AssignPartnerLender(request.Id, new AssignIslamicFinancePartnerLenderRequest
        {
            PartnerLenderId = partnerId
        }, 4);

        var review = shariahReviewService.CreateReviewCase(new CreateIslamicFinanceShariahReviewRequest
        {
            ReviewCaseNumber = $"{prefix}-SHR",
            ProductProfileId = productId,
            FinancingRequestId = request.Id,
            ReviewReasonCode = "SHARIAH_REVIEW"
        });

        shariahReviewService.SubmitReviewCase(review.Id, 5);
        shariahReviewService.RecommendOutcome(review.Id, new RecommendIslamicFinanceShariahOutcomeRequest
        {
            ReviewOutcomeCode = "APPROVED",
            ReviewNotes = "Approved for settlement QA."
        }, 6);
        shariahReviewService.ApproveOutcome(review.Id, 7);

        requestService.AttachShariahReview(request.Id, new AttachIslamicFinanceShariahReviewRequest
        {
            ShariahReviewCaseId = review.Id
        }, 7);

        var decision = requestService.CreateDecision(request.Id, new CreateIslamicFinanceDecisionRequest
        {
            DecisionCode = "APPROVED",
            PartnerLenderId = partnerId
        });

        requestService.SubmitDecision(decision.Id, 8);
        requestService.ApproveDecision(decision.Id, 9);

        return requestRepository.GetRequest(request.Id) is { } entry
            ? new IslamicFinanceFinancingRequestResponse
            {
                Id = entry.FinancingRequestId,
                RequestNumber = entry.RequestNumber,
                CustomerPartyId = entry.CustomerPartyId,
                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
            }
            : throw new InvalidOperationException("Expected financing request detail was not found.");
    }
}
