using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using Hl7.Fhir.Serialization;
using Microsoft.Extensions.Options;
using Task = System.Threading.Tasks.Task;

public class FhirApiKeyOrJwtMiddleware
{
    public const string SchemeName = "FhirApiKey";

    private readonly RequestDelegate _next;
    private readonly FhirOptions _options;
    private static readonly FhirJsonSerializer Serializer = new();

    public FhirApiKeyOrJwtMiddleware(RequestDelegate next, IOptions<FhirOptions> options)
    {
        _next = next;
        _options = options.Value;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        if (!context.Request.Path.StartsWithSegments("/fhir"))
        {
            await _next(context);
            return;
        }

        if (context.Request.Path.StartsWithSegments("/fhir/R4/metadata"))
        {
            await _next(context);
            return;
        }

        if (context.User.Identity?.IsAuthenticated == true)
        {
            await _next(context);
            return;
        }

        var configuredKeys = GetConfiguredKeys();
        if (configuredKeys.Length == 0)
        {
            await _next(context);
            return;
        }

        if (context.Request.Headers.TryGetValue("X-Api-Key", out var suppliedKey)
            && MatchesAnyKey(suppliedKey!, configuredKeys))
        {
            var claims = new[]
            {
                new Claim(ClaimTypes.NameIdentifier, "44444444-4444-4444-4444-444444444444"),
                new Claim(ClaimTypes.Name, "integration.mirth"),
                new Claim("display_name", "Mirth Connect"),
                new Claim("clinical_role", ClinicalRole.Integration.ToDbString()),
            };
            context.User = new ClaimsPrincipal(new ClaimsIdentity(claims, SchemeName));
            await _next(context);
            return;
        }

        if (context.Request.Headers.ContainsKey("X-Api-Key"))
        {
            context.Response.StatusCode = StatusCodes.Status401Unauthorized;
            context.Response.ContentType = "application/fhir+json";
            var outcome = FhirOperationOutcomeBuilder.Create(401, "login", "Invalid or missing API key.");
            await context.Response.WriteAsync(Serializer.SerializeToString(outcome));
            return;
        }

        await _next(context);
    }

    private string[] GetConfiguredKeys()
    {
        var keys = new List<string>();
        if (!string.IsNullOrWhiteSpace(_options.ApiKey))
            keys.Add(_options.ApiKey);
        foreach (var k in _options.ApiKeys)
        {
            if (!string.IsNullOrWhiteSpace(k))
                keys.Add(k);
        }
        return keys.ToArray();
    }

    private static bool MatchesAnyKey(string supplied, string[] configuredKeys)
    {
        var suppliedBytes = Encoding.UTF8.GetBytes(supplied);
        var matched = false;
        foreach (var configured in configuredKeys)
        {
            var configuredBytes = Encoding.UTF8.GetBytes(configured);
            if (CryptographicOperations.FixedTimeEquals(suppliedBytes, configuredBytes))
                matched = true;
        }
        return matched;
    }
}
