using CashLineERP.Application.Customers;
using CashLineERP.Domain.Audit;
using CashLineERP.Domain.Customers;
using CashLineERP.Domain.Documents;
using CashLineERP.Domain.Parties;
using CashLineERP.Domain.Workflow;
using CashLineERP.SharedKernel.Documents;
using CashLineERP.SharedKernel.Workflow;
using Microsoft.EntityFrameworkCore;

namespace CashLineERP.Infrastructure.Persistence.Repositories;

public sealed class CustomerProfileRepository : ICustomerProfileRepository
{
    private readonly CashLineErpDbContext _dbContext;

    public CustomerProfileRepository(CashLineErpDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IReadOnlyCollection<CustomerProfileDetailView> GetCustomerProfiles(bool activeOnly = false, string? statusCode = null, long? businessPartyId = null, string? classificationCode = null)
    {
        long? normalizedStatusId = string.IsNullOrWhiteSpace(statusCode) ? null : MapStatusCodeToId(statusCode.Trim().ToUpperInvariant());
        var normalizedClassificationCode = string.IsNullOrWhiteSpace(classificationCode) ? null : classificationCode.Trim().ToUpperInvariant();

        var profiles = _dbContext.CustomerProfiles
            .AsNoTracking()
            .Where(entry => !activeOnly || entry.IsActive)
            .Where(entry => !normalizedStatusId.HasValue || entry.StatusId == normalizedStatusId.Value)
            .Where(entry => !businessPartyId.HasValue || entry.BusinessPartyId == businessPartyId.Value)
            .Where(entry => normalizedClassificationCode == null || entry.CustomerClassificationCode == normalizedClassificationCode)
            .OrderBy(entry => entry.CustomerProfileCode)
            .ToArray();

        return BuildDetailViews(profiles);
    }

    public CustomerProfileDetailView? GetCustomerProfile(long customerProfileId)
    {
        var profile = _dbContext.CustomerProfiles
            .AsNoTracking()
            .SingleOrDefault(entry => entry.Id == customerProfileId);

        return profile is null ? null : BuildDetailViews([profile]).Single();
    }

    public CustomerProfileDetailView Create(CreateCustomerProfileCommand command)
    {
        var party = EnsureCustomerPartyActive(command.BusinessPartyId);

        if (!string.IsNullOrWhiteSpace(command.FirmNationalId))
        {
            var normalized = NormalizeFirmNationalId(command.FirmNationalId);
            if (normalized is not null)
            {
                var nationalIdDuplicateExists = _dbContext.BusinessParties
                    .AsNoTracking()
                    .Where(entry => entry.IsActive && entry.Id != party.Id)
                    .Any(entry => entry.FirmNationalId != null && NormalizeFirmNationalId(entry.FirmNationalId) == normalized);

                if (nationalIdDuplicateExists)
                {
                    throw new InvalidOperationException("A client already exists with the same firm national ID (Egypt). Duplicate registration is not allowed.");
                }

                if (string.IsNullOrWhiteSpace(party.FirmNationalId))
                {
                    party.SetFirmNationalId(normalized);
                    _dbContext.SaveChanges();
                }
            }
        }

        var profileDuplicateExists = _dbContext.CustomerProfiles
            .AsNoTracking()
            .Any(entry => entry.BusinessPartyId == command.BusinessPartyId);
        if (profileDuplicateExists)
        {
            throw new InvalidOperationException("Customer profile already exists for the selected customer party.");
        }

        var nextSequence = (_dbContext.CustomerProfiles.Max(entry => (long?)entry.Id) ?? 0L) + 1L;
        var profile = new CustomerProfile(
            customerProfileCode: $"CUSR-{nextSequence:D6}",
            businessPartyId: party.Id,
            customerClassificationCode: command.CustomerClassificationCode,
            customerSegmentCode: command.CustomerSegmentCode,
            defaultCurrencyCode: command.DefaultCurrencyCode,
            salesChannelCode: command.SalesChannelCode,
            priceTierCode: command.PriceTierCode,
            invoiceDeliveryMethodCode: command.InvoiceDeliveryMethodCode,
            notes: command.Notes);

        _dbContext.CustomerProfiles.Add(profile);
        _dbContext.SaveChanges();
        return GetCustomerProfile(profile.Id)!;
    }

    private static string? NormalizeFirmNationalId(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return null;
        }

        var buffer = new List<char>(value.Length);
        foreach (var ch in value.Trim())
        {
            if (ch >= '0' && ch <= '9')
            {
                buffer.Add(ch);
                continue;
            }

            if (ch >= '\u0660' && ch <= '\u0669')
            {
                buffer.Add((char)('0' + (ch - '\u0660')));
                continue;
            }

            if (ch >= '\u06F0' && ch <= '\u06F9')
            {
                buffer.Add((char)('0' + (ch - '\u06F0')));
                continue;
            }
        }

        return buffer.Count == 0 ? null : new string(buffer.ToArray());
    }

    public CustomerProfileDetailView Submit(long customerProfileId, long actorUserId)
    {
        var profile = _dbContext.CustomerProfiles.Single(entry => entry.Id == customerProfileId);
        _ = EnsureCustomerPartyActive(profile.BusinessPartyId);
        var oldStatusCode = WorkflowStatusSeedIds.ToCode(profile.StatusId);

        profile.Submit(actorUserId);

        var workflowInstance = new WorkflowInstance(
            moduleCode: "PARTY",
            documentType: "CUSTOMER_PROFILE",
            documentId: profile.Id,
            makerUserId: actorUserId,
            workflowRuleId: 1L);
        workflowInstance.Submit(actorUserId, "Customer profile submitted for controlled review.");
        AddAuditEvent(profile.Id, actorUserId, "SUBMITTED", oldStatusCode, WorkflowStatusSeedIds.ToCode(profile.StatusId));

        _dbContext.WorkflowInstances.Add(workflowInstance);
        _dbContext.SaveChanges();
        return GetCustomerProfile(profile.Id)!;
    }

    public CustomerProfileDetailView Approve(long customerProfileId, long actorUserId)
    {
        var profile = _dbContext.CustomerProfiles.Single(entry => entry.Id == customerProfileId);
        EnsureActivationPrerequisites(profile.BusinessPartyId);
        var oldStatusCode = WorkflowStatusSeedIds.ToCode(profile.StatusId);

        profile.Approve(actorUserId);

        var workflowInstance = ResolveLatestWorkflowInstance(customerProfileId);
        workflowInstance?.Approve(actorUserId, "Customer profile approved after classification and tax review.");
        AddAuditEvent(profile.Id, actorUserId, "APPROVED", oldStatusCode, WorkflowStatusSeedIds.ToCode(profile.StatusId));

        _dbContext.SaveChanges();
        return GetCustomerProfile(profile.Id)!;
    }

    public CustomerProfileDetailView Return(long customerProfileId, long actorUserId, string reviewNotes)
    {
        var profile = _dbContext.CustomerProfiles.Single(entry => entry.Id == customerProfileId);
        _ = EnsureCustomerPartyActive(profile.BusinessPartyId);
        var oldStatusCode = WorkflowStatusSeedIds.ToCode(profile.StatusId);

        profile.Return(actorUserId, reviewNotes);

        var workflowInstance = ResolveLatestWorkflowInstance(customerProfileId);
        workflowInstance?.Return(actorUserId, reviewNotes);
        AddAuditEvent(profile.Id, actorUserId, "RETURNED", oldStatusCode, $"{WorkflowStatusSeedIds.ToCode(profile.StatusId)}|{reviewNotes}");

        _dbContext.SaveChanges();
        return GetCustomerProfile(profile.Id)!;
    }

    public CustomerProfileDetailView Reject(long customerProfileId, long actorUserId, string reviewNotes)
    {
        var profile = _dbContext.CustomerProfiles.Single(entry => entry.Id == customerProfileId);
        _ = EnsureCustomerPartyActive(profile.BusinessPartyId);
        var oldStatusCode = WorkflowStatusSeedIds.ToCode(profile.StatusId);

        profile.Reject(actorUserId, reviewNotes);

        var workflowInstance = ResolveLatestWorkflowInstance(customerProfileId);
        workflowInstance?.Reject(actorUserId, reviewNotes);
        AddAuditEvent(profile.Id, actorUserId, "REJECTED", oldStatusCode, $"{WorkflowStatusSeedIds.ToCode(profile.StatusId)}|{reviewNotes}");

        _dbContext.SaveChanges();
        return GetCustomerProfile(profile.Id)!;
    }

    public CustomerProfileDetailView Hold(long customerProfileId, long actorUserId, string holdReason)
    {
        var profile = _dbContext.CustomerProfiles.Single(entry => entry.Id == customerProfileId);
        _ = EnsureCustomerPartyActive(profile.BusinessPartyId);
        var oldValue = $"{WorkflowStatusSeedIds.ToCode(profile.StatusId)}|{profile.HoldReason}";

        profile.Hold(actorUserId, holdReason);

        var workflowInstance = ResolveLatestWorkflowInstance(customerProfileId);
        workflowInstance?.Hold(actorUserId, holdReason);
        AddAuditEvent(profile.Id, actorUserId, "ON_HOLD", oldValue, $"{WorkflowStatusSeedIds.ToCode(profile.StatusId)}|{profile.HoldReason}");

        _dbContext.SaveChanges();
        return GetCustomerProfile(profile.Id)!;
    }

    public CustomerProfileDetailView Reopen(long customerProfileId, long actorUserId, string reopenReason)
    {
        var profile = _dbContext.CustomerProfiles.Single(entry => entry.Id == customerProfileId);
        _ = EnsureCustomerPartyActive(profile.BusinessPartyId);
        var oldValue = $"{WorkflowStatusSeedIds.ToCode(profile.StatusId)}|{profile.HoldReason}";

        profile.Reopen(actorUserId, reopenReason);

        var workflowInstance = ResolveLatestWorkflowInstance(customerProfileId);
        workflowInstance?.Reopen(actorUserId, reopenReason);
        AddAuditEvent(profile.Id, actorUserId, "REOPENED", oldValue, $"{WorkflowStatusSeedIds.ToCode(profile.StatusId)}|{reopenReason}");

        _dbContext.SaveChanges();
        return GetCustomerProfile(profile.Id)!;
    }

    public CustomerProfileDetailView Activate(long customerProfileId, long actorUserId)
    {
        var profile = _dbContext.CustomerProfiles.Single(entry => entry.Id == customerProfileId);
        var party = EnsureActivationPrerequisites(profile.BusinessPartyId);
        var oldStatusCode = WorkflowStatusSeedIds.ToCode(profile.StatusId);

        profile.Activate(actorUserId);

        OutboxEventWriter.Enqueue(
            _dbContext,
            sourceModuleCode: "PARTY",
            aggregateType: "CUSTOMER_PROFILE",
            aggregateId: profile.Id,
            eventType: "CUSTOMER_PROFILE_ACTIVATED",
            payload: new
            {
                profile.CustomerProfileCode,
                profile.BusinessPartyId,
                party.PartyCode,
                party.PartyName,
                profile.CustomerClassificationCode,
                profile.CustomerSegmentCode,
                profile.PriceTierCode
            },
            actorUserId: actorUserId,
            correlationKey: profile.CustomerProfileCode);
        AddAuditEvent(profile.Id, actorUserId, "ACTIVATED", oldStatusCode, $"ACTIVE|{WorkflowStatusSeedIds.ToCode(profile.StatusId)}");

        _dbContext.SaveChanges();
        return GetCustomerProfile(profile.Id)!;
    }

    private WorkflowInstance? ResolveLatestWorkflowInstance(long customerProfileId) =>
        _dbContext.WorkflowInstances
            .Include(entry => entry.Actions)
            .Where(entry => entry.DocumentType == "CUSTOMER_PROFILE" && entry.DocumentId == customerProfileId)
            .OrderByDescending(entry => entry.Id)
            .FirstOrDefault();

    private IReadOnlyCollection<CustomerProfileDetailView> BuildDetailViews(IReadOnlyCollection<CustomerProfile> profiles)
    {
        if (profiles.Count == 0)
        {
            return [];
        }

        var businessPartyIds = profiles
            .Select(entry => entry.BusinessPartyId)
            .Distinct()
            .ToList();

        var partiesById = _dbContext.BusinessParties
            .AsNoTracking()
            .Where(entry => businessPartyIds.Contains(entry.Id))
            .ToDictionary(entry => entry.Id);

        var complianceCountsByPartyId = _dbContext.DocumentLinks
            .AsNoTracking()
            .Where(link =>
                businessPartyIds.Contains(link.EntityId)
                && link.ModuleCode == "PARTY"
                && link.EntityName == "BUSINESS_PARTY"
                && link.LifecycleStatusCode == DocumentLifecycleStatusCode.Active)
            .ToArray()
            .GroupBy(link => link.EntityId)
            .ToDictionary(group => group.Key, group => group.Count());

        return profiles
            .Select(entry =>
            {
                var party = partiesById[entry.BusinessPartyId];
                complianceCountsByPartyId.TryGetValue(entry.BusinessPartyId, out var complianceCount);

                return new CustomerProfileDetailView(
                    entry.Id,
                    entry.CustomerProfileCode,
                    entry.BusinessPartyId,
                    party.PartyCode,
                    party.PartyName,
                    party.CountryCode,
                    party.TaxRegistrationNumber,
                    party.CreditLimitAmount,
                    party.CreditTermsDays,
                    party.IsCreditHoldEnabled,
                    party.CreditHoldReason,
                    entry.CustomerClassificationCode,
                    entry.CustomerSegmentCode,
                    entry.DefaultCurrencyCode,
                    entry.SalesChannelCode,
                    entry.PriceTierCode,
                    entry.InvoiceDeliveryMethodCode,
                    entry.TaxProfileStatusCode,
                    complianceCount,
                    WorkflowStatusSeedIds.ToCode(entry.StatusId),
                    entry.IsActive,
                    entry.IsOnHold,
                    entry.HoldReason,
                    entry.ReviewDecisionNotes,
                    entry.Notes,
                    entry.SubmittedAtUtc,
                    entry.SubmittedByUserId,
                    entry.ApprovedAtUtc,
                    entry.ApprovedByUserId,
                    entry.ActivatedAtUtc,
                    entry.ActivatedByUserId);
            })
            .ToArray();
    }

    private BusinessParty EnsureCustomerPartyActive(long businessPartyId)
    {
        var party = _dbContext.BusinessParties.SingleOrDefault(entry => entry.Id == businessPartyId)
            ?? throw new InvalidOperationException("Customer profile requires a valid customer business party.");

        if (!party.IsActive)
        {
            throw new InvalidOperationException("Customer profile requires an active customer business party.");
        }

        if (!string.Equals(party.PartyType, "CUSTOMER", StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("Customer profile can only be created for parties of type CUSTOMER.");
        }

        return party;
    }

    private BusinessParty EnsureActivationPrerequisites(long businessPartyId)
    {
        var party = EnsureCustomerPartyActive(businessPartyId);

        if (string.IsNullOrWhiteSpace(party.TaxRegistrationNumber))
        {
            throw new InvalidOperationException("Customer approval requires customer tax registration data.");
        }

        var hasComplianceDocument = _dbContext.DocumentLinks
            .AsNoTracking()
            .Any(link =>
                link.ModuleCode == "PARTY"
                && link.EntityName == "BUSINESS_PARTY"
                && link.EntityId == businessPartyId
                && link.LifecycleStatusCode == DocumentLifecycleStatusCode.Active);

        if (!hasComplianceDocument)
        {
            throw new InvalidOperationException("Customer approval requires at least one active compliance document.");
        }

        return party;
    }

    private static long MapStatusCodeToId(string statusCode) => statusCode switch
    {
        WorkflowStatusCode.Draft => WorkflowStatusSeedIds.Draft,
        WorkflowStatusCode.Submitted => WorkflowStatusSeedIds.Submitted,
        WorkflowStatusCode.Returned => WorkflowStatusSeedIds.Returned,
        WorkflowStatusCode.Approved => WorkflowStatusSeedIds.Approved,
        WorkflowStatusCode.Rejected => WorkflowStatusSeedIds.Rejected,
        WorkflowStatusCode.Closed => WorkflowStatusSeedIds.Closed,
        WorkflowStatusCode.Cancelled => WorkflowStatusSeedIds.Cancelled,
        _ => throw new InvalidOperationException($"Customer status code '{statusCode}' is not supported.")
    };

    private void AddAuditEvent(long customerProfileId, long actorUserId, string actionCode, string? oldValue, string? newValue)
    {
        _dbContext.AuditEvents.Add(new AuditEvent(
            moduleCode: "PARTY",
            entityName: "CUSTOMER_PROFILE",
            entityId: customerProfileId,
            actionCode: actionCode,
            actorUserId: actorUserId,
            oldValue: oldValue,
            newValue: newValue));
    }
}
