using System.Security.Cryptography.X509Certificates;
using System.Text.Json;
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;

namespace RegistroPersonalAN.Services
{
    public sealed class CertificateLoginBridgeService
    {
        private readonly IHttpClientFactory _httpClientFactory;
        private readonly CertificateLoginOptions _options;
        private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions
        {
            PropertyNameCaseInsensitive = true
        };

        public CertificateLoginBridgeService(
            IHttpClientFactory httpClientFactory,
            IOptions<CertificateLoginOptions> options)
        {
            _httpClientFactory = httpClientFactory;
            _options = options.Value;
        }

        public async Task<IResult> HandleAsync(HttpContext context)
        {
            if (!CertificateLoginBridgeHelper.IsCertificateHostRequest(context, _options.PublicUrl))
            {
                return Results.NotFound();
            }

            var parentOrigin = context.Request.Query["parentOrigin"].ToString();
            if (!CertificateLoginBridgeHelper.IsAllowedParentOrigin(parentOrigin, _options.AllowedParentOrigins))
            {
                return Results.BadRequest("El origen padre no está permitido.");
            }

            var certificate = CertificateLoginBridgeHelper.ReadCertificate(
                context,
                _options.ForwardedCertificateHeader,
                _options.AdditionalForwardedCertificateHeaders);

            if (certificate == null)
            {
                return Results.Unauthorized();
            }

            var dni = CertificateLoginBridgeHelper.ObtenerDni(certificate);
            if (string.IsNullOrWhiteSpace(dni))
            {
                return Results.Unauthorized();
            }

            var client = _httpClientFactory.CreateClient("DefaultClient");
            using var response = await client.PostAsJsonAsync("Auth/login-cert-proxy", new CertificateProxyLoginRequest
            {
                Dni = dni
            });

            var responseContent = await response.Content.ReadAsStringAsync();
            if (!response.IsSuccessStatusCode)
            {
                return Results.Content(
                    responseContent,
                    "text/plain; charset=utf-8",
                    statusCode: (int)response.StatusCode);
            }

            var loginResponse = JsonSerializer.Deserialize<CertificateProxyLoginResponse>(responseContent, _jsonOptions);
            if (loginResponse?.User == null || string.IsNullOrWhiteSpace(loginResponse.Token))
            {
                return Results.StatusCode(StatusCodes.Status502BadGateway);
            }

            var iframe = string.Equals(context.Request.Query["iframe"], "true", StringComparison.OrdinalIgnoreCase);
            if (iframe)
            {
                return Results.Content(
                    CertificateLoginBridgeHelper.BuildIframeHtml(loginResponse, parentOrigin),
                    "text/html; charset=utf-8");
            }

            return Results.Content(responseContent, "application/json; charset=utf-8");
        }
    }

    public sealed class CertificateLoginOptions
    {
        public string PublicUrl { get; set; } = string.Empty;
        public string ForwardedCertificateHeader { get; set; } = "X-ARR-ClientCert";
        public List<string> AdditionalForwardedCertificateHeaders { get; set; } = new();
        public List<string> AllowedParentOrigins { get; set; } = new();
    }

    public sealed class CertificateProxyLoginRequest
    {
        public string Dni { get; set; } = string.Empty;
    }

    public sealed class CertificateProxyLoginResponse
    {
        public string Token { get; set; } = string.Empty;
        public CertificateProxyUser? User { get; set; }
    }

    public sealed class CertificateProxyUser
    {
        public string NIF { get; set; } = string.Empty;
        public string NOMBRE { get; set; } = string.Empty;
        public string APELLIDOS { get; set; } = string.Empty;
        public bool? ADMINISTRARPTYREGISTRO { get; set; }
    }

    internal static class CertificateLoginBridgeHelper
    {
        private static readonly string[] DefaultCertificateHeaders =
        {
            "X-ARR-ClientCert",
            "X-Client-Cert",
            "X-Client-Certificate",
            "X-SSL-CERT",
            "Ssl-Client-Cert"
        };

        public static bool IsCertificateHostRequest(HttpContext context, string publicUrl)
        {
            if (!Uri.TryCreate(publicUrl, UriKind.Absolute, out var configuredUri))
            {
                return false;
            }

            var requestHost = context.Request.Host.Host;
            var requestPort = context.Request.Host.Port ?? GetDefaultPort(context.Request.Scheme);
            var configuredPort = configuredUri.IsDefaultPort
                ? GetDefaultPort(configuredUri.Scheme)
                : configuredUri.Port;

            return string.Equals(context.Request.Scheme, configuredUri.Scheme, StringComparison.OrdinalIgnoreCase) &&
                   string.Equals(requestHost, configuredUri.Host, StringComparison.OrdinalIgnoreCase) &&
                   requestPort == configuredPort;
        }

        public static bool IsAllowedParentOrigin(string parentOrigin, IEnumerable<string> allowedParentOrigins)
        {
            if (!TryGetOrigin(parentOrigin, out var normalizedParentOrigin))
            {
                return false;
            }

            return allowedParentOrigins
                .Where(origin => !string.IsNullOrWhiteSpace(origin))
                .Select(origin => NormalizeOrigin(origin))
                .Any(origin => string.Equals(origin, normalizedParentOrigin, StringComparison.OrdinalIgnoreCase));
        }

        public static X509Certificate2? ReadCertificate(
            HttpContext context,
            string forwardedCertificateHeader,
            IEnumerable<string>? additionalForwardedCertificateHeaders)
        {
            if (context.Connection.ClientCertificate != null)
            {
                return context.Connection.ClientCertificate;
            }

            var headerCandidates = new List<string>();
            AddHeaderName(headerCandidates, forwardedCertificateHeader);

            if (additionalForwardedCertificateHeaders != null)
            {
                foreach (var headerName in additionalForwardedCertificateHeaders)
                {
                    AddHeaderName(headerCandidates, headerName);
                }
            }

            foreach (var headerName in DefaultCertificateHeaders)
            {
                AddHeaderName(headerCandidates, headerName);
            }

            foreach (var headerName in headerCandidates.Distinct(StringComparer.OrdinalIgnoreCase))
            {
                var rawHeader = context.Request.Headers[headerName].ToString();
                if (string.IsNullOrWhiteSpace(rawHeader))
                {
                    continue;
                }

                var certificate = TryCreateCertificate(rawHeader);
                if (certificate != null)
                {
                    return certificate;
                }
            }

            return null;
        }

        public static string ObtenerDni(X509Certificate2 certificado)
        {
            try
            {
                var subject = certificado.Subject;
                var cnPrefix = "CN=";
                var startIndex = subject.IndexOf(cnPrefix, StringComparison.OrdinalIgnoreCase);
                if (startIndex == -1)
                    return string.Empty;

                startIndex += cnPrefix.Length;
                var endIndex = subject.IndexOf(',', startIndex);
                if (endIndex == -1)
                {
                    endIndex = subject.Length;
                }

                var commonName = subject.Substring(startIndex, endIndex - startIndex).Trim();
                var dniMatch = Regex.Match(commonName, @"\b\d{8}[A-Z]\b");
                return dniMatch.Success ? dniMatch.Value : string.Empty;
            }
            catch
            {
                return string.Empty;
            }
        }

        public static string BuildIframeHtml(CertificateProxyLoginResponse response, string parentOrigin)
        {
            var payloadJson = JsonSerializer.Serialize(new
            {
                token = response.Token,
                user = response.User
            });
            var targetOriginJson = JsonSerializer.Serialize(NormalizeOrigin(parentOrigin));

            return $@"<!DOCTYPE html>
<html>
  <head><title>Autenticación Certificado</title></head>
  <body>
    <script>
      window.parent.postMessage({payloadJson}, {targetOriginJson});
    </script>
  </body>
</html>";
        }

        private static X509Certificate2? TryCreateCertificate(string rawHeader)
        {
            var normalizedValue = Uri.UnescapeDataString(rawHeader).Trim();

            if (normalizedValue.Contains("BEGIN CERTIFICATE", StringComparison.OrdinalIgnoreCase))
            {
                try
                {
                    return X509Certificate2.CreateFromPem(normalizedValue);
                }
                catch
                {
                    return null;
                }
            }

            var compactValue = Regex.Replace(normalizedValue, @"\s+", string.Empty);

            if (compactValue.Length > 0 &&
                compactValue.Length % 2 == 0 &&
                Regex.IsMatch(compactValue, "^[0-9a-fA-F]+$"))
            {
                try
                {
                    return new X509Certificate2(Convert.FromHexString(compactValue));
                }
                catch
                {
                    return null;
                }
            }

            try
            {
                return new X509Certificate2(Convert.FromBase64String(compactValue));
            }
            catch
            {
                return null;
            }
        }

        private static bool TryGetOrigin(string value, out string origin)
        {
            origin = string.Empty;
            if (!Uri.TryCreate(value, UriKind.Absolute, out var uri))
            {
                return false;
            }

            origin = uri.GetLeftPart(UriPartial.Authority).TrimEnd('/');
            return true;
        }

        private static string NormalizeOrigin(string value)
        {
            return Uri.TryCreate(value, UriKind.Absolute, out var uri)
                ? uri.GetLeftPart(UriPartial.Authority).TrimEnd('/')
                : value.Trim().TrimEnd('/');
        }

        private static int GetDefaultPort(string scheme)
        {
            return string.Equals(scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) ? 80 : 443;
        }

        private static void AddHeaderName(ICollection<string> headerCandidates, string? headerName)
        {
            if (!string.IsNullOrWhiteSpace(headerName))
            {
                headerCandidates.Add(headerName.Trim());
            }
        }
    }
}
