using CashLineERP.Application.Integration;
using CashLineERP.Domain.Integration;
using CashLineERP.Domain.Workflow;
using CashLineERP.SharedKernel.Workflow;
using Microsoft.EntityFrameworkCore;

namespace CashLineERP.Infrastructure.Persistence.Repositories;

public sealed class IntegrationEndpointRegistryRepository : IIntegrationEndpointRegistryRepository
{
    private readonly CashLineErpDbContext _dbContext;

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

    public IReadOnlyCollection<IntegrationEndpointDetailView> GetIntegrationEndpoints(
        bool activeOnly = false,
        string? integrationProviderCode = null,
        string? environmentCode = null)
    {
        var normalizedProviderCode = string.IsNullOrWhiteSpace(integrationProviderCode)
            ? null
            : IntegrationEndpointRegistryCatalog.NormalizeProviderCode(integrationProviderCode);
        var normalizedEnvironmentCode = string.IsNullOrWhiteSpace(environmentCode)
            ? null
            : IntegrationEndpointRegistryCatalog.NormalizeEnvironmentCode(environmentCode);

        return _dbContext.IntegrationEndpointRegistryEntries
            .AsNoTracking()
            .Where(entry => !activeOnly || entry.IsActive)
            .Where(entry => normalizedProviderCode == null || entry.IntegrationProviderCode == normalizedProviderCode)
            .Where(entry => normalizedEnvironmentCode == null || entry.EnvironmentCode == normalizedEnvironmentCode)
            .OrderBy(entry => entry.IntegrationProviderCode)
            .ThenBy(entry => entry.EnvironmentCode)
            .ThenBy(entry => entry.EndpointCode)
            .ToArray()
            .Select(Map)
            .ToArray();
    }

    public IntegrationEndpointDetailView? GetIntegrationEndpoint(long integrationEndpointId)
    {
        var endpoint = _dbContext.IntegrationEndpointRegistryEntries
            .AsNoTracking()
            .SingleOrDefault(entry => entry.Id == integrationEndpointId);

        return endpoint is null ? null : Map(endpoint);
    }

    public IntegrationEndpointDetailView Create(CreateIntegrationEndpointCommand command)
    {
        var nextSequence = (_dbContext.IntegrationEndpointRegistryEntries.Max(entry => (long?)entry.Id) ?? 0L) + 1L;

        var endpointName = IntegrationEndpointRegistryCatalog.NormalizeEndpointName(command.EndpointName);
        var providerCode = IntegrationEndpointRegistryCatalog.NormalizeProviderCode(command.IntegrationProviderCode);
        var environmentCode = IntegrationEndpointRegistryCatalog.NormalizeEnvironmentCode(command.EnvironmentCode);
        var directionCode = IntegrationEndpointRegistryCatalog.NormalizeDirectionCode(command.DirectionCode);
        var transportCode = IntegrationEndpointRegistryCatalog.NormalizeTransportCode(command.TransportCode);
        var httpMethodCode = IntegrationEndpointRegistryCatalog.NormalizeHttpMethodCode(command.HttpMethodCode);
        var baseUrl = IntegrationEndpointRegistryCatalog.NormalizeBaseUrl(command.BaseUrl);
        var relativePath = IntegrationEndpointRegistryCatalog.NormalizeRelativePath(command.RelativePath);
        var authenticationModeCode = IntegrationEndpointRegistryCatalog.NormalizeAuthenticationModeCode(command.AuthenticationModeCode);
        var retryPolicyCode = IntegrationEndpointRegistryCatalog.NormalizeRetryPolicyCode(command.RetryPolicyCode);
        var credentialReference = IntegrationEndpointRegistryCatalog.NormalizeCredentialReference(command.CredentialReference);

        IntegrationEndpointRegistryCatalog.ValidateOperationalSettings(
            transportCode,
            httpMethodCode,
            command.TimeoutSeconds,
            command.MaxRetryCount);

        var endpoint = new IntegrationEndpointRegistryEntry(
            endpointCode: $"INTEP-{nextSequence:D6}",
            endpointName: endpointName,
            integrationProviderCode: providerCode,
            environmentCode: environmentCode,
            directionCode: directionCode,
            transportCode: transportCode,
            httpMethodCode: httpMethodCode,
            baseUrl: baseUrl,
            relativePath: relativePath,
            authenticationModeCode: authenticationModeCode,
            credentialReference: credentialReference,
            timeoutSeconds: command.TimeoutSeconds,
            retryPolicyCode: retryPolicyCode,
            maxRetryCount: command.MaxRetryCount,
            isIdempotencyKeyRequired: command.IsIdempotencyKeyRequired,
            notes: command.Notes);

        _dbContext.IntegrationEndpointRegistryEntries.Add(endpoint);
        _dbContext.SaveChanges();
        return GetIntegrationEndpoint(endpoint.Id)!;
    }

    public IntegrationEndpointDetailView Submit(long integrationEndpointId, long actorUserId)
    {
        var endpoint = _dbContext.IntegrationEndpointRegistryEntries.Single(entry => entry.Id == integrationEndpointId);
        endpoint.Submit(actorUserId);

        var workflowInstance = new WorkflowInstance(
            moduleCode: "INTEGRATION",
            documentType: "INTEGRATION_ENDPOINT",
            documentId: endpoint.Id,
            makerUserId: actorUserId,
            workflowRuleId: 1L);
        workflowInstance.Submit(actorUserId, "Integration endpoint submitted for approval.");

        _dbContext.WorkflowInstances.Add(workflowInstance);
        _dbContext.SaveChanges();
        return GetIntegrationEndpoint(endpoint.Id)!;
    }

    public IntegrationEndpointDetailView Approve(long integrationEndpointId, long actorUserId)
    {
        var endpoint = _dbContext.IntegrationEndpointRegistryEntries.Single(entry => entry.Id == integrationEndpointId);
        endpoint.Approve(actorUserId);

        var workflowInstance = _dbContext.WorkflowInstances
            .Include(entry => entry.Actions)
            .SingleOrDefault(entry => entry.DocumentType == "INTEGRATION_ENDPOINT" && entry.DocumentId == integrationEndpointId);
        workflowInstance?.Approve(actorUserId, "Integration endpoint approved.");

        _dbContext.SaveChanges();
        return GetIntegrationEndpoint(endpoint.Id)!;
    }

    public IntegrationEndpointDetailView Activate(long integrationEndpointId, long actorUserId)
    {
        var endpoint = _dbContext.IntegrationEndpointRegistryEntries.Single(entry => entry.Id == integrationEndpointId);

        var conflictExists = _dbContext.IntegrationEndpointRegistryEntries
            .AsNoTracking()
            .Any(entry =>
                entry.Id != integrationEndpointId
                && entry.IsActive
                && entry.IntegrationProviderCode == endpoint.IntegrationProviderCode
                && entry.EnvironmentCode == endpoint.EnvironmentCode
                && entry.DirectionCode == endpoint.DirectionCode
                && entry.TransportCode == endpoint.TransportCode
                && entry.HttpMethodCode == endpoint.HttpMethodCode
                && entry.BaseUrl == endpoint.BaseUrl
                && (entry.RelativePath ?? string.Empty) == (endpoint.RelativePath ?? string.Empty));

        if (conflictExists)
        {
            throw new InvalidOperationException("Another active integration endpoint already exists for the same provider, environment, direction, and route.");
        }

        endpoint.Activate(actorUserId);
        OutboxEventWriter.Enqueue(
            _dbContext,
            sourceModuleCode: "INTEGRATION",
            aggregateType: "INTEGRATION_ENDPOINT",
            aggregateId: endpoint.Id,
            eventType: "INTEGRATION_ENDPOINT_ACTIVATED",
            payload: new
            {
                endpoint.EndpointCode,
                endpoint.EndpointName,
                endpoint.IntegrationProviderCode,
                endpoint.EnvironmentCode,
                endpoint.DirectionCode,
                endpoint.TransportCode,
                endpoint.HttpMethodCode,
                endpoint.BaseUrl,
                endpoint.RelativePath
            },
            actorUserId: actorUserId,
            correlationKey: endpoint.EndpointCode);
        _dbContext.SaveChanges();
        return GetIntegrationEndpoint(endpoint.Id)!;
    }

    private static IntegrationEndpointDetailView Map(IntegrationEndpointRegistryEntry endpoint) =>
        new(
            endpoint.Id,
            endpoint.EndpointCode,
            endpoint.EndpointName,
            endpoint.IntegrationProviderCode,
            endpoint.EnvironmentCode,
            endpoint.DirectionCode,
            endpoint.TransportCode,
            endpoint.HttpMethodCode,
            endpoint.BaseUrl,
            endpoint.RelativePath,
            endpoint.AuthenticationModeCode,
            endpoint.CredentialReference,
            endpoint.TimeoutSeconds,
            endpoint.RetryPolicyCode,
            endpoint.MaxRetryCount,
            endpoint.IsIdempotencyKeyRequired,
            WorkflowStatusSeedIds.ToCode(endpoint.StatusId),
            endpoint.IsActive,
            endpoint.Notes,
            endpoint.SubmittedAtUtc,
            endpoint.SubmittedByUserId,
            endpoint.ApprovedAtUtc,
            endpoint.ApprovedByUserId,
            endpoint.ActivatedAtUtc,
            endpoint.ActivatedByUserId);
}
