using System.Net;
using System.Net.Http.Json;
using CashLineERP.Contracts.Finance;
using CashLineERP.Contracts.Procurement;

namespace CashLineERP.Api.Tests;

public sealed class ReconciliationWorkflowQaTests : IClassFixture<ApiTestFactory>
{
    private readonly HttpClient _client;

    public ReconciliationWorkflowQaTests(ApiTestFactory factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task Bank_Statement_List_Detail_And_Reconciliation_Endpoints_Work_End_To_End()
    {
        await _client.AuthenticateAsFinanceMakerAsync();

        var createReceiptResponse = await _client.PostAsJsonAsync("/api/v1/finance/ar-receipts", new CreateAccountsReceivableReceiptRequest(
            CustomerId: 2,
            ReceiptDate: new DateOnly(2026, 4, 24),
            ReceiptMethod: "BANK_TRANSFER",
            CurrencyCode: "EGP",
            AmountAmount: 500m,
            ReferenceNumber: "HTTP-RECON-0001",
            Notes: "HTTP reconciliation receipt",
            Allocations: []));
        Assert.True(createReceiptResponse.IsSuccessStatusCode, await createReceiptResponse.Content.ReadAsStringAsync());

        var createdReceipt = await createReceiptResponse.Content.ReadFromJsonAsync<AccountsReceivableReceiptResponse>();
        Assert.NotNull(createdReceipt);

        var submitReceiptResponse = await _client.PostAsync($"/api/v1/finance/ar-receipts/{createdReceipt!.AccountsReceivableReceiptId}/submit", null);
        Assert.True(submitReceiptResponse.IsSuccessStatusCode, await submitReceiptResponse.Content.ReadAsStringAsync());

        await _client.AuthenticateAsFinanceCheckerAsync();

        var approveReceiptResponse = await _client.PostAsync($"/api/v1/finance/ar-receipts/{createdReceipt.AccountsReceivableReceiptId}/approve", null);
        Assert.True(approveReceiptResponse.IsSuccessStatusCode, await approveReceiptResponse.Content.ReadAsStringAsync());

        var postReceiptResponse = await _client.PostAsync($"/api/v1/finance/ar-receipts/{createdReceipt.AccountsReceivableReceiptId}/post", null);
        Assert.True(postReceiptResponse.IsSuccessStatusCode, await postReceiptResponse.Content.ReadAsStringAsync());

        var createBankStatementResponse = await _client.PostAsJsonAsync("/api/v1/finance/bank-statements", new CreateBankStatementRequest(
            BankAccountCode: "CIB-EGP-HTTP-001",
            StatementDate: new DateOnly(2026, 4, 24),
            CurrencyCode: "EGP",
            OpeningBalanceAmount: 1000m,
            ClosingBalanceAmount: 1500m,
            Lines:
            [
                new CreateBankStatementLineRequest(
                    TransactionDate: new DateOnly(2026, 4, 24),
                    ExternalReference: "HTTP-RECON-0001",
                    DescriptionText: "Customer transfer",
                    DebitAmount: 0m,
                    CreditAmount: 500m,
                    RunningBalanceAmount: 1500m)
            ]));
        Assert.Equal(HttpStatusCode.OK, createBankStatementResponse.StatusCode);

        var bankStatement = await createBankStatementResponse.Content.ReadFromJsonAsync<BankStatementResponse>();
        Assert.NotNull(bankStatement);
        Assert.Single(bankStatement!.Lines);

        var listResponse = await _client.GetAsync("/api/v1/finance/bank-statements");
        Assert.Equal(HttpStatusCode.OK, listResponse.StatusCode);
        var statements = await listResponse.Content.ReadFromJsonAsync<IReadOnlyCollection<BankStatementResponse>>();
        Assert.NotNull(statements);
        Assert.Contains(statements!, item => item.BankStatementId == bankStatement.BankStatementId);

        var detailResponse = await _client.GetAsync($"/api/v1/finance/bank-statements/{bankStatement.BankStatementId}");
        Assert.Equal(HttpStatusCode.OK, detailResponse.StatusCode);
        var detailedStatement = await detailResponse.Content.ReadFromJsonAsync<BankStatementResponse>();
        Assert.NotNull(detailedStatement);
        Assert.Equal("CIB-EGP-HTTP-001", detailedStatement!.BankAccountCode);

        var runResponse = await _client.PostAsJsonAsync("/api/v1/finance/reconciliations/run", new RunReceiptReconciliationRequest(
            AccountingPeriodId: 1,
            BankStatementId: bankStatement.BankStatementId,
            ActorUserId: 4));
        Assert.Equal(HttpStatusCode.OK, runResponse.StatusCode);

        var run = await runResponse.Content.ReadFromJsonAsync<ReconciliationRunResponse>();
        Assert.NotNull(run);
        Assert.Equal("COMPLETED", run!.RunStatusCode);
        Assert.Equal(1, run.MatchedItemCount);

        var getRunResponse = await _client.GetAsync($"/api/v1/finance/reconciliations/{run.ReceiptReconciliationRunId}");
        Assert.Equal(HttpStatusCode.OK, getRunResponse.StatusCode);
    }

    [Fact]
    public async Task Manual_Match_Override_And_Period_Close_Endpoints_Work()
    {
        await _client.AuthenticateAsFinanceMakerAsync();

        var unmatchedStatementResponse = await _client.PostAsJsonAsync("/api/v1/finance/bank-statements", new CreateBankStatementRequest(
            BankAccountCode: "CIB-EGP-HTTP-002",
            StatementDate: new DateOnly(2026, 4, 25),
            CurrencyCode: "EGP",
            OpeningBalanceAmount: 2000m,
            ClosingBalanceAmount: 2450m,
            Lines:
            [
                new CreateBankStatementLineRequest(
                    TransactionDate: new DateOnly(2026, 4, 25),
                    ExternalReference: "HTTP-MANUAL-0001",
                    DescriptionText: "Manual match candidate",
                    DebitAmount: 0m,
                    CreditAmount: 450m,
                    RunningBalanceAmount: 2450m)
            ]));
        Assert.Equal(HttpStatusCode.OK, unmatchedStatementResponse.StatusCode);
        var unmatchedStatement = await unmatchedStatementResponse.Content.ReadFromJsonAsync<BankStatementResponse>();
        Assert.NotNull(unmatchedStatement);

        var runUnmatchedResponse = await _client.PostAsJsonAsync("/api/v1/finance/reconciliations/run", new RunReceiptReconciliationRequest(
            AccountingPeriodId: 1,
            BankStatementId: unmatchedStatement!.BankStatementId,
            ActorUserId: 3));
        Assert.Equal(HttpStatusCode.OK, runUnmatchedResponse.StatusCode);

        var unmatchedQueueResponse = await _client.GetAsync("/api/v1/finance/reconciliations/exception-queue?matchStatusCode=UNMATCHED");
        Assert.Equal(HttpStatusCode.OK, unmatchedQueueResponse.StatusCode);
        var unmatchedQueue = await unmatchedQueueResponse.Content.ReadFromJsonAsync<IReadOnlyCollection<ReconciliationExceptionQueueItemResponse>>();
        Assert.NotNull(unmatchedQueue);
        var unmatchedItem = unmatchedQueue!.Single(item => item.BankStatementId == unmatchedStatement.BankStatementId);

        var createReceiptResponse = await _client.PostAsJsonAsync("/api/v1/finance/ar-receipts", new CreateAccountsReceivableReceiptRequest(
            CustomerId: 2,
            ReceiptDate: new DateOnly(2026, 4, 25),
            ReceiptMethod: "BANK_TRANSFER",
            CurrencyCode: "EGP",
            AmountAmount: 450m,
            ReferenceNumber: "HTTP-MANUAL-RECEIPT-0001",
            Notes: "Receipt for manual match",
            Allocations: []));
        Assert.True(createReceiptResponse.IsSuccessStatusCode, await createReceiptResponse.Content.ReadAsStringAsync());
        var createdReceipt = await createReceiptResponse.Content.ReadFromJsonAsync<AccountsReceivableReceiptResponse>();
        Assert.NotNull(createdReceipt);

        var submitReceiptResponse = await _client.PostAsync($"/api/v1/finance/ar-receipts/{createdReceipt!.AccountsReceivableReceiptId}/submit", null);
        Assert.True(submitReceiptResponse.IsSuccessStatusCode, await submitReceiptResponse.Content.ReadAsStringAsync());

        await _client.AuthenticateAsFinanceCheckerAsync();

        var approveReceiptResponse = await _client.PostAsync($"/api/v1/finance/ar-receipts/{createdReceipt.AccountsReceivableReceiptId}/approve", null);
        Assert.True(approveReceiptResponse.IsSuccessStatusCode, await approveReceiptResponse.Content.ReadAsStringAsync());

        var postReceiptResponse = await _client.PostAsync($"/api/v1/finance/ar-receipts/{createdReceipt.AccountsReceivableReceiptId}/post", null);
        Assert.True(postReceiptResponse.IsSuccessStatusCode, await postReceiptResponse.Content.ReadAsStringAsync());

        var manualMatchResponse = await _client.PostAsJsonAsync(
            $"/api/v1/finance/reconciliations/items/{unmatchedItem.ReceiptReconciliationItemId}/manual-match",
            new ManualMatchReconciliationItemRequest(
                ActorUserId: 4,
                MatchedDocumentType: "AR_RECEIPT",
                AccountsReceivableReceiptId: createdReceipt.AccountsReceivableReceiptId,
                AccountsPayablePaymentId: null,
                Notes: "HTTP manual match"));
        Assert.Equal(HttpStatusCode.OK, manualMatchResponse.StatusCode);
        var resolvedManualMatch = await manualMatchResponse.Content.ReadFromJsonAsync<ReconciliationItemResponse>();
        Assert.NotNull(resolvedManualMatch);
        Assert.Equal("MANUAL_MATCHED", resolvedManualMatch!.ResolutionCode);

        await _client.AuthenticateAsFinanceMakerAsync();

        var createBillResponse = await _client.PostAsJsonAsync("/api/v1/procurement/ap-bills", new CreateAccountsPayableBillRequest(
            SupplierId: 1,
            PurchaseOrderId: 1,
            GoodsReceiptId: 1,
            BillDate: new DateOnly(2026, 4, 26),
            DueDate: new DateOnly(2026, 5, 10),
            CurrencyCode: "EGP",
            Lines:
            [
                new CreateAccountsPayableBillLineRequest(1, 1001, 5m, "EA", 24m, "VAT14")
            ]));
        Assert.True(createBillResponse.IsSuccessStatusCode, await createBillResponse.Content.ReadAsStringAsync());
        var bill = await createBillResponse.Content.ReadFromJsonAsync<AccountsPayableBillResponse>();
        Assert.NotNull(bill);

        var submitBillResponse = await _client.PostAsync($"/api/v1/procurement/ap-bills/{bill!.AccountsPayableBillId}/submit", null);
        Assert.True(submitBillResponse.IsSuccessStatusCode, await submitBillResponse.Content.ReadAsStringAsync());

        await _client.AuthenticateAsFinanceCheckerAsync();

        var approveBillResponse = await _client.PostAsync($"/api/v1/procurement/ap-bills/{bill.AccountsPayableBillId}/approve-match", null);
        Assert.True(approveBillResponse.IsSuccessStatusCode, await approveBillResponse.Content.ReadAsStringAsync());

        var postBillResponse = await _client.PostAsync($"/api/v1/procurement/ap-bills/{bill.AccountsPayableBillId}/post", null);
        Assert.True(postBillResponse.IsSuccessStatusCode, await postBillResponse.Content.ReadAsStringAsync());
        var postedBill = await postBillResponse.Content.ReadFromJsonAsync<AccountsPayableBillResponse>();
        Assert.NotNull(postedBill);

        await _client.AuthenticateAsFinanceMakerAsync();

        var createPaymentResponse = await _client.PostAsJsonAsync("/api/v1/finance/ap-payments", new CreateAccountsPayablePaymentRequest(
            SupplierId: 1,
            PaymentDate: new DateOnly(2026, 4, 26),
            PaymentMethod: "BANK_TRANSFER",
            CurrencyCode: "EGP",
            AmountAmount: postedBill!.TotalAmount,
            ReferenceNumber: "HTTP-OVERRIDE-0001",
            Notes: "Payment for override scenario",
            Allocations:
            [
                new CreateAccountsPayablePaymentAllocationRequest(postedBill.AccountsPayableBillId, postedBill.TotalAmount, new DateOnly(2026, 4, 26))
            ]));
        Assert.True(createPaymentResponse.IsSuccessStatusCode, await createPaymentResponse.Content.ReadAsStringAsync());
        var payment = await createPaymentResponse.Content.ReadFromJsonAsync<AccountsPayablePaymentResponse>();
        Assert.NotNull(payment);

        var submitPaymentResponse = await _client.PostAsync($"/api/v1/finance/ap-payments/{payment!.AccountsPayablePaymentId}/submit", null);
        Assert.True(submitPaymentResponse.IsSuccessStatusCode, await submitPaymentResponse.Content.ReadAsStringAsync());

        await _client.AuthenticateAsFinanceCheckerAsync();

        var approvePaymentResponse = await _client.PostAsync($"/api/v1/finance/ap-payments/{payment.AccountsPayablePaymentId}/approve", null);
        Assert.True(approvePaymentResponse.IsSuccessStatusCode, await approvePaymentResponse.Content.ReadAsStringAsync());

        var postPaymentResponse = await _client.PostAsync($"/api/v1/finance/ap-payments/{payment.AccountsPayablePaymentId}/post", null);
        Assert.True(postPaymentResponse.IsSuccessStatusCode, await postPaymentResponse.Content.ReadAsStringAsync());

        await _client.AuthenticateAsFinanceMakerAsync();

        var exceptionStatementResponse = await _client.PostAsJsonAsync("/api/v1/finance/bank-statements", new CreateBankStatementRequest(
            BankAccountCode: "CIB-EGP-HTTP-003",
            StatementDate: new DateOnly(2026, 4, 26),
            CurrencyCode: "EGP",
            OpeningBalanceAmount: 3000m,
            ClosingBalanceAmount: 2580m,
            Lines:
            [
                new CreateBankStatementLineRequest(
                    TransactionDate: new DateOnly(2026, 4, 26),
                    ExternalReference: "HTTP-OVERRIDE-0001",
                    DescriptionText: "Override candidate",
                    DebitAmount: postedBill.TotalAmount - 10m,
                    CreditAmount: 0m,
                    RunningBalanceAmount: 2580m)
            ]));
        Assert.Equal(HttpStatusCode.OK, exceptionStatementResponse.StatusCode);
        var exceptionStatement = await exceptionStatementResponse.Content.ReadFromJsonAsync<BankStatementResponse>();
        Assert.NotNull(exceptionStatement);

        var runSettlementResponse = await _client.PostAsJsonAsync("/api/v1/finance/reconciliations/run-settlement", new RunReceiptReconciliationRequest(
            AccountingPeriodId: 1,
            BankStatementId: exceptionStatement!.BankStatementId,
            ActorUserId: 4));
        Assert.Equal(HttpStatusCode.OK, runSettlementResponse.StatusCode);

        var exceptionQueueResponse = await _client.GetAsync("/api/v1/finance/reconciliations/exception-queue?matchStatusCode=EXCEPTION");
        Assert.Equal(HttpStatusCode.OK, exceptionQueueResponse.StatusCode);
        var exceptionQueue = await exceptionQueueResponse.Content.ReadFromJsonAsync<IReadOnlyCollection<ReconciliationExceptionQueueItemResponse>>();
        Assert.NotNull(exceptionQueue);
        var exceptionItem = exceptionQueue!.Single(item => item.BankStatementId == exceptionStatement.BankStatementId);

        var overrideResponse = await _client.PostAsJsonAsync(
            $"/api/v1/finance/reconciliations/items/{exceptionItem.ReceiptReconciliationItemId}/override",
            new OverrideReconciliationItemRequest(
                ActorUserId: 4,
                MatchedDocumentType: "AP_PAYMENT",
                AccountsReceivableReceiptId: null,
                AccountsPayablePaymentId: payment.AccountsPayablePaymentId,
                ApprovalRoleCode: "TREASURY_MANAGER",
                OverrideReasonCode: "BANK_CHARGE",
                Notes: "Approved variance after finance review"));
        Assert.Equal(HttpStatusCode.OK, overrideResponse.StatusCode);
        var resolvedOverride = await overrideResponse.Content.ReadFromJsonAsync<ReconciliationItemResponse>();
        Assert.NotNull(resolvedOverride);
        Assert.Equal("OVERRIDDEN", resolvedOverride!.ResolutionCode);
        Assert.Equal("TREASURY_MANAGER", resolvedOverride.OverrideApprovalRoleCode);
        Assert.Equal("BANK_CHARGE", resolvedOverride.OverrideReasonCode);

        var historyResponse = await _client.GetAsync($"/api/v1/finance/reconciliations/history?bankStatementId={exceptionStatement.BankStatementId}&resolutionCode=OVERRIDDEN");
        Assert.Equal(HttpStatusCode.OK, historyResponse.StatusCode);
        var history = await historyResponse.Content.ReadFromJsonAsync<IReadOnlyCollection<ReconciliationHistoryEntryResponse>>();
        Assert.NotNull(history);
        var historyItem = history!.Single(item => item.ReceiptReconciliationItemId == exceptionItem.ReceiptReconciliationItemId);
        Assert.Equal("OVERRIDDEN", historyItem.ResolutionCode);
        Assert.Equal("TREASURY_MANAGER", historyItem.OverrideApprovalRoleCode);

        var auditResponse = await _client.GetAsync($"/api/v1/finance/reconciliations/items/{exceptionItem.ReceiptReconciliationItemId}/audit");
        Assert.Equal(HttpStatusCode.OK, auditResponse.StatusCode);
        var audit = await auditResponse.Content.ReadFromJsonAsync<ReconciliationAuditResponse>();
        Assert.NotNull(audit);
        Assert.Equal(exceptionItem.ReceiptReconciliationItemId, audit!.Item.ReceiptReconciliationItemId);
        Assert.Contains(audit.Events, item => item.ActionCode == "EXCEPTION_IDENTIFIED");
        Assert.Contains(audit.Events, item => item.ActionCode == "OVERRIDDEN");

        var bulkResolveResponse = await _client.PostAsJsonAsync(
            "/api/v1/finance/reconciliations/bulk-resolve",
            new BulkResolveReconciliationItemsRequest(
                ActorUserId: 0,
                Items:
                [
                    new BulkResolveReconciliationItemRequest(
                        exceptionItem.ReceiptReconciliationItemId,
                        "OVERRIDE",
                        "AP_PAYMENT",
                        null,
                        payment.AccountsPayablePaymentId,
                        "TREASURY_MANAGER",
                        "BANK_CHARGE",
                        "Second attempt after resolution should fail",
                        null)
                ]));
        Assert.Equal(HttpStatusCode.OK, bulkResolveResponse.StatusCode);
        var bulkResult = await bulkResolveResponse.Content.ReadFromJsonAsync<BulkResolveReconciliationItemsResponse>();
        Assert.NotNull(bulkResult);
        Assert.Equal(1, bulkResult!.FailedCount);
        Assert.Contains(bulkResult.Items, item => !item.Succeeded);

        await _client.AuthenticateAsFinanceMakerAsync();

        var createMayReceiptResponse = await _client.PostAsJsonAsync("/api/v1/finance/ar-receipts", new CreateAccountsReceivableReceiptRequest(
            CustomerId: 2,
            ReceiptDate: new DateOnly(2026, 5, 5),
            ReceiptMethod: "BANK_TRANSFER",
            CurrencyCode: "EGP",
            AmountAmount: 300m,
            ReferenceNumber: "HTTP-MAY-CLOSE-0001",
            Notes: "Period two close readiness receipt",
            Allocations: []));
        Assert.True(createMayReceiptResponse.IsSuccessStatusCode, await createMayReceiptResponse.Content.ReadAsStringAsync());
        var mayReceipt = await createMayReceiptResponse.Content.ReadFromJsonAsync<AccountsReceivableReceiptResponse>();
        Assert.NotNull(mayReceipt);

        var submitMayReceiptResponse = await _client.PostAsync($"/api/v1/finance/ar-receipts/{mayReceipt!.AccountsReceivableReceiptId}/submit", null);
        Assert.True(submitMayReceiptResponse.IsSuccessStatusCode, await submitMayReceiptResponse.Content.ReadAsStringAsync());

        await _client.AuthenticateAsFinanceCheckerAsync();

        var approveMayReceiptResponse = await _client.PostAsync($"/api/v1/finance/ar-receipts/{mayReceipt.AccountsReceivableReceiptId}/approve", null);
        Assert.True(approveMayReceiptResponse.IsSuccessStatusCode, await approveMayReceiptResponse.Content.ReadAsStringAsync());

        var postMayReceiptResponse = await _client.PostAsync($"/api/v1/finance/ar-receipts/{mayReceipt.AccountsReceivableReceiptId}/post", null);
        Assert.True(postMayReceiptResponse.IsSuccessStatusCode, await postMayReceiptResponse.Content.ReadAsStringAsync());

        await _client.AuthenticateAsFinanceMakerAsync();

        var mayStatementResponse = await _client.PostAsJsonAsync("/api/v1/finance/bank-statements", new CreateBankStatementRequest(
            BankAccountCode: "CIB-EGP-MAY-001",
            StatementDate: new DateOnly(2026, 5, 5),
            CurrencyCode: "EGP",
            OpeningBalanceAmount: 5000m,
            ClosingBalanceAmount: 5300m,
            Lines:
            [
                new CreateBankStatementLineRequest(
                    TransactionDate: new DateOnly(2026, 5, 5),
                    ExternalReference: "HTTP-MAY-CLOSE-0001",
                    DescriptionText: "May close readiness receipt",
                    DebitAmount: 0m,
                    CreditAmount: 300m,
                    RunningBalanceAmount: 5300m)
            ]));
        Assert.Equal(HttpStatusCode.OK, mayStatementResponse.StatusCode);
        var mayStatement = await mayStatementResponse.Content.ReadFromJsonAsync<BankStatementResponse>();
        Assert.NotNull(mayStatement);

        var mayRunResponse = await _client.PostAsJsonAsync("/api/v1/finance/reconciliations/run", new RunReceiptReconciliationRequest(
            AccountingPeriodId: 2,
            BankStatementId: mayStatement!.BankStatementId,
            ActorUserId: 4));
        Assert.Equal(HttpStatusCode.OK, mayRunResponse.StatusCode);

        var readinessResponse = await _client.GetAsync("/api/v1/finance/accounting-periods/2/close-readiness");
        Assert.Equal(HttpStatusCode.OK, readinessResponse.StatusCode);

        await _client.AuthenticateAsFinanceCheckerAsync();

        var markReadyResponse = await _client.PostAsync("/api/v1/finance/accounting-periods/2/mark-ready-to-close", null);
        Assert.Equal(HttpStatusCode.OK, markReadyResponse.StatusCode);
        var readyPeriod = await markReadyResponse.Content.ReadFromJsonAsync<AccountingPeriodResponse>();
        Assert.NotNull(readyPeriod);
        Assert.Equal("READY_TO_CLOSE", readyPeriod!.CloseStatusCode);

        var closeResponse = await _client.PostAsync("/api/v1/finance/accounting-periods/2/close", null);
        Assert.Equal(HttpStatusCode.OK, closeResponse.StatusCode);
        var closedPeriod = await closeResponse.Content.ReadFromJsonAsync<AccountingPeriodResponse>();
        Assert.NotNull(closedPeriod);
        Assert.Equal("CLOSED", closedPeriod!.CloseStatusCode);
    }
}
