using System.Net.Http.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Options;
using StackExchange.Redis;

namespace Meezi.API.Security;

public class AbuseProtectionService : IAbuseProtectionService
{
    private readonly IConnectionMultiplexer _redis;
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly AbuseProtectionOptions _options;
    private readonly ILogger<AbuseProtectionService> _logger;

    public AbuseProtectionService(
        IConnectionMultiplexer redis,
        IHttpClientFactory httpClientFactory,
        IOptions<AbuseProtectionOptions> options,
        ILogger<AbuseProtectionService> logger)
    {
        _redis = redis;
        _httpClientFactory = httpClientFactory;
        _options = options.Value;
        _logger = logger;
    }

    public bool IsCaptchaConfigured =>
        !string.IsNullOrWhiteSpace(_options.Turnstile.SecretKey);

    public string? CaptchaSiteKey =>
        string.IsNullOrWhiteSpace(_options.Turnstile.SiteKey) ? null : _options.Turnstile.SiteKey.Trim();

    public Task<(bool Allowed, string? ErrorCode, string? Message)> CheckAuthOtpByIpAsync(
        string clientIp,
        CancellationToken cancellationToken = default) =>
        CheckSlidingAsync($"abuse:otp-ip:{clientIp}", _options.RateLimits.AuthOtpPerIpPerHour, TimeSpan.FromHours(1), cancellationToken);

    public async Task<(bool Allowed, string? ErrorCode, string? Message)> CheckGuestOrderAsync(
        string cafeId,
        string clientIp,
        CancellationToken cancellationToken = default)
    {
        if (!_options.Enabled)
            return (true, null, null);

        var checks = new[]
        {
            ($"abuse:qr-ip:{cafeId}:{clientIp}", _options.GuestOrders.PerIpPerCafePerHour, TimeSpan.FromHours(1)),
            ($"abuse:qr-cafe:{cafeId}", _options.GuestOrders.PerCafePerHour, TimeSpan.FromHours(1)),
            ($"abuse:qr-ip-global:{clientIp}", _options.GuestOrders.PerIpGlobalPerHour, TimeSpan.FromHours(1))
        };

        foreach (var (key, limit, window) in checks)
        {
            if (limit <= 0) continue;
            var (allowed, _, _) = await CheckSlidingAsync(key, limit, window, cancellationToken);
            if (!allowed)
                return (false, "RATE_LIMITED", "Too many orders from this network. Please wait and try again.");
        }

        return (true, null, null);
    }

    public Task<(bool Allowed, string? ErrorCode, string? Message)> CheckPublicWriteByIpAsync(
        string clientIp,
        CancellationToken cancellationToken = default) =>
        CheckSlidingAsync(
            $"abuse:pub-write:{clientIp}",
            _options.RateLimits.PublicWritesPerIpPerMinute,
            TimeSpan.FromMinutes(1),
            cancellationToken);

    public async Task<(bool Ok, string? ErrorCode, string? Message)> VerifyCaptchaAsync(
        string? captchaToken,
        CancellationToken cancellationToken = default)
    {
        var required = _options.RequireCaptchaOnPublicWrites && IsCaptchaConfigured;
        if (!required)
        {
            if (string.IsNullOrWhiteSpace(captchaToken) || !IsCaptchaConfigured)
                return (true, null, null);
        }
        else if (string.IsNullOrWhiteSpace(captchaToken))
        {
            return (false, "CAPTCHA_REQUIRED", "Complete the security check and try again.");
        }

        if (!IsCaptchaConfigured)
            return (true, null, null);

        try
        {
            var client = _httpClientFactory.CreateClient("turnstile");
            var response = await client.PostAsync(
                "https://challenges.cloudflare.com/turnstile/v0/siteverify",
                new FormUrlEncodedContent(new Dictionary<string, string>
                {
                    ["secret"] = _options.Turnstile.SecretKey,
                    ["response"] = captchaToken!.Trim()
                }),
                cancellationToken);

            var body = await response.Content.ReadFromJsonAsync<TurnstileVerifyResponse>(cancellationToken);
            if (body?.Success == true)
                return (true, null, null);

            _logger.LogWarning("Turnstile verification failed");
            return (false, "CAPTCHA_INVALID", "Security check failed. Refresh and try again.");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Turnstile verify error");
            return (false, "CAPTCHA_ERROR", "Could not verify security check.");
        }
    }

    private async Task<(bool Allowed, string? ErrorCode, string? Message)> CheckSlidingAsync(
        string key,
        int limit,
        TimeSpan window,
        CancellationToken cancellationToken)
    {
        if (!_options.Enabled || limit <= 0)
            return (true, null, null);

        var db = _redis.GetDatabase();
        var count = await db.StringIncrementAsync(key);
        if (count == 1)
            await db.KeyExpireAsync(key, window);

        if ((int)count > limit)
            return (false, "RATE_LIMITED", "Too many requests. Please slow down.");

        return (true, null, null);
    }

    private sealed class TurnstileVerifyResponse
    {
        [JsonPropertyName("success")]
        public bool Success { get; set; }
    }
}
