using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace Csob.Credentials;

/// <summary>
/// Resolves the ČSOB credentials and PSU context for the current request, exclusively from HTTP
/// headers (this service stores no secrets). Missing required headers produce a 401; a malformed
/// certificate or wrong passphrase produces a 400 (via <see cref="CryptographicException"/>).
/// </summary>
public sealed class RequestCredentialsProvider
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public RequestCredentialsProvider(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    /// <summary>Reads a single request header, returning null when absent or blank.</summary>
    public string? Header(string name)
    {
        var headers = _httpContextAccessor.HttpContext?.Request.Headers;
        if (headers is not null && headers.TryGetValue(name, out var value))
        {
            var raw = value.ToString();
            if (!string.IsNullOrWhiteSpace(raw))
            {
                return raw;
            }
        }

        return null;
    }

    /// <summary>
    /// Builds the eIDAS client certificate from the Base64 PFX header (+ optional passphrase).
    /// Returns null when no certificate header is present.
    /// </summary>
    /// <exception cref="CryptographicException">The header is not valid Base64 or the PFX/passphrase is invalid.</exception>
    public X509Certificate2? TryBuildCertificate()
    {
        var base64 = Header(CredentialConstants.CertificateHeader);
        if (base64 is null)
        {
            return null;
        }

        byte[] raw;
        try
        {
            raw = Convert.FromBase64String(base64.Trim());
        }
        catch (FormatException ex)
        {
            throw new CryptographicException($"{CredentialConstants.CertificateHeader} is not valid Base64.", ex);
        }

        var password = Header(CredentialConstants.CertificatePasswordHeader);

        // EphemeralKeySet keeps the private key in memory only — never written to the machine key store / disk.
        return new X509Certificate2(raw, password, X509KeyStorageFlags.EphemeralKeySet);
    }

    /// <summary>
    /// Resolves the full credential set required for an AISP/PISP/consent call. Throws
    /// <see cref="MissingCredentialsException"/> if any required header is absent.
    /// </summary>
    public CsobCredentials Resolve()
    {
        var accessToken = Header(CredentialConstants.AccessTokenHeader);
        var apiKey = Header(CredentialConstants.ApiKeyHeader);
        var tppName = Header(CredentialConstants.TppNameHeader);
        var certificate = TryBuildCertificate();

        var missing = new List<string>();
        if (string.IsNullOrWhiteSpace(accessToken)) missing.Add(CredentialConstants.AccessTokenHeader);
        if (string.IsNullOrWhiteSpace(apiKey)) missing.Add(CredentialConstants.ApiKeyHeader);
        if (string.IsNullOrWhiteSpace(tppName)) missing.Add(CredentialConstants.TppNameHeader);
        if (certificate is null) missing.Add(CredentialConstants.CertificateHeader);
        if (missing.Count > 0)
        {
            certificate?.Dispose();
            throw new MissingCredentialsException(missing);
        }

        return new CsobCredentials
        {
            AccessToken = accessToken!,
            ApiKey = apiKey!,
            TppName = tppName!,
            Certificate = certificate,
            UserInvolved = string.Equals(Header(CredentialConstants.UserInvolvedHeader), "true", StringComparison.OrdinalIgnoreCase),
            UserIpAddress = Header(CredentialConstants.UserIpAddressHeader),
        };
    }
}
