using CashLineERP.Application.Identity;
using CashLineERP.Application.Finance;
using CashLineERP.Application.Parties;
using CashLineERP.Contracts.Identity;
using CashLineERP.Contracts.External;
using CashLineERP.Contracts.Finance;
using CashLineERP.Contracts.Parties;
using CashLineERP.Contracts.Tax;
using Microsoft.Extensions.DependencyInjection;
using System.Net;
using System.Net.Http.Json;

namespace CashLineERP.Api.Tests;

public sealed class AccountingOfficeQaTests : IClassFixture<ApiTestFactory>
{
    private readonly ApiTestFactory _factory;

    public AccountingOfficeQaTests(ApiTestFactory factory)
    {
        _factory = factory;
    }

    [Fact]
    public void Accounting_Office_Assignment_And_Scoped_Workspace_Work_End_To_End()
    {
        using var scope = _factory.Services.CreateScope();
        var adminUserService = scope.ServiceProvider.GetRequiredService<IAdminUserService>();
        var accountingOfficeService = scope.ServiceProvider.GetRequiredService<IAccountingOfficeService>();

        var created = adminUserService.CreateUser(new CreateAdminManagedUserRequest(
            LoginName: "accounting.office.one@uat.cashline.test",
            DisplayName: "Accounting Office One",
            UserTypeCode: "INTERNAL",
            Password: "Accounting@12345",
            Email: "accounting.office.one@uat.cashline.test",
            BranchId: 1,
            PartyId: null,
            RoleCodes: new[] { "ACCOUNTING_OFFICE_USER" }),
            actorUserId: 6);

        Assert.Contains("ACCOUNTING_OFFICE_USER", created.RoleCodes);
        Assert.Contains("ACCOUNTING_OFFICE_CLIENT_ONBOARDING", created.FunctionCodes);

        var assigned = accountingOfficeService.AssignClient(created.UserId, new AssignAccountingOfficeClientRequest(2), actorUserId: 6);
        Assert.Equal(2, assigned.PartyId);
        Assert.Equal("CUSTOMER", assigned.PartyType);

        var currentClients = accountingOfficeService.GetCurrentUserClients(created.UserId);
        Assert.Contains(currentClients, entry => entry.PartyId == 2 && entry.PartyType == "CUSTOMER");

        var workspace = accountingOfficeService.GetCurrentUserClientWorkspace(created.UserId, 2);
        Assert.Equal(2, workspace.Client.PartyId);
        Assert.Equal("CUSTOMER", workspace.Client.PartyType);
        Assert.NotNull(workspace.StatementSummary);
        Assert.NotNull(workspace.ComplianceDocuments);

        accountingOfficeService.RemoveClient(created.UserId, 2, actorUserId: 6);
        Assert.DoesNotContain(accountingOfficeService.GetCurrentUserClients(created.UserId), entry => entry.PartyId == 2);
    }

    [Fact]
    public void Accounting_Office_Workspace_Rejects_Unassigned_Client_Access()
    {
        using var scope = _factory.Services.CreateScope();
        var adminUserService = scope.ServiceProvider.GetRequiredService<IAdminUserService>();
        var accountingOfficeService = scope.ServiceProvider.GetRequiredService<IAccountingOfficeService>();

        var created = adminUserService.CreateUser(new CreateAdminManagedUserRequest(
            LoginName: "accounting.office.two@uat.cashline.test",
            DisplayName: "Accounting Office Two",
            UserTypeCode: "INTERNAL",
            Password: "Accounting@12345",
            Email: "accounting.office.two@uat.cashline.test",
            BranchId: 1,
            PartyId: null,
            RoleCodes: new[] { "ACCOUNTING_OFFICE_SUPERVISOR" }),
            actorUserId: 6);

        var ex = Assert.Throws<InvalidOperationException>(() =>
            accountingOfficeService.GetCurrentUserClientWorkspace(created.UserId, 2));

        Assert.Contains("not assigned", ex.Message, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public void Accounting_Office_Delegated_Client_Actions_Are_Audited()
    {
        using var scope = _factory.Services.CreateScope();
        var adminUserService = scope.ServiceProvider.GetRequiredService<IAdminUserService>();
        var accountingOfficeService = scope.ServiceProvider.GetRequiredService<IAccountingOfficeService>();

        var created = adminUserService.CreateUser(new CreateAdminManagedUserRequest(
            LoginName: "accounting.office.audit@uat.cashline.test",
            DisplayName: "Accounting Office Audit",
            UserTypeCode: "INTERNAL",
            Password: "Accounting@12345",
            Email: "accounting.office.audit@uat.cashline.test",
            BranchId: 1,
            PartyId: null,
            RoleCodes: new[] { "ACCOUNTING_OFFICE_USER" }),
            actorUserId: 6);

        accountingOfficeService.AssignClient(created.UserId, new AssignAccountingOfficeClientRequest(2), actorUserId: 6);

        var workspace = accountingOfficeService.GetCurrentUserClientWorkspace(created.UserId, 2);
        Assert.Equal(2, workspace.Client.PartyId);

        var posture = accountingOfficeService.GetCurrentUserClientSubmissionPosture(created.UserId, 2);
        Assert.Equal(2, posture.Client.PartyId);
        Assert.False(string.IsNullOrWhiteSpace(posture.EtaReadinessCode));

        var export = accountingOfficeService.ExportCurrentUserClientReportCsv(created.UserId, 2);
        Assert.Contains("Metric,Value", export, StringComparison.Ordinal);

        var activity = accountingOfficeService.GetCurrentUserActivity(created.UserId, 2);
        Assert.Contains(activity, item => item.ActionCode == "VIEW_CLIENT_WORKSPACE");
        Assert.Contains(activity, item => item.ActionCode == "VIEW_SUBMISSION_POSTURE");
        Assert.Contains(activity, item => item.ActionCode == "EXPORT_CLIENT_REPORT");
        Assert.All(activity, item => Assert.Equal(2, item.TargetPartyId));
    }

    [Fact]
    public void Accounting_Office_Can_Upload_Client_Compliance_Document_With_Audit_Trail()
    {
        using var scope = _factory.Services.CreateScope();
        var adminUserService = scope.ServiceProvider.GetRequiredService<IAdminUserService>();
        var accountingOfficeService = scope.ServiceProvider.GetRequiredService<IAccountingOfficeService>();

        var created = adminUserService.CreateUser(new CreateAdminManagedUserRequest(
            LoginName: "accounting.office.docs@uat.cashline.test",
            DisplayName: "Accounting Office Docs",
            UserTypeCode: "INTERNAL",
            Password: "Accounting@12345",
            Email: "accounting.office.docs@uat.cashline.test",
            BranchId: 1,
            PartyId: null,
            RoleCodes: new[] { "ACCOUNTING_OFFICE_USER" }),
            actorUserId: 6);

        accountingOfficeService.AssignClient(created.UserId, new AssignAccountingOfficeClientRequest(2), actorUserId: 6);

        var document = accountingOfficeService.RecordCurrentUserClientComplianceDocument(
            created.UserId,
            2,
            new RecordBusinessPartyComplianceDocumentRequest(
                "COMMERCIAL_REGISTER",
                "client-commercial-register.pdf",
                "managed://accounting-office/client-commercial-register.pdf",
                "application/pdf",
                1024));

        Assert.Equal(2, document.PartyId);
        Assert.Equal("COMMERCIAL_REGISTER", document.DocumentType);

        var complianceDocuments = accountingOfficeService
            .GetCurrentUserClientWorkspace(created.UserId, 2)
            .ComplianceDocuments;
        Assert.Contains(complianceDocuments, item => item.DocumentLinkId == document.DocumentLinkId);

        var activity = accountingOfficeService.GetCurrentUserActivity(created.UserId, 2);
        Assert.Contains(activity, item => item.ActionCode == "UPLOAD_COMPLIANCE_DOCUMENT");
    }

    [Fact]
    public void Accounting_Office_Can_Submit_Assigned_Client_Invoice_To_Eta_With_Audit_Trail()
    {
        using var scope = _factory.Services.CreateScope();
        var adminUserService = scope.ServiceProvider.GetRequiredService<IAdminUserService>();
        var accountingOfficeService = scope.ServiceProvider.GetRequiredService<IAccountingOfficeService>();
        var invoiceService = scope.ServiceProvider.GetRequiredService<IAccountsReceivableInvoiceService>();

        var created = adminUserService.CreateUser(new CreateAdminManagedUserRequest(
            LoginName: "accounting.office.eta@uat.cashline.test",
            DisplayName: "Accounting Office ETA",
            UserTypeCode: "INTERNAL",
            Password: "Accounting@12345",
            Email: "accounting.office.eta@uat.cashline.test",
            BranchId: 1,
            PartyId: null,
            RoleCodes: new[] { "ACCOUNTING_OFFICE_USER" }),
            actorUserId: 6);

        accountingOfficeService.AssignClient(created.UserId, new AssignAccountingOfficeClientRequest(2), actorUserId: 6);

        var createdInvoice = invoiceService.CreateInvoice(new CreateAccountsReceivableInvoiceRequest(
            CustomerId: 2,
            SalesOrderId: 1,
            DispatchOrderId: 1,
            InvoiceType: "TAX",
            InvoiceDate: new DateOnly(2026, 5, 31),
            TaxDate: new DateOnly(2026, 5, 31),
            DueDate: new DateOnly(2026, 6, 10),
            CurrencyCode: "EGP",
            Lines:
            [
                new CreateAccountsReceivableInvoiceLineRequest(1, 1001, 2m, "EA", 32m, "VAT14")
            ]));
        var postedInvoice = AccountsReceivableWorkflowTestHelper.SubmitApproveAndPostInvoice(
            invoiceService,
            createdInvoice.AccountsReceivableInvoiceId);

        var workspace = accountingOfficeService.GetCurrentUserClientWorkspace(created.UserId, 2);
        var invoice = workspace.Invoices.SingleOrDefault(item => item.InvoiceId == postedInvoice.AccountsReceivableInvoiceId);
        Assert.NotNull(invoice);

        var validationPack = accountingOfficeService.GetCurrentUserClientInvoiceEtaValidationPack(created.UserId, 2, invoice!.InvoiceId);
        Assert.NotNull(validationPack);

        var submission = accountingOfficeService.SubmitCurrentUserClientInvoiceToEta(
            created.UserId,
            2,
            invoice.InvoiceId,
            new SubmitInvoiceToEtaRequest(0, "Accounting Office delegated ETA submission"));

        Assert.Equal(invoice.InvoiceId, submission.AccountsReceivableInvoiceId);
        Assert.False(string.IsNullOrWhiteSpace(submission.StatusCode));

        var logs = accountingOfficeService.GetCurrentUserClientInvoiceEtaSubmissionLogs(created.UserId, 2, invoice.InvoiceId);
        Assert.Contains(logs, item => item.EtaSubmissionLogId == submission.EtaSubmissionLogId);

        var activity = accountingOfficeService.GetCurrentUserActivity(created.UserId, 2);
        Assert.Contains(activity, item => item.ActionCode == "SUBMIT_ETA_INVOICE");
    }

    [Fact]
    public void Accounting_Office_Can_Record_Assigned_Client_Statement_Delivery_With_Audit_Trail()
    {
        using var scope = _factory.Services.CreateScope();
        var adminUserService = scope.ServiceProvider.GetRequiredService<IAdminUserService>();
        var accountingOfficeService = scope.ServiceProvider.GetRequiredService<IAccountingOfficeService>();
        var customerStatementService = scope.ServiceProvider.GetRequiredService<ICustomerStatementService>();

        var created = adminUserService.CreateUser(new CreateAdminManagedUserRequest(
            LoginName: "accounting.office.statement@uat.cashline.test",
            DisplayName: "Accounting Office Statement",
            UserTypeCode: "INTERNAL",
            Password: "Accounting@12345",
            Email: "accounting.office.statement@uat.cashline.test",
            BranchId: 1,
            PartyId: null,
            RoleCodes: new[] { "ACCOUNTING_OFFICE_USER" }),
            actorUserId: 6);

        accountingOfficeService.AssignClient(created.UserId, new AssignAccountingOfficeClientRequest(2), actorUserId: 6);

        var statement = customerStatementService.GenerateStatement(
            actorUserId: 6,
            new CreateCustomerStatementRequest(
                CustomerId: 2,
                DateFrom: new DateOnly(2026, 1, 1),
                DateTo: new DateOnly(2026, 12, 31),
                CurrencyCode: "EGP"));

        var statementDocuments = accountingOfficeService.GetCurrentUserClientStatementDocuments(created.UserId, 2);
        Assert.Contains(statementDocuments, item => item.StatementId == statement.CustomerStatementId);

        var deliveryLog = accountingOfficeService.RecordCurrentUserClientStatementDelivery(
            created.UserId,
            2,
            statement.CustomerStatementId,
            new RecordAccountingOfficeClientStatementDeliveryRequest(
                DeliveredAtUtc: DateTime.UtcNow,
                ChannelCode: "EMAIL",
                RecipientAddress: "client.finance@cashline.test",
                DeliveryStatusCode: "DELIVERED",
                Notes: "Delegated customer statement delivery from accounting-office lane."));

        Assert.Equal(statement.CustomerStatementId, deliveryLog.StatementId);
        Assert.Equal("CUSTOMER_STATEMENT", deliveryLog.StatementType);
        Assert.Equal("DELIVERED", deliveryLog.DeliveryStatusCode);

        var logs = accountingOfficeService.GetCurrentUserClientStatementDeliveryLogs(created.UserId, 2, statement.CustomerStatementId);
        Assert.Contains(logs, item => item.DeliveryLogId == deliveryLog.DeliveryLogId);

        var activity = accountingOfficeService.GetCurrentUserActivity(created.UserId, 2);
        Assert.Contains(activity, item => item.ActionCode == "RECORD_STATEMENT_DELIVERY");
    }

    [Fact]
    public void Accounting_Office_Can_Retry_Assigned_Client_Statement_Delivery_With_Audit_Trail()
    {
        using var scope = _factory.Services.CreateScope();
        var adminUserService = scope.ServiceProvider.GetRequiredService<IAdminUserService>();
        var accountingOfficeService = scope.ServiceProvider.GetRequiredService<IAccountingOfficeService>();
        var customerStatementService = scope.ServiceProvider.GetRequiredService<ICustomerStatementService>();

        var created = adminUserService.CreateUser(new CreateAdminManagedUserRequest(
            LoginName: "accounting.office.statement.retry@uat.cashline.test",
            DisplayName: "Accounting Office Statement Retry",
            UserTypeCode: "INTERNAL",
            Password: "Accounting@12345",
            Email: "accounting.office.statement.retry@uat.cashline.test",
            BranchId: 1,
            PartyId: null,
            RoleCodes: new[] { "ACCOUNTING_OFFICE_USER" }),
            actorUserId: 6);

        accountingOfficeService.AssignClient(created.UserId, new AssignAccountingOfficeClientRequest(2), actorUserId: 6);

        var statement = customerStatementService.GenerateStatement(
            actorUserId: 6,
            new CreateCustomerStatementRequest(
                CustomerId: 2,
                DateFrom: new DateOnly(2026, 1, 1),
                DateTo: new DateOnly(2026, 12, 31),
                CurrencyCode: "EGP"));

        var failedLog = accountingOfficeService.RecordCurrentUserClientStatementDelivery(
            created.UserId,
            2,
            statement.CustomerStatementId,
            new RecordAccountingOfficeClientStatementDeliveryRequest(
                DeliveredAtUtc: DateTime.UtcNow,
                ChannelCode: "EMAIL",
                RecipientAddress: "client.finance@cashline.test",
                DeliveryStatusCode: "FAILED",
                Notes: "Initial delivery attempt failed."));

        Assert.Equal("FAILED", failedLog.DeliveryStatusCode);

        var retryLog = accountingOfficeService.RetryCurrentUserClientStatementDelivery(
            created.UserId,
            2,
            statement.CustomerStatementId,
            failedLog.DeliveryLogId,
            new RetryAccountingOfficeClientStatementDeliveryRequest(
                ChannelCode: null,
                RecipientAddress: null,
                DeliveryStatusCode: "SENT",
                Notes: "Retry after initial failure."));

        Assert.Equal("CUSTOMER_STATEMENT", retryLog.StatementType);
        Assert.Equal(statement.CustomerStatementId, retryLog.StatementId);
        Assert.Equal("SENT", retryLog.DeliveryStatusCode);
        Assert.NotEqual(failedLog.DeliveryLogId, retryLog.DeliveryLogId);

        var logs = accountingOfficeService.GetCurrentUserClientStatementDeliveryLogs(created.UserId, 2, statement.CustomerStatementId);
        Assert.Contains(logs, item => item.DeliveryLogId == failedLog.DeliveryLogId && item.DeliveryStatusCode == "FAILED");
        Assert.Contains(logs, item => item.DeliveryLogId == retryLog.DeliveryLogId && item.DeliveryStatusCode == "SENT");

        var activity = accountingOfficeService.GetCurrentUserActivity(created.UserId, 2);
        Assert.Contains(activity, item => item.ActionCode == "RETRY_STATEMENT_DELIVERY");
    }

    [Fact]
    public void Accounting_Office_Can_Generate_Assigned_Client_Statement_With_Audit_Trail()
    {
        using var scope = _factory.Services.CreateScope();
        var adminUserService = scope.ServiceProvider.GetRequiredService<IAdminUserService>();
        var accountingOfficeService = scope.ServiceProvider.GetRequiredService<IAccountingOfficeService>();

        var created = adminUserService.CreateUser(new CreateAdminManagedUserRequest(
            LoginName: "accounting.office.statement.generate@uat.cashline.test",
            DisplayName: "Accounting Office Statement Generate",
            UserTypeCode: "INTERNAL",
            Password: "Accounting@12345",
            Email: "accounting.office.statement.generate@uat.cashline.test",
            BranchId: 1,
            PartyId: null,
            RoleCodes: new[] { "ACCOUNTING_OFFICE_USER" }),
            actorUserId: 6);

        accountingOfficeService.AssignClient(created.UserId, new AssignAccountingOfficeClientRequest(2), actorUserId: 6);

        var generated = accountingOfficeService.GenerateCurrentUserClientStatement(
            created.UserId,
            2,
            new GenerateAccountingOfficeClientStatementRequest(
                DateFrom: new DateOnly(2026, 1, 1),
                DateTo: new DateOnly(2026, 12, 31),
                CurrencyCode: "EGP"));

        Assert.Equal("CUSTOMER_STATEMENT", generated.StatementType);
        Assert.False(string.IsNullOrWhiteSpace(generated.StatementNumber));

        var documents = accountingOfficeService.GetCurrentUserClientStatementDocuments(created.UserId, 2);
        Assert.Contains(documents, item => item.StatementId == generated.StatementId && item.StatementType == "CUSTOMER_STATEMENT");

        var activity = accountingOfficeService.GetCurrentUserActivity(created.UserId, 2);
        Assert.Contains(activity, item => item.ActionCode == "GENERATE_STATEMENT");
    }

    [Fact]
    public void Accounting_Office_Can_Acknowledge_Assigned_Supplier_Statement_Delivery_With_Audit_Trail()
    {
        using var scope = _factory.Services.CreateScope();
        var adminUserService = scope.ServiceProvider.GetRequiredService<IAdminUserService>();
        var accountingOfficeService = scope.ServiceProvider.GetRequiredService<IAccountingOfficeService>();
        var supplierStatementService = scope.ServiceProvider.GetRequiredService<ISupplierStatementService>();
        var partyOnboardingService = scope.ServiceProvider.GetRequiredService<IPartyOnboardingService>();

        var supplierPartyId = partyOnboardingService.GetParties("SUPPLIER").Select(item => item.PartyId).First();

        var created = adminUserService.CreateUser(new CreateAdminManagedUserRequest(
            LoginName: "accounting.office.statement.ack@uat.cashline.test",
            DisplayName: "Accounting Office Statement Ack",
            UserTypeCode: "INTERNAL",
            Password: "Accounting@12345",
            Email: "accounting.office.statement.ack@uat.cashline.test",
            BranchId: 1,
            PartyId: null,
            RoleCodes: new[] { "ACCOUNTING_OFFICE_USER" }),
            actorUserId: 6);

        accountingOfficeService.AssignClient(created.UserId, new AssignAccountingOfficeClientRequest(supplierPartyId), actorUserId: 6);

        var statement = supplierStatementService.GenerateStatement(
            actorUserId: 6,
            new CreateSupplierStatementRequest(
                SupplierId: supplierPartyId,
                DateFrom: new DateOnly(2026, 1, 1),
                DateTo: new DateOnly(2026, 12, 31),
                CurrencyCode: "EGP"));

        var deliveryLog = accountingOfficeService.RecordCurrentUserClientStatementDelivery(
            created.UserId,
            supplierPartyId,
            statement.SupplierStatementId,
            new RecordAccountingOfficeClientStatementDeliveryRequest(
                DeliveredAtUtc: DateTime.UtcNow,
                ChannelCode: "EMAIL",
                RecipientAddress: "supplier.finance@cashline.test",
                DeliveryStatusCode: "DELIVERED",
                Notes: "Delegated supplier statement delivery from accounting-office lane."));

        var acknowledgedLog = accountingOfficeService.AcknowledgeCurrentUserClientSupplierStatementDelivery(
            created.UserId,
            supplierPartyId,
            statement.SupplierStatementId,
            deliveryLog.DeliveryLogId,
            new AcknowledgeAccountingOfficeClientStatementDeliveryRequest(
                AcknowledgmentStatusCode: "ACKNOWLEDGED",
                Notes: "Supplier recipient acknowledged delivery."));

        Assert.Equal("SUPPLIER_STATEMENT", acknowledgedLog.StatementType);
        Assert.Equal(deliveryLog.DeliveryLogId, acknowledgedLog.DeliveryLogId);
        Assert.Equal("ACKNOWLEDGED", acknowledgedLog.AcknowledgmentStatusCode);
        Assert.NotNull(acknowledgedLog.AcknowledgedAtUtc);
        Assert.NotNull(acknowledgedLog.AcknowledgedByUserId);

        var activity = accountingOfficeService.GetCurrentUserActivity(created.UserId, supplierPartyId);
        Assert.Contains(activity, item => item.ActionCode == "ACKNOWLEDGE_STATEMENT_DELIVERY");
    }

    [Fact]
    public async Task Accounting_Office_Onboarding_Is_Tagged_And_Scoped_To_Its_Submitter()
    {
        using var scope = _factory.Services.CreateScope();
        var adminUserService = scope.ServiceProvider.GetRequiredService<IAdminUserService>();

        var created = adminUserService.CreateUser(new CreateAdminManagedUserRequest(
            LoginName: "accounting.office.three@uat.cashline.test",
            DisplayName: "Accounting Office Three",
            UserTypeCode: "INTERNAL",
            Password: "Accounting@12345",
            Email: "accounting.office.three@uat.cashline.test",
            BranchId: 1,
            PartyId: null,
            RoleCodes: new[] { "ACCOUNTING_OFFICE_USER" }),
            actorUserId: 6);

        using var accountingOfficeClient = _factory.CreateClient();
        await accountingOfficeClient.AuthenticateAsync(created.LoginName, "Accounting@12345", "INTERNAL");

        var createResponse = await accountingOfficeClient.PostAsJsonAsync("/api/v1/internal/onboarding/customers", new CreateInternalCustomerOnboardingRequest(
            PartyName: "Accounting Office Managed Customer",
            CountryCode: "EG",
            FirmNationalId: null,
            TaxRegistrationNumber: "AO-CUST-001",
            TaxBranchCode: "001",
            Email: "ao-customer@example.com",
            MobileNumber: "+201000000901",
            AddressLine1: "New Cairo",
            CreditLimitAmount: 150000m,
            CreditTermsDays: 30,
            CustomerClassificationCode: "COMPANY",
            CustomerSegmentCode: "STANDARD",
            DefaultCurrencyCode: "EGP",
            SalesChannelCode: "DIRECT",
            PriceTierCode: "STANDARD",
            InvoiceDeliveryMethodCode: "EMAIL",
            Notes: "Accounting office onboarding QA"));
        Assert.Equal(HttpStatusCode.OK, createResponse.StatusCode);

        var createdCase = await createResponse.Content.ReadFromJsonAsync<PublicCustomerOnboardingApplicationResponse>();
        Assert.NotNull(createdCase);
        Assert.Contains("accounting office", createdCase!.ReviewStatusMessage, StringComparison.OrdinalIgnoreCase);
        Assert.Contains("-AO-", createdCase.PartyCode, StringComparison.OrdinalIgnoreCase);

        var scopedListResponse = await accountingOfficeClient.GetAsync("/api/v1/internal/onboarding/cases?subjectTypeCode=CUSTOMER");
        Assert.Equal(HttpStatusCode.OK, scopedListResponse.StatusCode);

        var scopedCases = await scopedListResponse.Content.ReadFromJsonAsync<IReadOnlyCollection<OnboardingCaseSummaryResponse>>();
        Assert.NotNull(scopedCases);

        var onboardingCase = scopedCases!.Single(item => item.SubjectProfileId == createdCase.CustomerProfileId);
        Assert.Equal("ACCOUNTING_OFFICE_MANAGED", onboardingCase.InitiationPathCode);
        Assert.Equal("ACCOUNTING_OFFICE_SUBMISSION", onboardingCase.SubmissionSourceCode);
        Assert.Equal(created.LoginName, onboardingCase.SubmittedByLoginName);
        Assert.Equal(created.DisplayName, onboardingCase.SubmittedByDisplayName);

        var scopedDetailResponse = await accountingOfficeClient.GetAsync($"/api/v1/internal/onboarding/cases/CUSTOMER/{createdCase.CustomerProfileId}");
        Assert.Equal(HttpStatusCode.OK, scopedDetailResponse.StatusCode);

        var scopedDetail = await scopedDetailResponse.Content.ReadFromJsonAsync<OnboardingCaseDetailResponse>();
        Assert.NotNull(scopedDetail);
        Assert.Equal("ACCOUNTING_OFFICE_SUBMISSION", scopedDetail!.SubmissionSourceCode);

        await accountingOfficeClient.AuthenticateAsInternalAsync();

        var operationsListResponse = await accountingOfficeClient.GetAsync("/api/v1/internal/onboarding/cases?subjectTypeCode=CUSTOMER");
        Assert.Equal(HttpStatusCode.OK, operationsListResponse.StatusCode);

        var operationsCases = await operationsListResponse.Content.ReadFromJsonAsync<IReadOnlyCollection<OnboardingCaseSummaryResponse>>();
        Assert.NotNull(operationsCases);
        Assert.Contains(operationsCases!, item => item.SubjectProfileId == createdCase.CustomerProfileId && item.SubmissionSourceCode == "ACCOUNTING_OFFICE_SUBMISSION");
    }

    [Fact]
    public async Task System_Owner_Can_Use_Iam_Parties_But_Is_Blocked_From_Non_Iam_Parties()
    {
        using var scope = _factory.Services.CreateScope();
        var adminUserService = scope.ServiceProvider.GetRequiredService<IAdminUserService>();

        var created = adminUserService.CreateUser(new CreateAdminManagedUserRequest(
            LoginName: "system.owner.qa@uat.cashline.test",
            DisplayName: "System Owner QA",
            UserTypeCode: "INTERNAL",
            Password: "SystemOwner@12345",
            Email: "system.owner.qa@uat.cashline.test",
            BranchId: 1,
            PartyId: null,
            RoleCodes: new[] { "SYSTEM_OWNER" }),
            actorUserId: 6);

        using var client = _factory.CreateClient();
        await client.AuthenticateAsync(created.LoginName, "SystemOwner@12345", "INTERNAL");

        var blocked = await client.GetAsync("/api/v1/parties");
        Assert.Equal(HttpStatusCode.Forbidden, blocked.StatusCode);

        var allowed = await client.GetAsync("/api/v1/iam/parties?partyType=CUSTOMER");
        Assert.Equal(HttpStatusCode.OK, allowed.StatusCode);
        var payload = await allowed.Content.ReadFromJsonAsync<IReadOnlyCollection<BusinessPartyResponse>>();
        Assert.NotNull(payload);
        Assert.All(payload!, item => Assert.Equal("CUSTOMER", item.PartyType));
    }

    [Fact]
    public async Task Accounting_Office_Is_Blocked_From_Internal_Workbench_Apis_But_Can_Access_Internal_Onboarding()
    {
        using var scope = _factory.Services.CreateScope();
        var adminUserService = scope.ServiceProvider.GetRequiredService<IAdminUserService>();
        var accountingOfficeService = scope.ServiceProvider.GetRequiredService<IAccountingOfficeService>();

        var created = adminUserService.CreateUser(new CreateAdminManagedUserRequest(
            LoginName: "accounting.office.boundary@uat.cashline.test",
            DisplayName: "Accounting Office Boundary",
            UserTypeCode: "INTERNAL",
            Password: "Accounting@12345",
            Email: "accounting.office.boundary@uat.cashline.test",
            BranchId: 1,
            PartyId: null,
            RoleCodes: new[] { "ACCOUNTING_OFFICE_USER" }),
            actorUserId: 6);

        accountingOfficeService.AssignClient(created.UserId, new AssignAccountingOfficeClientRequest(2), actorUserId: 6);

        using var client = _factory.CreateClient();
        await client.AuthenticateAsync(created.LoginName, "Accounting@12345", "INTERNAL");

        var internalMonitoring = await client.GetAsync("/api/v1/internal/workflow-monitoring?itemLimit=2");
        Assert.Equal(HttpStatusCode.Forbidden, internalMonitoring.StatusCode);

        var internalApprovals = await client.GetAsync("/api/v1/internal/work-queues/approvals?moduleCode=FINANCE");
        Assert.Equal(HttpStatusCode.Forbidden, internalApprovals.StatusCode);

        var internalExceptions = await client.GetAsync("/api/v1/internal/work-queues/exceptions");
        Assert.Equal(HttpStatusCode.Forbidden, internalExceptions.StatusCode);

        var internalDashboard = await client.GetAsync("/api/v1/internal/dashboard");
        Assert.Equal(HttpStatusCode.Forbidden, internalDashboard.StatusCode);

        var onboardingCases = await client.GetAsync("/api/v1/internal/onboarding/cases?subjectTypeCode=CUSTOMER");
        Assert.Equal(HttpStatusCode.OK, onboardingCases.StatusCode);
    }

    [Fact]
    public async Task System_Owner_Report_Export_Is_Limited_To_Administrators_And_Accounting_Office_Users()
    {
        using var scope = _factory.Services.CreateScope();
        var adminUserService = scope.ServiceProvider.GetRequiredService<IAdminUserService>();

        var nonManagedRole = adminUserService.GetRoleFunctionOptions("INTERNAL")
            .Select(option => option.RoleCode)
            .FirstOrDefault(roleCode =>
                !string.Equals(roleCode, "SUPER_ADMIN", StringComparison.OrdinalIgnoreCase)
                && !string.Equals(roleCode, "ACCOUNTING_OFFICE_USER", StringComparison.OrdinalIgnoreCase)
                && !string.Equals(roleCode, "ACCOUNTING_OFFICE_SUPERVISOR", StringComparison.OrdinalIgnoreCase)
                && !string.Equals(roleCode, "SYSTEM_OWNER", StringComparison.OrdinalIgnoreCase));
        Assert.False(string.IsNullOrWhiteSpace(nonManagedRole));

        var operationsUser = adminUserService.CreateUser(new CreateAdminManagedUserRequest(
            LoginName: "ops.user.qa@uat.cashline.test",
            DisplayName: "Ops QA User",
            UserTypeCode: "INTERNAL",
            Password: "Ops@12345",
            Email: "ops.user.qa@uat.cashline.test",
            BranchId: 1,
            PartyId: null,
            RoleCodes: new[] { nonManagedRole! }),
            actorUserId: 6);

        var createdSystemOwner = adminUserService.CreateUser(new CreateAdminManagedUserRequest(
            LoginName: "system.owner.reports@uat.cashline.test",
            DisplayName: "System Owner Reports",
            UserTypeCode: "INTERNAL",
            Password: "SystemOwner@12345",
            Email: "system.owner.reports@uat.cashline.test",
            BranchId: 1,
            PartyId: null,
            RoleCodes: new[] { "SYSTEM_OWNER" }),
            actorUserId: 6);

        using var client = _factory.CreateClient();
        await client.AuthenticateAsync(createdSystemOwner.LoginName, "SystemOwner@12345", "INTERNAL");

        var export = await client.GetAsync("/api/v1/iam/reports/users/export?format=EXCEL");
        Assert.Equal(HttpStatusCode.OK, export.StatusCode);

        var content = await export.Content.ReadAsStringAsync();
        Assert.DoesNotContain(operationsUser.DisplayName, content, StringComparison.OrdinalIgnoreCase);
        Assert.DoesNotContain($"\t{nonManagedRole}\t", content, StringComparison.OrdinalIgnoreCase);
        Assert.DoesNotContain($"\t{nonManagedRole}\r", content, StringComparison.OrdinalIgnoreCase);
        Assert.DoesNotContain($"\t{nonManagedRole}\n", content, StringComparison.OrdinalIgnoreCase);
    }

    [Fact]
    public async Task System_Owner_Cannot_Run_User_Lifecycle_Actions()
    {
        using var scope = _factory.Services.CreateScope();
        var adminUserService = scope.ServiceProvider.GetRequiredService<IAdminUserService>();

        var nonManagedRole = adminUserService.GetRoleFunctionOptions("INTERNAL")
            .Select(option => option.RoleCode)
            .FirstOrDefault(roleCode =>
                !string.Equals(roleCode, "SUPER_ADMIN", StringComparison.OrdinalIgnoreCase)
                && !string.Equals(roleCode, "ACCOUNTING_OFFICE_USER", StringComparison.OrdinalIgnoreCase)
                && !string.Equals(roleCode, "ACCOUNTING_OFFICE_SUPERVISOR", StringComparison.OrdinalIgnoreCase)
                && !string.Equals(roleCode, "SYSTEM_OWNER", StringComparison.OrdinalIgnoreCase));
        Assert.False(string.IsNullOrWhiteSpace(nonManagedRole));

        var createdSystemOwner = adminUserService.CreateUser(new CreateAdminManagedUserRequest(
            LoginName: "system.owner.lifecycle@uat.cashline.test",
            DisplayName: "System Owner Lifecycle",
            UserTypeCode: "INTERNAL",
            Password: "SystemOwner@12345",
            Email: "system.owner.lifecycle@uat.cashline.test",
            BranchId: 1,
            PartyId: null,
            RoleCodes: new[] { "SYSTEM_OWNER" }),
            actorUserId: 6);

        var targetUser = adminUserService.CreateUser(new CreateAdminManagedUserRequest(
            LoginName: "ops.user.lifecycle@uat.cashline.test",
            DisplayName: "Ops Lifecycle",
            UserTypeCode: "INTERNAL",
            Password: "Ops@12345",
            Email: "ops.user.lifecycle@uat.cashline.test",
            BranchId: 1,
            PartyId: null,
            RoleCodes: new[] { nonManagedRole! }),
            actorUserId: 6);

        using var client = _factory.CreateClient();
        await client.AuthenticateAsync(createdSystemOwner.LoginName, "SystemOwner@12345", "INTERNAL");

        var response = await client.PostAsJsonAsync(
            $"/api/v1/iam/users/{targetUser.UserId}/suspend",
            new AdminUserLifecycleActionRequest("UAT scope"));
        Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode);
    }
}
