using CashLineERP.Application.Finance;
using CashLineERP.Application.Inventory;
using CashLineERP.Application.Procurement;
using CashLineERP.Application.Tax;
using CashLineERP.Contracts.Finance;
using CashLineERP.Contracts.Inventory;
using CashLineERP.Contracts.Procurement;
using CashLineERP.Contracts.Tax;
using CashLineERP.Infrastructure.Persistence;
using CashLineERP.Infrastructure.Persistence.Repositories;

namespace CashLineERP.Api.Tests;

public sealed class PeriodLockEnforcementServiceQaTests
{
    [Fact]
    public void AP_Bill_Creation_Is_Blocked_When_Period_Is_Locked()
    {
        using var dbContext = QaDbContextFactory.CreateContext();
        LockPeriod(dbContext, "2026-04", "CLOSED");

        var billService = new AccountsPayableBillService(new AccountsPayableBillRepository(dbContext));

        var error = Assert.Throws<InvalidOperationException>(() =>
            billService.CreateBill(new CreateAccountsPayableBillRequest(
                SupplierId: 1,
                PurchaseOrderId: 1,
                GoodsReceiptId: 1,
                BillDate: new DateOnly(2026, 4, 14),
                DueDate: new DateOnly(2026, 4, 30),
                CurrencyCode: "EGP",
                Lines:
                [
                    new CreateAccountsPayableBillLineRequest(1, 1001, 10m, "EA", 24m, "VAT14")
                ])));

        Assert.Contains("2026-04", error.Message, StringComparison.OrdinalIgnoreCase);
        Assert.Contains("blocked", error.Message, StringComparison.OrdinalIgnoreCase);
    }

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

        var billService = new AccountsPayableBillService(new AccountsPayableBillRepository(dbContext));
        var paymentService = new AccountsPayablePaymentService(new AccountsPayablePaymentRepository(dbContext));

        var bill = billService.CreateBill(new CreateAccountsPayableBillRequest(
            SupplierId: 1,
            PurchaseOrderId: 1,
            GoodsReceiptId: 1,
            BillDate: new DateOnly(2026, 4, 18),
            DueDate: new DateOnly(2026, 4, 30),
            CurrencyCode: "EGP",
            Lines:
            [
                new CreateAccountsPayableBillLineRequest(1, 1001, 10m, "EA", 24m, "VAT14")
            ]));

        billService.SubmitBill(bill.AccountsPayableBillId, 3);
        billService.ApproveMatch(bill.AccountsPayableBillId, 4);
        var postedBill = billService.PostBill(bill.AccountsPayableBillId, 4);

        var payment = paymentService.CreatePayment(new CreateAccountsPayablePaymentRequest(
            SupplierId: 1,
            PaymentDate: new DateOnly(2026, 4, 19),
            PaymentMethod: "BANK_TRANSFER",
            CurrencyCode: "EGP",
            AmountAmount: postedBill.TotalAmount,
            ReferenceNumber: "LOCK-AP-PAY-0001",
            Notes: "Period lock QA",
            Allocations:
            [
                new CreateAccountsPayablePaymentAllocationRequest(postedBill.AccountsPayableBillId, postedBill.TotalAmount, new DateOnly(2026, 4, 19))
            ]));

        LockPeriod(dbContext, "2026-04", "READY_TO_CLOSE");

        var error = Assert.Throws<InvalidOperationException>(() =>
            paymentService.SubmitPayment(payment.AccountsPayablePaymentId, 7));

        Assert.Contains("AP payment", error.Message, StringComparison.OrdinalIgnoreCase);
        Assert.Contains("READY_TO_CLOSE", error.Message, StringComparison.OrdinalIgnoreCase);
    }

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

        var invoiceService = new AccountsReceivableInvoiceService(new AccountsReceivableInvoiceRepository(dbContext));
        var invoice = invoiceService.CreateInvoice(new CreateAccountsReceivableInvoiceRequest(
            CustomerId: 2,
            SalesOrderId: 1,
            DispatchOrderId: 1,
            InvoiceType: "TAX",
            InvoiceDate: new DateOnly(2026, 4, 20),
            TaxDate: new DateOnly(2026, 4, 20),
            DueDate: new DateOnly(2026, 4, 30),
            CurrencyCode: "EGP",
            Lines:
            [
                new CreateAccountsReceivableInvoiceLineRequest(1, 1001, 5m, "EA", 32m, "VAT14")
            ]));

        invoiceService.SubmitInvoice(invoice.AccountsReceivableInvoiceId, 5);
        invoiceService.ApproveInvoice(invoice.AccountsReceivableInvoiceId, 6);
        LockPeriod(dbContext, "2026-04", "CLOSED");

        var error = Assert.Throws<InvalidOperationException>(() =>
            invoiceService.PostInvoice(invoice.AccountsReceivableInvoiceId, 6));

        Assert.Contains("AR invoice", error.Message, StringComparison.OrdinalIgnoreCase);
        Assert.Contains("2026-04", error.Message, StringComparison.OrdinalIgnoreCase);
    }

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

        var adjustmentService = new InventoryAdjustmentService(new InventoryAdjustmentRepository(dbContext));
        var adjustment = adjustmentService.CreateInventoryAdjustment(new CreateInventoryAdjustmentRequest(
            BranchId: 1,
            WarehouseId: 1,
            AdjustmentDate: new DateOnly(2026, 5, 6),
            ReasonCode: "COUNT_VARIANCE",
            Notes: "Period lock inventory QA.",
            Lines:
            [
                new CreateInventoryAdjustmentLineRequest(1001, null, -1m, "EA", 18m, "AVAILABLE", "LOCK-INV-0001", null, "Variance")
            ]));

        adjustmentService.SubmitInventoryAdjustment(adjustment.InventoryAdjustmentId, 11);
        adjustmentService.ApproveInventoryAdjustment(adjustment.InventoryAdjustmentId, 12);
        LockPeriod(dbContext, "2026-05", "READY_TO_CLOSE");

        var error = Assert.Throws<InvalidOperationException>(() =>
            adjustmentService.PostInventoryAdjustment(adjustment.InventoryAdjustmentId, 13));

        Assert.Contains("Inventory adjustment", error.Message, StringComparison.OrdinalIgnoreCase);
        Assert.Contains("READY_TO_CLOSE", error.Message, StringComparison.OrdinalIgnoreCase);
    }

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

        var invoiceService = new AccountsReceivableInvoiceService(new AccountsReceivableInvoiceRepository(dbContext));
        var etaService = new EtaComplianceService(new EtaComplianceRepository(dbContext));

        var invoice = invoiceService.CreateInvoice(new CreateAccountsReceivableInvoiceRequest(
            CustomerId: 2,
            SalesOrderId: 1,
            DispatchOrderId: 1,
            InvoiceType: "TAX",
            InvoiceDate: new DateOnly(2026, 4, 22),
            TaxDate: new DateOnly(2026, 4, 22),
            DueDate: new DateOnly(2026, 4, 30),
            CurrencyCode: "EGP",
            Lines:
            [
                new CreateAccountsReceivableInvoiceLineRequest(1, 1001, 2m, "EA", 32m, "VAT14")
            ]));

        var postedInvoice = AccountsReceivableWorkflowTestHelper.SubmitApproveAndPostInvoice(invoiceService, invoice.AccountsReceivableInvoiceId);
        LockPeriod(dbContext, "2026-04", "CLOSED");

        var submissionError = Assert.Throws<InvalidOperationException>(() =>
            etaService.SubmitInvoice(postedInvoice.AccountsReceivableInvoiceId, new SubmitInvoiceToEtaRequest(
                ActorUserId: 4,
                PayloadText: null)));

        Assert.Contains("ETA submission", submissionError.Message, StringComparison.OrdinalIgnoreCase);

        UnlockPeriod(dbContext, "2026-04");
        var submitted = etaService.SubmitInvoice(postedInvoice.AccountsReceivableInvoiceId, new SubmitInvoiceToEtaRequest(
            ActorUserId: 4,
            PayloadText: null));

        etaService.RecordInvoiceResponse(
            postedInvoice.AccountsReceivableInvoiceId,
            submitted.EtaSubmissionLogId,
            new RecordEtaInvoiceResponseRequest(
                ActorUserId: 5,
                StatusCode: "REJECTED",
                EtaUuid: null,
                ResponseText: "{\"status\":\"rejected\"}",
                ErrorCode: "ETA-422",
                ErrorMessage: "Validation rejected",
                NotesText: "Rejected for correction."));

        LockPeriod(dbContext, "2026-04", "READY_TO_CLOSE");

        var resubmitError = Assert.Throws<InvalidOperationException>(() =>
            etaService.ResubmitRejectedInvoice(
                postedInvoice.AccountsReceivableInvoiceId,
                submitted.EtaSubmissionLogId,
                new ResubmitEtaInvoiceRequest(
                    ActorUserId: 6,
                    PayloadText: null,
                    CorrectionNotes: "Corrected and resubmitted.")));

        Assert.Contains("ETA resubmission", resubmitError.Message, StringComparison.OrdinalIgnoreCase);
    }

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

        var invoiceService = new AccountsReceivableInvoiceService(new AccountsReceivableInvoiceRepository(dbContext));
        var etaService = new EtaComplianceService(new EtaComplianceRepository(dbContext));

        var invoice = invoiceService.CreateInvoice(new CreateAccountsReceivableInvoiceRequest(
            CustomerId: 2,
            SalesOrderId: 1,
            DispatchOrderId: 1,
            InvoiceType: "TAX",
            InvoiceDate: new DateOnly(2026, 4, 23),
            TaxDate: new DateOnly(2026, 4, 23),
            DueDate: new DateOnly(2026, 4, 30),
            CurrencyCode: "EGP",
            Lines:
            [
                new CreateAccountsReceivableInvoiceLineRequest(1, 1001, 1m, "EA", 32m, "VAT14")
            ]));

        var postedInvoice = AccountsReceivableWorkflowTestHelper.SubmitApproveAndPostInvoice(invoiceService, invoice.AccountsReceivableInvoiceId);

        var submission = etaService.SubmitInvoice(postedInvoice.AccountsReceivableInvoiceId, new SubmitInvoiceToEtaRequest(
            ActorUserId: 4,
            PayloadText: null));

        LockPeriod(dbContext, "2026-04", "CLOSED");

        var response = etaService.RecordInvoiceResponse(
            postedInvoice.AccountsReceivableInvoiceId,
            submission.EtaSubmissionLogId,
            new RecordEtaInvoiceResponseRequest(
                ActorUserId: 5,
                StatusCode: "ACCEPTED",
                EtaUuid: "ETA-UUID-LOCK-0001",
                ResponseText: "{\"status\":\"accepted\"}",
                ErrorCode: null,
                ErrorMessage: null,
                NotesText: "Acceptance recorded after lock."));

        Assert.Equal("ACCEPTED", response.StatusCode);
        Assert.Equal("ETA-UUID-LOCK-0001", response.EtaUuid);
    }

    private static void LockPeriod(CashLineErpDbContext dbContext, string periodCode, string lockStatusCode)
    {
        var period = dbContext.AccountingPeriods.Single(existingPeriod => existingPeriod.PeriodCode == periodCode);
        dbContext.Entry(period).Property(nameof(CashLineERP.Domain.Finance.AccountingPeriod.CloseStatusCode)).CurrentValue = lockStatusCode;
        dbContext.SaveChanges();
    }

    private static void UnlockPeriod(CashLineErpDbContext dbContext, string periodCode)
    {
        var period = dbContext.AccountingPeriods.Single(existingPeriod => existingPeriod.PeriodCode == periodCode);
        dbContext.Entry(period).Property(nameof(CashLineERP.Domain.Finance.AccountingPeriod.CloseStatusCode)).CurrentValue = "OPEN";
        dbContext.SaveChanges();
    }
}
