using System.Text;
using CashLineERP.Application.Finance;
using CashLineERP.Application.Procurement;
using CashLineERP.Application.Reporting;
using CashLineERP.Contracts.Finance;
using CashLineERP.Contracts.Procurement;
using CashLineERP.Domain.IslamicFinance.Disbursements;
using CashLineERP.Domain.IslamicFinance.Facilities;
using CashLineERP.Domain.IslamicFinance.Murabaha;
using CashLineERP.Domain.IslamicFinance.PartnerLenders;
using CashLineERP.Domain.IslamicFinance.Requests;
using CashLineERP.Domain.IslamicFinance.Settlements;
using CashLineERP.Domain.Parties;
using CashLineERP.Domain.Procurement;
using CashLineERP.SharedKernel.IslamicFinance;
using CashLineERP.Infrastructure.Persistence.Repositories;

namespace CashLineERP.Api.Tests;

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

        var accountingRuleService = new AccountingRuleService(new FinanceAccountingRuleRepository(dbContext));
        var invoiceService = new AccountsReceivableInvoiceService(new AccountsReceivableInvoiceRepository(dbContext));
        var receiptService = new AccountsReceivableReceiptService(new AccountsReceivableReceiptRepository(dbContext));
        var payableBillService = new AccountsPayableBillService(new AccountsPayableBillRepository(dbContext));
        var payablePaymentService = new AccountsPayablePaymentService(new AccountsPayablePaymentRepository(dbContext));
        var reportingService = new CoreReportingService(new CoreReportingRepository(dbContext));

        var createdRule = accountingRuleService.CreateAccountingRule(new CreateFinanceAccountingRuleRequest(
            RuleName: "Deferred expense classification",
            GeneralLedgerAccountId: 6201,
            GeneralLedgerAccountCode: "6201",
            GeneralLedgerAccountName: "Deferred Expense",
            AccountCategoryCode: "EXPENSE",
            StatementSectionCode: "OPERATING_EXPENSE",
            CashFlowCategoryCode: "NON_CASH",
            IsCashAccount: false,
            DisplayOrder: 6201,
            EffectiveFromDate: new DateOnly(2026, 4, 1),
            EffectiveToDate: null,
            Notes: "Service QA accounting rule."));

        Assert.Equal("DRAFT", createdRule.StatusCode);

        var submittedRule = accountingRuleService.SubmitAccountingRule(createdRule.FinanceAccountingRuleId, 71);
        Assert.Equal("SUBMITTED", submittedRule.StatusCode);

        var selfApprovalError = Assert.Throws<InvalidOperationException>(() =>
            accountingRuleService.ApproveAccountingRule(createdRule.FinanceAccountingRuleId, 71));
        Assert.Contains("self-approval", selfApprovalError.Message, StringComparison.OrdinalIgnoreCase);

        var approvedRule = accountingRuleService.ApproveAccountingRule(createdRule.FinanceAccountingRuleId, 72);
        Assert.Equal("APPROVED", approvedRule.StatusCode);

        var activatedRule = accountingRuleService.ActivateAccountingRule(createdRule.FinanceAccountingRuleId, 72);
        Assert.True(activatedRule.IsActive);
        Assert.Equal("APPROVED", activatedRule.StatusCode);

        var postedInvoice = AccountsReceivableWorkflowTestHelper.SubmitApproveAndPostInvoice(
            invoiceService,
            invoiceService.CreateInvoice(new CreateAccountsReceivableInvoiceRequest(
                CustomerId: 2,
                SalesOrderId: 1,
                DispatchOrderId: 1,
                InvoiceType: "TAX",
                InvoiceDate: new DateOnly(2026, 4, 26),
                TaxDate: new DateOnly(2026, 4, 26),
                DueDate: new DateOnly(2026, 4, 30),
                CurrencyCode: "USD",
                Lines:
                [
                    new CreateAccountsReceivableInvoiceLineRequest(1, 1001, 2m, "EA", 12m, "VAT14")
                ])).AccountsReceivableInvoiceId);

        var postedReceipt = AccountsReceivableWorkflowTestHelper.SubmitApproveAndPostReceipt(
            receiptService,
            receiptService.CreateReceipt(new CreateAccountsReceivableReceiptRequest(
                CustomerId: 2,
                ReceiptDate: new DateOnly(2026, 4, 27),
                ReceiptMethod: "BANK_TRANSFER",
                CurrencyCode: "USD",
                AmountAmount: postedInvoice.TotalAmount,
                ReferenceNumber: "FIN-STMT-RCPT-01",
                Notes: "Statement QA receipt",
                Allocations:
                [
                    new CreateAccountsReceivableReceiptAllocationRequest(postedInvoice.AccountsReceivableInvoiceId, postedInvoice.TotalAmount, new DateOnly(2026, 4, 27))
                ])).AccountsReceivableReceiptId);

        Assert.NotNull(postedReceipt);

        var oldSupplier = new BusinessParty("SUP-OLD-001", "Archived Supplier Exposure", "SUPPLIER");
        var draftAllocationSupplier = new BusinessParty("SUP-DRAFT-001", "Draft Allocation Supplier", "SUPPLIER");
        dbContext.BusinessParties.AddRange(oldSupplier, draftAllocationSupplier);
        dbContext.SaveChanges();

        var oldBill = new AccountsPayableBill(
            "BILL-OLD-001",
            oldSupplier.Id,
            null,
            null,
            null,
            "INVOICE",
            new DateOnly(2026, 4, 1),
            new DateOnly(2026, 4, 4),
            "USD");
        oldBill.AddLine(1, null, 1001, 4m, "EA", 20m, "VAT14");
        oldBill.Submit(71);
        oldBill.RecordMatchReview(72, true, 0m, "Old supplier bill matched.");
        oldBill.ApproveForPosting(72);
        oldBill.Post(72, 9001);
        dbContext.AccountsPayableBills.Add(oldBill);

        var postedDraftAllocationBillEntity = new AccountsPayableBill(
            "BILL-DRAFT-ALLOC-001",
            draftAllocationSupplier.Id,
            null,
            null,
            null,
            "INVOICE",
            new DateOnly(2026, 4, 23),
            new DateOnly(2026, 4, 30),
            "USD");
        postedDraftAllocationBillEntity.AddLine(1, null, 1001, 5m, "EA", 30m, "VAT14");
        postedDraftAllocationBillEntity.Submit(71);
        postedDraftAllocationBillEntity.RecordMatchReview(72, true, 0m, "Draft allocation bill matched.");
        postedDraftAllocationBillEntity.ApproveForPosting(72);
        postedDraftAllocationBillEntity.Post(72, 9002);
        dbContext.AccountsPayableBills.Add(postedDraftAllocationBillEntity);
        dbContext.SaveChanges();

        var postedSupplierBill = payableBillService.GetBill(postedDraftAllocationBillEntity.Id);
        Assert.NotNull(postedSupplierBill);

        var draftPayment = payablePaymentService.CreatePayment(new CreateAccountsPayablePaymentRequest(
            SupplierId: draftAllocationSupplier.Id,
            PaymentDate: new DateOnly(2026, 4, 24),
            PaymentMethod: "BANK_TRANSFER",
            CurrencyCode: "USD",
            AmountAmount: postedSupplierBill!.TotalAmount / 2m,
            ReferenceNumber: "DRAFT-PAY-001",
            Notes: "Draft payment must not reduce open payable exposure.",
            Allocations:
            [
                new CreateAccountsPayablePaymentAllocationRequest(postedSupplierBill.AccountsPayableBillId, postedSupplierBill.TotalAmount / 2m, new DateOnly(2026, 4, 24))
            ]));
        Assert.Equal("DRAFT", draftPayment.StatusCode);

        var partnerLenderParty = new BusinessParty("LND-0001", "Baraka Partner Lender", "PARTNER_LENDER");
        dbContext.BusinessParties.Add(partnerLenderParty);
        dbContext.SaveChanges();

        var partnerLender = new IslamicFinancePartnerLender(
            partnerLenderParty.Id,
            "USD",
            supportsMurabaha: true,
            supportsReceivablesFinance: false,
            supportsTawarruq: false);
        partnerLender.Submit(81);
        partnerLender.Approve(82);
        partnerLender.Activate(82);
        dbContext.IslamicFinancePartnerLenders.Add(partnerLender);
        dbContext.SaveChanges();

        var facility = new IslamicFinanceFacility(
            partnerLender.Id,
            2,
            1,
            "FAC-USD-0001",
            "USD",
            5000m);
        facility.Submit(81);
        facility.Approve(82);
        facility.Activate(82);
        facility.Allocate(1500m, 82);
        dbContext.IslamicFinanceFacilities.Add(facility);

        var draftFacility = new IslamicFinanceFacility(
            partnerLender.Id,
            2,
            1,
            "FAC-DRAFT-0001",
            "USD",
            8000m);
        dbContext.IslamicFinanceFacilities.Add(draftFacility);
        dbContext.SaveChanges();

        var request = new IslamicFinanceRequest(
            "IFREQ-0001",
            2,
            2,
            "CUSTOMER",
            1,
            facility.Id,
            IslamicFinanceFundingSourceMode.Partner,
            "SALES_ORDER",
            1,
            "USD",
            1500m);
        request.Submit(81);
        request.Approve(82);
        request.AssignPartnerLender(partnerLender.Id, 82);
        dbContext.IslamicFinanceRequests.Add(request);
        dbContext.SaveChanges();

        var murabahaCase = new IslamicFinanceMurabahaCase(
            "MC-0001",
            request.Id,
            2,
            partnerLender.Id,
            1,
            1,
            null,
            "USD",
            1200m,
            180m);
        murabahaCase.Submit(81);
        murabahaCase.Approve(82);
        murabahaCase.Activate(82);
        dbContext.IslamicFinanceMurabahaCases.Add(murabahaCase);
        dbContext.SaveChanges();

        var disbursement = new IslamicFinanceMurabahaDisbursement(
            "DISB-0001",
            murabahaCase.Id,
            2,
            partnerLender.Id,
            1,
            IslamicFinanceFundingSourceMode.Partner,
            IslamicFinanceDisbursementTargetType.SupplierPayment,
            "USD",
            1380m,
            new DateOnly(2026, 4, 27));
        disbursement.Submit(81);
        disbursement.Approve(82);
        disbursement.Execute(82, "WIRE-0001", new DateTime(2026, 4, 27, 10, 0, 0, DateTimeKind.Utc), 1380m, "Service QA disbursement");
        dbContext.IslamicFinanceMurabahaDisbursements.Add(disbursement);
        dbContext.SaveChanges();

        var settlement = new IslamicFinanceSettlementHeader(
            "SET-0001",
            murabahaCase.Id,
            request.Id,
            partnerLender.Id,
            IslamicFinanceFundingSourceMode.Partner,
            new DateOnly(2026, 4, 28),
            "USD",
            1380m);
        settlement.Submit(81);
        settlement.Approve(82);
        settlement.Close(82);
        dbContext.IslamicFinanceSettlementHeaders.Add(settlement);
        dbContext.SaveChanges();

        var balanceSheet = reportingService.GetFinancialStatementReport("BALANCE_SHEET", null, null, new DateOnly(2026, 4, 27));
        var incomeStatement = reportingService.GetFinancialStatementReport("INCOME_STATEMENT", new DateOnly(2026, 4, 1), new DateOnly(2026, 4, 27), null);
        var cashFlow = reportingService.GetFinancialStatementReport("CASH_FLOW", new DateOnly(2026, 4, 1), new DateOnly(2026, 4, 27), null);
        var trialBalance = reportingService.GetFinancialStatementReport("TRIAL_BALANCE", new DateOnly(2026, 4, 1), new DateOnly(2026, 4, 27), new DateOnly(2026, 4, 27));
        var accountingEntries = reportingService.GetFinancialStatementReport("ACCOUNTING_ENTRIES", new DateOnly(2026, 4, 1), new DateOnly(2026, 4, 27), null);
        var stakeholderSummary = reportingService.GetFinanceStakeholderSummaryReport(new DateOnly(2026, 4, 5), new DateOnly(2026, 4, 30));

        Assert.Equal("BALANCE_SHEET", balanceSheet.StatementCode);
        Assert.NotEmpty(balanceSheet.Rows);
        Assert.Contains(balanceSheet.Metrics, metric => metric.MetricCode == "TOTAL_ASSETS");

        Assert.Equal("INCOME_STATEMENT", incomeStatement.StatementCode);
        Assert.NotEmpty(incomeStatement.Rows);
        Assert.Contains(incomeStatement.Metrics, metric => metric.MetricCode == "NET_INCOME");

        Assert.Equal("CASH_FLOW", cashFlow.StatementCode);
        Assert.NotEmpty(cashFlow.Rows);
        Assert.Contains(cashFlow.Metrics, metric => metric.MetricCode == "CLOSING_CASH");

        Assert.Equal("TRIAL_BALANCE", trialBalance.StatementCode);
        Assert.NotEmpty(trialBalance.Rows);
        Assert.Contains(trialBalance.Rows, row => row.GeneralLedgerAccountCode == "1001");

        Assert.Equal("ACCOUNTING_ENTRIES", accountingEntries.StatementCode);
        Assert.NotEmpty(accountingEntries.Rows);
        Assert.Contains(accountingEntries.Rows, row => row.DocumentType == "AR_RECEIPT");

        Assert.NotEmpty(stakeholderSummary.Rows);
        Assert.DoesNotContain(stakeholderSummary.Rows, row => row.StakeholderCode == "SUP-OLD-001");
        Assert.Contains(stakeholderSummary.Rows, row => row.StakeholderCode == "SUP-DRAFT-001" && row.OpenPayableAmount >= postedSupplierBill.TotalAmount);
        Assert.Contains(stakeholderSummary.Rows, row => row.StakeholderType == "CUSTOMER" && row.FacilityLimitAmount == 5000m && row.RequestedFinancingAmount >= 1500m);
        Assert.Contains(stakeholderSummary.Rows, row => row.StakeholderType == "PARTNER_LENDER" && row.FacilityLimitAmount == 5000m && row.DisbursedAmount >= 1380m && row.SettledAmount >= 1380m);

        var customerDetail = reportingService.GetFinanceStakeholderDetailReport("CUSTOMER", 2, "USD", new DateOnly(2026, 4, 5), new DateOnly(2026, 4, 30));
        Assert.Equal("CUSTOMER", customerDetail.StakeholderType);
        Assert.Equal(2, customerDetail.StakeholderId);
        Assert.Contains(customerDetail.Rows, row => row.ActivityType == "AR_INVOICE");
        Assert.Contains(customerDetail.Rows, row => row.ActivityType == "AR_RECEIPT");
        Assert.Contains(customerDetail.Rows, row => row.ActivityType == "IF_REQUEST");
        Assert.Contains(customerDetail.Rows, row => row.ActivityType == "IF_MURABAHA_CASE");
        Assert.Contains(customerDetail.Rows, row => row.ActivityType == "IF_DISBURSEMENT");
        Assert.Contains(customerDetail.Rows, row => row.ActivityType == "IF_SETTLEMENT");

        var csvExport = reportingService.ExportFinancialStatement("BALANCE_SHEET", "CSV", null, null, new DateOnly(2026, 4, 27));
        Assert.Equal("text/csv", csvExport.ContentType);
        Assert.Contains("SectionCode,SectionTitle", Encoding.UTF8.GetString(csvExport.ContentBytes));

        var excelExport = reportingService.ExportFinancialStatement("INCOME_STATEMENT", "EXCEL", new DateOnly(2026, 4, 1), new DateOnly(2026, 4, 27), null);
        Assert.Equal("application/vnd.ms-excel", excelExport.ContentType);
        Assert.Contains("<Workbook", Encoding.UTF8.GetString(excelExport.ContentBytes));

        var pdfExport = reportingService.ExportFinancialStatement("ACCOUNTING_ENTRIES", "PDF", new DateOnly(2026, 4, 1), new DateOnly(2026, 4, 27), null);
        Assert.Equal("application/pdf", pdfExport.ContentType);
        Assert.StartsWith("%PDF-1.4", Encoding.ASCII.GetString(pdfExport.ContentBytes));

        var stakeholderExport = reportingService.ExportFinanceStakeholderSummaryReportCsv(new DateOnly(2026, 4, 5), new DateOnly(2026, 4, 30));
        Assert.Contains("StakeholderType,StakeholderId,StakeholderCode", stakeholderExport);
        Assert.Contains("PARTNER_LENDER", stakeholderExport);

        var stakeholderDetailExport = reportingService.ExportFinanceStakeholderDetailReportCsv("CUSTOMER", 2, "USD", new DateOnly(2026, 4, 5), new DateOnly(2026, 4, 30));
        Assert.Contains("ActivityDate,ActivityType,RecordId,RecordNumber", stakeholderDetailExport);
        Assert.Contains("IF_MURABAHA_CASE", stakeholderDetailExport);
    }
}
