using CashLineERP.Contracts.Notifications;
using CashLineERP.Application.Notifications;
using CashLineERP.Application.Customers;
using CashLineERP.Application.Suppliers;

namespace CashLineERP.Application.Identity;

public sealed class OnboardingAccessProvisioningService : IOnboardingAccessProvisioningService
{
    private const string SupplierRoleCode = "SUPPLIER_ADMIN";
    private const string CustomerRoleCode = "CUSTOMER_ADMIN";
    private const string EmailTemplateCode = "PARTY_ONBOARDING_CREDENTIAL_EMAIL";
    private const string SmsTemplateCode = "PARTY_ONBOARDING_CREDENTIAL_SMS";
    private const string WhatsappTemplateCode = "PARTY_ONBOARDING_CREDENTIAL_WHATSAPP";

    private readonly IOnboardingAccessProvisioningRepository _repository;
    private readonly INotificationTemplateService _notificationTemplateService;
    private readonly INotificationOrchestrationService _notificationOrchestrationService;
    private readonly IIdentityReadRepository _identityReadRepository;
    private readonly ISupplierProfileRepository _supplierProfileRepository;
    private readonly ICustomerProfileRepository _customerProfileRepository;
    private readonly IAccountingOfficeClientAssignmentRepository _accountingOfficeClientAssignmentRepository;

    public OnboardingAccessProvisioningService(
        IOnboardingAccessProvisioningRepository repository,
        INotificationTemplateService notificationTemplateService,
        INotificationOrchestrationService notificationOrchestrationService,
        IIdentityReadRepository identityReadRepository,
        ISupplierProfileRepository supplierProfileRepository,
        ICustomerProfileRepository customerProfileRepository,
        IAccountingOfficeClientAssignmentRepository accountingOfficeClientAssignmentRepository)
    {
        _repository = repository;
        _notificationTemplateService = notificationTemplateService;
        _notificationOrchestrationService = notificationOrchestrationService;
        _identityReadRepository = identityReadRepository;
        _supplierProfileRepository = supplierProfileRepository;
        _customerProfileRepository = customerProfileRepository;
        _accountingOfficeClientAssignmentRepository = accountingOfficeClientAssignmentRepository;
    }

    public void ProvisionSupplierAccess(long partyId, long actorUserId)
    {
        var result = _repository.EnsureExternalPartyAccess(partyId, "SUPPLIER", SupplierRoleCode, actorUserId);
        TryAssignDelegatedAccountingOfficeClient(partyId, "SUPPLIER", actorUserId);
        EnqueueCredentialNotifications(actorUserId, result, "supplier");
    }

    public void ProvisionCustomerAccess(long partyId, long actorUserId)
    {
        var result = _repository.EnsureExternalPartyAccess(partyId, "CUSTOMER", CustomerRoleCode, actorUserId);
        TryAssignDelegatedAccountingOfficeClient(partyId, "CUSTOMER", actorUserId);
        EnqueueCredentialNotifications(actorUserId, result, "customer");
    }

    private void TryAssignDelegatedAccountingOfficeClient(long partyId, string partyType, long actorUserId)
    {
        long? submittedByUserId = null;
        if (string.Equals(partyType, "SUPPLIER", StringComparison.OrdinalIgnoreCase))
        {
            submittedByUserId = _supplierProfileRepository
                .GetSupplierProfiles(activeOnly: false, businessPartyId: partyId)
                .OrderByDescending(item => item.SubmittedAtUtc ?? DateTime.MinValue)
                .ThenByDescending(item => item.SupplierProfileId)
                .FirstOrDefault()
                ?.SubmittedByUserId;
        }
        else if (string.Equals(partyType, "CUSTOMER", StringComparison.OrdinalIgnoreCase))
        {
            submittedByUserId = _customerProfileRepository
                .GetCustomerProfiles(activeOnly: false, businessPartyId: partyId)
                .OrderByDescending(item => item.SubmittedAtUtc ?? DateTime.MinValue)
                .ThenByDescending(item => item.CustomerProfileId)
                .FirstOrDefault()
                ?.SubmittedByUserId;
        }

        if (submittedByUserId is null || submittedByUserId <= 0)
        {
            return;
        }

        var submitter = _identityReadRepository.FindByUserId(submittedByUserId.Value);
        var isAccountingOfficeSubmitter = submitter?.RoleCodes.Any(roleCode =>
            string.Equals(roleCode, "ACCOUNTING_OFFICE_USER", StringComparison.OrdinalIgnoreCase)
            || string.Equals(roleCode, "ACCOUNTING_OFFICE_SUPERVISOR", StringComparison.OrdinalIgnoreCase)) == true;

        if (!isAccountingOfficeSubmitter)
        {
            return;
        }

        // Make the delegated client appear inside the submitter's Accounting Office workspace after Operations activation.
        _accountingOfficeClientAssignmentRepository.Assign(submittedByUserId.Value, partyId, actorUserId);
    }

    private void EnqueueCredentialNotifications(long actorUserId, OnboardingAccessProvisioningResult result, string workspaceCode)
    {
        if (!result.WasCreated || string.IsNullOrWhiteSpace(result.TemporaryPassword))
        {
            return;
        }

        var variables = new Dictionary<string, string?>
        {
            ["partyId"] = result.PartyId.ToString(),
            ["partyCode"] = result.PartyCode,
            ["partyName"] = result.PartyName,
            ["partyType"] = result.PartyType,
            ["username"] = result.Username,
            ["temporaryPassword"] = result.TemporaryPassword,
            ["workspace"] = workspaceCode,
            ["loginUrl"] = "/"
        };

        EnqueueByTemplateCode(actorUserId, EmailTemplateCode, result.Email, result, variables);
        EnqueueByTemplateCode(actorUserId, SmsTemplateCode, result.MobileNumber, result, variables);
        EnqueueByTemplateCode(actorUserId, WhatsappTemplateCode, result.MobileNumber, result, variables);
    }

    private void EnqueueByTemplateCode(
        long actorUserId,
        string templateCode,
        string? recipientReference,
        OnboardingAccessProvisioningResult result,
        Dictionary<string, string?> variables)
    {
        if (string.IsNullOrWhiteSpace(recipientReference))
        {
            return;
        }

        var template = _notificationTemplateService
            .GetTemplates(activeOnly: true)
            .SingleOrDefault(item => string.Equals(item.TemplateCode, templateCode, StringComparison.OrdinalIgnoreCase));

        if (template is null)
        {
            return;
        }

        _notificationOrchestrationService.EnqueueNotification(actorUserId, new EnqueueNotificationRequest(
            template.NotificationTemplateId,
            recipientReference,
            "PARTY_ONBOARDING",
            result.PartyType,
            result.PartyId,
            variables));
    }
}
