using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;
using CashLineERP.Application.Integration;
using CashLineERP.Contracts.Tax;

namespace CashLineERP.Application.Tax;

public sealed class EtaAuthorityAuthService : IEtaAuthorityAuthService
{
    private readonly IIntegrationEndpointRegistryService _integrationEndpointRegistryService;
    private readonly string _targetEnvironmentCode;

    public EtaAuthorityAuthService(IIntegrationEndpointRegistryService integrationEndpointRegistryService)
    {
        _integrationEndpointRegistryService = integrationEndpointRegistryService;
        _targetEnvironmentCode = ResolveTargetEnvironmentCode();
    }

    public EtaAuthorityAuthHealthResponse GetHealth()
    {
        var authEndpoint = ResolveEndpoint("OUTBOUND", "POST", "REST", ["NONE", "BASIC"], relativePathContains: "/connect/token");
        var submissionEndpoint = ResolveEndpoint("OUTBOUND", "POST", "REST", ["BEARER"], relativePathContains: "/documentsubmissions");
        var submissionStatusEndpoint = ResolveEndpoint("OUTBOUND", "GET", "REST", ["BEARER"], relativePathContains: "/submissions");
        var callbackPingEndpoint = ResolveEndpoint("INBOUND", "PUT", "WEBHOOK", ["API_KEY", "SIGNATURE", "NONE", "BASIC"], relativePathContains: "/ping");
        var credentialsResolved = TryResolveClientCredentials(authEndpoint?.CredentialReference, out _, out _);

        var statusCode = authEndpoint is null
            ? "NOT_CONFIGURED"
            : credentialsResolved
                ? "READY_FOR_AUTH"
                : "CREDENTIAL_REFERENCE_UNRESOLVED";

        var message = authEndpoint is null
            ? "ETA auth endpoint is not configured."
            : credentialsResolved
                ? "ETA auth endpoint and credential format are ready for live auth."
                : "ETA auth endpoint exists, but the credential reference is not resolvable by the current Phase 1 runtime.";

        return new EtaAuthorityAuthHealthResponse(
            authEndpoint is not null,
            authEndpoint?.EndpointCode,
            authEndpoint?.BaseUrl,
            submissionEndpoint is not null,
            submissionEndpoint?.EndpointCode,
            submissionStatusEndpoint is not null,
            submissionStatusEndpoint?.EndpointCode,
            callbackPingEndpoint is not null,
            callbackPingEndpoint?.EndpointCode,
            credentialsResolved,
            authEndpoint is not null && credentialsResolved,
            statusCode,
            message);
    }

    public EtaAuthorityEndpointSelection? GetSubmissionEndpoint() =>
        ResolveEndpoint("OUTBOUND", "POST", "REST", ["BEARER"], relativePathContains: "/documentsubmissions");

    public EtaAuthorityEndpointSelection? GetSubmissionStatusEndpoint() =>
        ResolveEndpoint("OUTBOUND", "GET", "REST", ["BEARER"], relativePathContains: "/submissions");

    public EtaAuthorityEndpointSelection? GetCallbackPingEndpoint() =>
        ResolveEndpoint("INBOUND", "PUT", "WEBHOOK", ["API_KEY", "SIGNATURE", "NONE", "BASIC"], relativePathContains: "/ping");

    public EtaAuthorityEndpointSelection? GetDocumentNotificationEndpoint() =>
        ResolveEndpoint("INBOUND", "PUT", "WEBHOOK", ["API_KEY", "SIGNATURE", "NONE", "BASIC"], relativePathContains: "/notifications/documents");

    public string? ResolveCallbackPingSharedSecret()
    {
        var endpoint = GetCallbackPingEndpoint();
        if (endpoint is null)
        {
            return null;
        }

        return TryResolveSharedSecret(endpoint.CredentialReference, out var sharedSecret) ? sharedSecret : null;
    }

    public string? ResolveDocumentNotificationSharedSecret()
    {
        var endpoint = GetDocumentNotificationEndpoint();
        if (endpoint is null)
        {
            return null;
        }

        return TryResolveSharedSecret(endpoint.CredentialReference, out var sharedSecret) ? sharedSecret : null;
    }

    public async Task<EtaAuthorityAccessToken> AcquireAccessTokenAsync(CancellationToken cancellationToken = default)
    {
        var authEndpoint = ResolveEndpoint("OUTBOUND", "POST", "REST", ["NONE", "BASIC"], relativePathContains: "/connect/token");
        if (authEndpoint is null)
        {
            throw new InvalidOperationException("ETA auth endpoint is not configured.");
        }

        if (!TryResolveClientCredentials(authEndpoint.CredentialReference, out var clientId, out var clientSecret))
        {
            throw new InvalidOperationException("ETA auth credential reference is not resolvable by the current runtime.");
        }

        using var httpClient = CreateClient(authEndpoint);
        using var request = new HttpRequestMessage(HttpMethod.Post, BuildAbsoluteUri(authEndpoint));
        request.Content = new FormUrlEncodedContent(new Dictionary<string, string>
        {
            ["grant_type"] = "client_credentials",
            ["client_id"] = clientId!,
            ["client_secret"] = clientSecret!
        });
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

        using var response = await httpClient.SendAsync(request, cancellationToken);
        var responseText = await response.Content.ReadAsStringAsync(cancellationToken);
        if (!response.IsSuccessStatusCode)
        {
            throw new InvalidOperationException($"ETA auth request failed with HTTP {(int)response.StatusCode}: {responseText}");
        }

        using var document = JsonDocument.Parse(responseText);
        if (!document.RootElement.TryGetProperty("access_token", out var accessTokenElement))
        {
            throw new InvalidOperationException("ETA auth response did not include access_token.");
        }

        var tokenType = document.RootElement.TryGetProperty("token_type", out var tokenTypeElement)
            ? tokenTypeElement.GetString() ?? "Bearer"
            : "Bearer";
        var expiresInSeconds = document.RootElement.TryGetProperty("expires_in", out var expiresElement)
            && expiresElement.TryGetInt32(out var parsedExpiresIn)
            ? parsedExpiresIn
            : 3600;

        return new EtaAuthorityAccessToken(
            accessTokenElement.GetString() ?? throw new InvalidOperationException("ETA auth access_token was empty."),
            tokenType,
            DateTime.UtcNow.AddSeconds(expiresInSeconds));
    }

    private EtaAuthorityEndpointSelection? ResolveEndpoint(
        string directionCode,
        string httpMethodCode,
        string transportCode,
        IReadOnlyCollection<string> acceptedAuthenticationModes,
        string? relativePathContains = null)
    {
        return _integrationEndpointRegistryService
            .GetIntegrationEndpoints(activeOnly: true, integrationProviderCode: "ETA", environmentCode: _targetEnvironmentCode)
            .Where(endpoint => string.Equals(endpoint.DirectionCode, directionCode, StringComparison.OrdinalIgnoreCase))
            .Where(endpoint => string.Equals(endpoint.TransportCode, transportCode, StringComparison.OrdinalIgnoreCase))
            .Where(endpoint => string.Equals(endpoint.HttpMethodCode, httpMethodCode, StringComparison.OrdinalIgnoreCase))
            .Where(endpoint => acceptedAuthenticationModes.Contains(endpoint.AuthenticationModeCode, StringComparer.OrdinalIgnoreCase))
            .Where(endpoint => string.IsNullOrWhiteSpace(relativePathContains)
                || (endpoint.RelativePath?.Contains(relativePathContains, StringComparison.OrdinalIgnoreCase) ?? false))
            .OrderBy(endpoint => endpoint.IntegrationEndpointId)
            .Select(endpoint => new EtaAuthorityEndpointSelection(
                endpoint.IntegrationEndpointId,
                endpoint.EndpointCode,
                endpoint.BaseUrl,
                endpoint.RelativePath ?? string.Empty,
                endpoint.AuthenticationModeCode,
                endpoint.CredentialReference,
                endpoint.TimeoutSeconds))
            .FirstOrDefault();
    }

    private static Uri BuildAbsoluteUri(EtaAuthorityEndpointSelection endpoint)
    {
        var baseUrl = endpoint.BaseUrl.TrimEnd('/');
        var relativePath = string.IsNullOrWhiteSpace(endpoint.RelativePath)
            ? string.Empty
            : endpoint.RelativePath.StartsWith('/')
                ? endpoint.RelativePath
                : "/" + endpoint.RelativePath;
        return new Uri($"{baseUrl}{relativePath}", UriKind.Absolute);
    }

    private static HttpClient CreateClient(EtaAuthorityEndpointSelection endpoint) =>
        new()
        {
            Timeout = TimeSpan.FromSeconds(endpoint.TimeoutSeconds)
        };

    private bool TryResolveClientCredentials(string? credentialReference, out string? clientId, out string? clientSecret)
    {
        clientId = null;
        clientSecret = null;

        if (string.IsNullOrWhiteSpace(credentialReference))
        {
            return false;
        }

        var normalized = credentialReference.Trim();
        const string inlinePrefix = "inline://";
        const string secretPrefix = "secret://";
        if (normalized.StartsWith(inlinePrefix, StringComparison.OrdinalIgnoreCase))
        {
            var payload = normalized[inlinePrefix.Length..];
            var separatorIndex = payload.IndexOf('|');
            if (separatorIndex <= 0 || separatorIndex >= payload.Length - 1)
            {
                return false;
            }

            clientId = Uri.UnescapeDataString(payload[..separatorIndex]);
            clientSecret = Uri.UnescapeDataString(payload[(separatorIndex + 1)..]);
            return !string.IsNullOrWhiteSpace(clientId) && !string.IsNullOrWhiteSpace(clientSecret);
        }

        if (!normalized.StartsWith(secretPrefix, StringComparison.OrdinalIgnoreCase))
        {
            return false;
        }

        var secretPayload = normalized[secretPrefix.Length..];
        var secretSeparatorIndex = secretPayload.IndexOf('|');
        if (secretSeparatorIndex <= 0 || secretSeparatorIndex >= secretPayload.Length - 1)
        {
            return false;
        }

        var clientIdReference = secretPayload[..secretSeparatorIndex];
        var clientSecretReference = secretPayload[(secretSeparatorIndex + 1)..];

        if (!TryResolveSecretReferenceValue(clientIdReference, out clientId)
            || !TryResolveSecretReferenceValue(clientSecretReference, out clientSecret))
        {
            clientId = null;
            clientSecret = null;
            return false;
        }

        return !string.IsNullOrWhiteSpace(clientId) && !string.IsNullOrWhiteSpace(clientSecret);
    }

    private bool TryResolveSharedSecret(string? credentialReference, out string? sharedSecret)
    {
        sharedSecret = null;
        if (string.IsNullOrWhiteSpace(credentialReference))
        {
            return false;
        }

        const string inlinePrefix = "inline://";
        var normalized = credentialReference.Trim();
        const string secretPrefix = "secret://";
        if (normalized.StartsWith(inlinePrefix, StringComparison.OrdinalIgnoreCase))
        {
            sharedSecret = Uri.UnescapeDataString(normalized[inlinePrefix.Length..]);
            return !string.IsNullOrWhiteSpace(sharedSecret);
        }

        if (!normalized.StartsWith(secretPrefix, StringComparison.OrdinalIgnoreCase))
        {
            return false;
        }

        return TryResolveSecretReferenceValue(normalized[secretPrefix.Length..], out sharedSecret);
    }

    private bool TryResolveSecretReferenceValue(string? secretReference, out string? resolvedValue)
    {
        resolvedValue = null;
        if (string.IsNullOrWhiteSpace(secretReference))
        {
            return false;
        }

        const string secretPrefix = "secret://";
        var normalizedReference = secretReference.Trim();
        if (normalizedReference.StartsWith(secretPrefix, StringComparison.OrdinalIgnoreCase))
        {
            normalizedReference = normalizedReference[secretPrefix.Length..];
        }

        var keySegments = normalizedReference
            .Split('/', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
            .Select(NormalizeSecretReferenceSegment)
            .Where(segment => !string.IsNullOrWhiteSpace(segment))
            .ToArray();
        if (keySegments.Length == 0)
        {
            return false;
        }

        var environmentVariableName = "SecretReferences__" + string.Join("__", keySegments);
        resolvedValue = Environment.GetEnvironmentVariable(environmentVariableName)
            ?? Environment.GetEnvironmentVariable(environmentVariableName.ToUpperInvariant());
        return !string.IsNullOrWhiteSpace(resolvedValue);
    }

    private static string NormalizeSecretReferenceSegment(string segment)
    {
        var builder = new System.Text.StringBuilder(segment.Length);
        var previousUnderscore = false;

        foreach (var character in segment.Trim())
        {
            if (char.IsLetterOrDigit(character))
            {
                builder.Append(char.ToUpperInvariant(character));
                previousUnderscore = false;
                continue;
            }

            if (!previousUnderscore)
            {
                builder.Append('_');
                previousUnderscore = true;
            }
        }

        return builder.ToString().Trim('_');
    }

    private static string ResolveTargetEnvironmentCode()
    {
        var configuredEnvironmentCode = Environment.GetEnvironmentVariable("EtaAuthority__TargetEnvironmentCode")
            ?? Environment.GetEnvironmentVariable("ETA_AUTHORITY_TARGET_ENVIRONMENT_CODE");
        return string.IsNullOrWhiteSpace(configuredEnvironmentCode)
            ? "UAT"
            : IntegrationEndpointRegistryCatalog.NormalizeEnvironmentCode(configuredEnvironmentCode);
    }
}
