using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Text.Encodings.Web;
using Books.Api.EventFlow.Repositories;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;

namespace Books.Api.Authentication;

public class ApiKeyAuthenticationOptions : AuthenticationSchemeOptions
{
    public string HeaderName { get; set; } = ApiKeyDefaults.HeaderName;
    public TimeSpan CacheDuration { get; set; } = TimeSpan.FromHours(24);
}

public class ApiKeyAuthenticationHandler(
    IOptionsMonitor<ApiKeyAuthenticationOptions> options,
    ILoggerFactory logger,
    UrlEncoder encoder,
    IMemoryCache cache,
    IServiceProvider serviceProvider)
    : AuthenticationHandler<ApiKeyAuthenticationOptions>(options, logger, encoder)
{
    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (!Request.Headers.TryGetValue(Options.HeaderName, out var apiKeyHeader))
        {
            return AuthenticateResult.NoResult();
        }

        var apiKeyValue = apiKeyHeader.ToString();
        if (string.IsNullOrWhiteSpace(apiKeyValue))
        {
            return AuthenticateResult.NoResult();
        }

        // Expected format: {keyId}.{secret}
        var parts = apiKeyValue.Split('.', 2);
        if (parts.Length != 2)
        {
            return AuthenticateResult.Fail("Invalid API key format. Expected: {keyId}.{secret}");
        }

        var keyId = parts[0];
        var secret = parts[1];

        // Check cache first
        var cacheKey = $"apikey:{keyId}";
        if (cache.TryGetValue(cacheKey, out CachedApiKeyInfo? cachedInfo) && cachedInfo != null)
        {
            var providedHash = HashSecret(secret);
            if (cachedInfo.KeyHash == providedHash)
            {
                return AuthenticateResult.Success(
                    new AuthenticationTicket(cachedInfo.Principal, Scheme.Name));
            }

            return AuthenticateResult.Fail("Invalid API key");
        }

        // Cache miss - lookup in database
        using var scope = serviceProvider.CreateScope();
        var repository = scope.ServiceProvider.GetService<IApiKeyRepository>();

        if (repository == null)
        {
            Logger.LogWarning("IApiKeyRepository not registered - API key authentication unavailable");
            return AuthenticateResult.NoResult();
        }

        var apiKey = await repository.GetByIdForValidationAsync(keyId);
        if (apiKey == null)
        {
            return AuthenticateResult.Fail("Invalid API key");
        }

        // Verify hash
        var secretHash = HashSecret(secret);
        if (apiKey.KeyHash != secretHash)
        {
            return AuthenticateResult.Fail("Invalid API key");
        }

        // Build claims
        var claims = new List<Claim>
        {
            new(ClaimTypes.NameIdentifier, apiKey.ApiKeyId),
            new(ClaimTypes.Name, apiKey.Name),
            new("company_id", apiKey.CompanyId),
            new("preferred_username", $"apikey:{apiKey.Name}"),
            new(ClaimTypes.Role, "api_client"),
            new(ClaimTypes.AuthenticationMethod, "api_key")
        };

        var identity = new ClaimsIdentity(claims, Scheme.Name);
        var principal = new ClaimsPrincipal(identity);

        // Cache the result
        var cacheInfo = new CachedApiKeyInfo(secretHash, principal);
        cache.Set(cacheKey, cacheInfo, Options.CacheDuration);

        return AuthenticateResult.Success(new AuthenticationTicket(principal, Scheme.Name));
    }

    private static string HashSecret(string secret)
    {
        var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(secret));
        return Convert.ToHexString(bytes).ToLowerInvariant();
    }

    private sealed record CachedApiKeyInfo(string KeyHash, ClaimsPrincipal Principal);
}
