using CashLineERP.Application.Parties;
using CashLineERP.Application.Suppliers;
using CashLineERP.Application.Identity;
using CashLineERP.Application.Notifications;
using CashLineERP.Contracts.Parties;
using CashLineERP.Contracts.Suppliers;
using CashLineERP.Infrastructure.Persistence;
using CashLineERP.Infrastructure.Persistence.Repositories;

namespace CashLineERP.Api.Tests;

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

        var partyService = new PartyOnboardingService(new BusinessPartyRepository(dbContext));
        var supplierService = CreateSupplierService(dbContext);

        var supplierParty = CreateSupplierParty(partyService, "SUP-SVC-001", hasTaxRegistration: true);
        partyService.RecordComplianceDocument(
            actorUserId: 3,
            partyId: supplierParty.PartyId,
            new RecordBusinessPartyComplianceDocumentRequest(
                "TAX_CARD",
                "tax-card.pdf",
                "managed://supplier/tax-card.pdf",
                "application/pdf",
                2048));

        var created = supplierService.CreateSupplierProfile(new CreateSupplierProfileRequest(
            supplierParty.PartyId,
            null,
            "LOCAL_VENDOR",
            "EGP",
            "BANK_TRANSFER",
            30,
            7,
            1000m,
            "Banque Misr",
            "1234567890123456",
            "BMISEGCX",
            "Primary industrial supplier"));

        Assert.Equal("DRAFT", created.StatusCode);
        Assert.Equal("PENDING", created.ComplianceStatusCode);

        supplierService.SubmitSupplierProfile(created.SupplierProfileId, 3);
        supplierService.ApproveSupplierProfile(created.SupplierProfileId, 4);
        supplierService.VerifySupplierBank(created.SupplierProfileId, 4, new VerifySupplierBankRequest("BANK-CHECK-001"));
        supplierService.RecordPerformanceReview(created.SupplierProfileId, 4, new RecordSupplierPerformanceReviewRequest(
            "LOW",
            96m,
            98m,
            95m,
            "Strong first review"));
        var activated = supplierService.ActivateSupplierProfile(created.SupplierProfileId, 4);

        Assert.True(activated.IsActive);
        Assert.Equal("APPROVED", activated.StatusCode);
        Assert.Equal("APPROVED", activated.ComplianceStatusCode);
        Assert.Equal("VERIFIED", activated.BankVerificationStatusCode);
        Assert.Equal("LOW", activated.RiskRatingCode);
        Assert.Equal(96m, activated.PerformanceScore);

        var detail = supplierService.GetSupplierProfile(created.SupplierProfileId);
        Assert.NotNull(detail);
        Assert.Equal(1, detail!.ComplianceDocumentCount);

        var filtered = supplierService.GetSupplierProfiles(activeOnly: true, businessPartyId: supplierParty.PartyId);
        Assert.Single(filtered);
    }

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

        var partyService = new PartyOnboardingService(new BusinessPartyRepository(dbContext));
        var supplierService = CreateSupplierService(dbContext);

        var supplierParty = CreateSupplierParty(partyService, "SUP-SVC-002", hasTaxRegistration: false);

        var created = supplierService.CreateSupplierProfile(new CreateSupplierProfileRequest(
            supplierParty.PartyId,
            null,
            "IMPORT_VENDOR",
            "USD",
            "BANK_TRANSFER",
            45,
            14,
            null,
            "National Bank of Egypt",
            "9988776655443322",
            null,
            null));

        supplierService.SubmitSupplierProfile(created.SupplierProfileId, 3);

        var exception = Assert.Throws<InvalidOperationException>(() => supplierService.ApproveSupplierProfile(created.SupplierProfileId, 4));
        Assert.Contains("tax registration", exception.Message, StringComparison.OrdinalIgnoreCase);
    }

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

        var partyService = new PartyOnboardingService(new BusinessPartyRepository(dbContext));
        var supplierService = CreateSupplierService(dbContext);

        var supplierParty = CreateSupplierParty(partyService, "SUP-SVC-003", hasTaxRegistration: true);
        partyService.RecordComplianceDocument(
            actorUserId: 3,
            partyId: supplierParty.PartyId,
            new RecordBusinessPartyComplianceDocumentRequest(
                "BANK_LETTER",
                "bank-letter.pdf",
                "managed://supplier/bank-letter.pdf",
                "application/pdf",
                2048));

        var created = supplierService.CreateSupplierProfile(new CreateSupplierProfileRequest(
            supplierParty.PartyId,
            null,
            "LOCAL_VENDOR",
            "EGP",
            "BANK_TRANSFER",
            30,
            7,
            null,
            "Banque Misr",
            "1231231231231231",
            "BMISEGCX",
            null));

        supplierService.SubmitSupplierProfile(created.SupplierProfileId, 3);
        supplierService.ApproveSupplierProfile(created.SupplierProfileId, 4);

        var exception = Assert.Throws<InvalidOperationException>(() => supplierService.ActivateSupplierProfile(created.SupplierProfileId, 4));
        Assert.Contains("verified bank", exception.Message, StringComparison.OrdinalIgnoreCase);
    }

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

        var partyService = new PartyOnboardingService(new BusinessPartyRepository(dbContext));
        var supplierService = CreateSupplierService(dbContext);
        var reviewService = CreateOnboardingReviewService(dbContext);

        var supplierParty = CreateSupplierParty(partyService, "SUP-SVC-005", hasTaxRegistration: true);
        partyService.RecordComplianceDocument(
            actorUserId: 3,
            partyId: supplierParty.PartyId,
            new RecordBusinessPartyComplianceDocumentRequest(
                "COMMERCIAL_REGISTER",
                "commercial-register.pdf",
                "managed://supplier/commercial-register-five.pdf",
                "application/pdf",
                2048));

        var created = supplierService.CreateSupplierProfile(new CreateSupplierProfileRequest(
            supplierParty.PartyId,
            null,
            "LOCAL_VENDOR",
            "EGP",
            "BANK_TRANSFER",
            30,
            7,
            null,
            "Banque Misr",
            "1231231231231235",
            "BMISEGCX",
            null));

        supplierService.SubmitSupplierProfile(created.SupplierProfileId, 3);
        reviewService.UpsertSupplierChecklistItems(created.SupplierProfileId, 4,
        [
            new UpsertOnboardingReviewChecklistItemRequest(
                "BANK_CONFIRMATION",
                "Bank Confirmation",
                "MISSING",
                "PARTIAL",
                "Still pending.")
        ]);

        var exception = Assert.Throws<InvalidOperationException>(() => supplierService.ApproveSupplierProfile(created.SupplierProfileId, 4));
        Assert.Contains("checklist items", exception.Message, StringComparison.OrdinalIgnoreCase);
    }

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

        var partyService = new PartyOnboardingService(new BusinessPartyRepository(dbContext));
        var supplierService = CreateSupplierService(dbContext);

        var supplierParty = CreateSupplierParty(partyService, "SUP-SVC-006", hasTaxRegistration: true);

        var created = supplierService.CreateSupplierProfile(new CreateSupplierProfileRequest(
            supplierParty.PartyId,
            null,
            "LOCAL_VENDOR",
            "EGP",
            "BANK_TRANSFER",
            30,
            7,
            null,
            "Banque Misr",
            "1231231231231236",
            "BMISEGCX",
            null));

        supplierService.SubmitSupplierProfile(created.SupplierProfileId, 3);
        var held = supplierService.HoldSupplierProfile(created.SupplierProfileId, 4, new HoldSupplierProfileRequest("Awaiting legal clarification."));
        Assert.True(held.IsOnHold);
        Assert.Equal("Awaiting legal clarification.", held.HoldReason);
        Assert.Equal("SUBMITTED", held.StatusCode);

        var reopened = supplierService.ReopenSupplierProfile(created.SupplierProfileId, 4, new ReopenSupplierProfileRequest("Applicant resubmitted details."));
        Assert.False(reopened.IsOnHold);
        Assert.Equal("DRAFT", reopened.StatusCode);

        supplierService.SubmitSupplierProfile(created.SupplierProfileId, 3);
        var returned = supplierService.ReturnSupplierProfile(created.SupplierProfileId, 4, new ReviewSupplierProfileRequest("Missing supporting document."));
        Assert.Equal("RETURNED", returned.StatusCode);
        Assert.Equal("Missing supporting document.", returned.ReviewDecisionNotes);
    }

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

        var partyService = new PartyOnboardingService(new BusinessPartyRepository(dbContext));
        var supplierService = CreateSupplierService(dbContext);

        var supplierParty = CreateSupplierParty(partyService, "SUP-SVC-007", hasTaxRegistration: true);

        var created = supplierService.CreateSupplierProfile(new CreateSupplierProfileRequest(
            supplierParty.PartyId,
            null,
            "LOCAL_VENDOR",
            "EGP",
            "BANK_TRANSFER",
            30,
            7,
            null,
            "Banque Misr",
            "1231231231231237",
            "BMISEGCX",
            null));

        supplierService.SubmitSupplierProfile(created.SupplierProfileId, 3);
        var rejected = supplierService.RejectSupplierProfile(created.SupplierProfileId, 4, new ReviewSupplierProfileRequest("Sanctions review failed."));

        Assert.Equal("REJECTED", rejected.StatusCode);
        Assert.Equal("Sanctions review failed.", rejected.ReviewDecisionNotes);
    }

    private static BusinessPartyResponse CreateSupplierParty(PartyOnboardingService partyService, string partyCode, bool hasTaxRegistration)
    {
        return partyService.CreateParty(new CreateBusinessPartyRequest(
            partyCode,
            $"Supplier {partyCode}",
            "SUPPLIER",
            "EG",
            null,
            hasTaxRegistration ? $"TAX-{partyCode}" : null,
            "001",
            $"{partyCode.ToLowerInvariant()}@example.com",
            "+201000000099",
            "Cairo",
            null,
            null,
            false,
            null));
    }

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

        var partyService = new PartyOnboardingService(new BusinessPartyRepository(dbContext));
        var supplierService = CreateSupplierService(dbContext);

        var supplierParty = CreateSupplierParty(partyService, "SUP-SVC-004", hasTaxRegistration: true);
        partyService.RecordComplianceDocument(
            actorUserId: 3,
            partyId: supplierParty.PartyId,
            new RecordBusinessPartyComplianceDocumentRequest(
                "COMMERCIAL_REGISTER",
                "commercial-register.pdf",
                "managed://supplier/commercial-register.pdf",
                "application/pdf",
                4096));

        var created = supplierService.CreateSupplierProfile(new CreateSupplierProfileRequest(
            supplierParty.PartyId,
            null,
            "LOCAL_VENDOR",
            "EGP",
            "BANK_TRANSFER",
            30,
            7,
            null,
            "Banque Misr",
            "1111222233334444",
            "BMISEGCX",
            "Provision access on activation"));

        supplierService.SubmitSupplierProfile(created.SupplierProfileId, 3);
        supplierService.ApproveSupplierProfile(created.SupplierProfileId, 4);
        supplierService.VerifySupplierBank(created.SupplierProfileId, 4, new VerifySupplierBankRequest("BANK-CHECK-004"));
        supplierService.ActivateSupplierProfile(created.SupplierProfileId, 4);

        var provisionedUser = dbContext.Users.Single(entry => entry.PartyId == supplierParty.PartyId && entry.UserTypeCode == "EXTERNAL");
        Assert.StartsWith("SUP_", provisionedUser.LoginName, StringComparison.OrdinalIgnoreCase);

        var roleAssignment = (
            from userRole in dbContext.UserRoles
            join role in dbContext.Roles on userRole.RoleId equals role.Id
            where userRole.UserId == provisionedUser.Id && userRole.IsActive
            select role.RoleCode)
            .Single();
        Assert.Equal("SUPPLIER_ADMIN", roleAssignment);

        var queueItems = dbContext.NotificationQueueItems
            .Where(entry => entry.RelatedEntityType == "SUPPLIER" && entry.RelatedEntityId == supplierParty.PartyId)
            .OrderBy(entry => entry.Id)
            .ToArray();
        Assert.Equal(3, queueItems.Length);
        Assert.Contains(queueItems, item => item.ChannelCode == "EMAIL");
        Assert.Contains(queueItems, item => item.ChannelCode == "SMS");
        Assert.Contains(queueItems, item => item.ChannelCode == "WHATSAPP");
    }

    private static SupplierProfileService CreateSupplierService(CashLineErpDbContext dbContext)
    {
        var notificationTemplateService = new NotificationTemplateService(new NotificationTemplateRepository(dbContext));
        var notificationOrchestrationService = new NotificationOrchestrationService(new NotificationOrchestrationRepository(dbContext));
        var provisioningService = new OnboardingAccessProvisioningService(
            new OnboardingAccessProvisioningRepository(dbContext),
            notificationTemplateService,
            notificationOrchestrationService,
            new IdentityReadRepository(dbContext),
            new SupplierProfileRepository(dbContext),
            new CustomerProfileRepository(dbContext),
            new AccountingOfficeClientAssignmentRepository(dbContext));
        var reviewGateService = new OnboardingReviewApprovalGateService(new OnboardingReviewRepository(dbContext));
        return new SupplierProfileService(new SupplierProfileRepository(dbContext), provisioningService, reviewGateService);
    }

    private static OnboardingReviewService CreateOnboardingReviewService(CashLineErpDbContext dbContext) =>
        new(new OnboardingReviewRepository(dbContext));
}
