using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json.Serialization;
using FileAccessAgent.Application.Abstractions;
using FileAccessAgent.Application.Models;
using FileAccessAgent.Infrastructure.Configuration;
using Microsoft.Extensions.Options;

namespace FileAccessAgent.Infrastructure.Services;

public sealed class MemberCenterDelegatedDownloadTokenValidator : IDelegatedDownloadTokenValidator
{
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly MemberCenterOptions _options;
    private readonly SemaphoreSlim _tokenLock = new(1, 1);
    private string? _cachedAccessToken;
    private DateTimeOffset _cachedAccessTokenExpiresAt;

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

    public async Task<DelegatedDownloadTokenValidationResult> ValidateAsync(
        DelegatedDownloadTokenValidationRequest request,
        CancellationToken cancellationToken)
    {
        var accessToken = await GetServiceAccessTokenAsync(cancellationToken);
        if (string.IsNullOrWhiteSpace(accessToken))
        {
            return new DelegatedDownloadTokenValidationResult(false);
        }

        var http = _httpClientFactory.CreateClient(nameof(MemberCenterDelegatedDownloadTokenValidator));
        http.BaseAddress = BuildBaseUri(_options.BaseUrl);
        http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);

        var payload = new ValidateFileDownloadTokenPayload(
            request.Token,
            request.TenantId,
            request.FileId,
            request.ObjectKey,
            request.Method);

        using var response = await http.PostAsJsonAsync(
            _options.DownloadTokenValidationPath,
            payload,
            cancellationToken);

        if (!response.IsSuccessStatusCode)
        {
            return new DelegatedDownloadTokenValidationResult(false);
        }

        var result = await response.Content.ReadFromJsonAsync<ValidateFileDownloadTokenResponse>(cancellationToken: cancellationToken);
        if (result is null || !result.Active)
        {
            return new DelegatedDownloadTokenValidationResult(false);
        }

        return new DelegatedDownloadTokenValidationResult(
            result.Active,
            result.TenantId,
            result.UserId,
            result.FileId,
            result.ObjectKey,
            result.Method,
            result.ExpiresAt);
    }

    private async Task<string?> GetServiceAccessTokenAsync(CancellationToken cancellationToken)
    {
        if (!string.IsNullOrWhiteSpace(_cachedAccessToken) && _cachedAccessTokenExpiresAt > DateTimeOffset.UtcNow.AddSeconds(15))
        {
            return _cachedAccessToken;
        }

        await _tokenLock.WaitAsync(cancellationToken);
        try
        {
            if (!string.IsNullOrWhiteSpace(_cachedAccessToken) && _cachedAccessTokenExpiresAt > DateTimeOffset.UtcNow.AddSeconds(15))
            {
                return _cachedAccessToken;
            }

            if (string.IsNullOrWhiteSpace(_options.ClientId) || string.IsNullOrWhiteSpace(_options.ClientSecret))
            {
                return null;
            }

            var http = _httpClientFactory.CreateClient($"{nameof(MemberCenterDelegatedDownloadTokenValidator)}.Token");
            http.BaseAddress = BuildBaseUri(_options.BaseUrl);

            var form = new Dictionary<string, string>
            {
                ["grant_type"] = "client_credentials",
                ["client_id"] = _options.ClientId,
                ["client_secret"] = _options.ClientSecret,
                ["scope"] = _options.Scope
            };

            using var response = await http.PostAsync(
                _options.TokenEndpointPath,
                new FormUrlEncodedContent(form),
                cancellationToken);

            if (!response.IsSuccessStatusCode)
            {
                return null;
            }

            var token = await response.Content.ReadFromJsonAsync<TokenResponse>(cancellationToken: cancellationToken);
            if (token is null || string.IsNullOrWhiteSpace(token.AccessToken))
            {
                return null;
            }

            _cachedAccessToken = token.AccessToken;
            _cachedAccessTokenExpiresAt = DateTimeOffset.UtcNow.AddSeconds(Math.Max(0, token.ExpiresIn));
            return _cachedAccessToken;
        }
        finally
        {
            _tokenLock.Release();
        }
    }

    private static Uri BuildBaseUri(string baseUrl)
    {
        if (!Uri.TryCreate(baseUrl, UriKind.Absolute, out var uri))
        {
            throw new InvalidOperationException("MemberCenter:BaseUrl is invalid.");
        }

        return uri;
    }

    private sealed record TokenResponse(
        [property: JsonPropertyName("access_token")] string AccessToken,
        [property: JsonPropertyName("expires_in")] int ExpiresIn);

    private sealed record ValidateFileDownloadTokenPayload(
        [property: JsonPropertyName("token")] string Token,
        [property: JsonPropertyName("tenant_id")] Guid TenantId,
        [property: JsonPropertyName("file_id")] string? FileId,
        [property: JsonPropertyName("object_key")] string? ObjectKey,
        [property: JsonPropertyName("method")] string Method);

    private sealed record ValidateFileDownloadTokenResponse(
        [property: JsonPropertyName("active")] bool Active,
        [property: JsonPropertyName("tenant_id")] Guid? TenantId,
        [property: JsonPropertyName("user_id")] Guid? UserId,
        [property: JsonPropertyName("file_id")] string? FileId,
        [property: JsonPropertyName("object_key")] string? ObjectKey,
        [property: JsonPropertyName("method")] string? Method,
        [property: JsonPropertyName("expires_at")] DateTimeOffset? ExpiresAt);
}
