using System.Text.Json;
using Backend.Domain;

namespace Backend.Services;

public sealed record RiskRuleSetting(
    string Key,
    string Label,
    bool Enabled,
    int Threshold,
    int WindowMinutes,
    string Severity,
    string Description);

public static class RiskRuleSettings
{
    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);

    public static RiskRuleSetting[] Defaults { get; } =
    [
        new("resubmitted_ballot", "Ballot erneut gespeichert", true, 1, 360, "low", "Erstellt einen Hinweis, wenn ein User sein Voting erneut speichert."),
        new("rapid_vote_updates", "Voting-Burst", true, 3, 10, "high", "Erstellt einen Hinweis, wenn ein User sehr schnell mehrere Voting-Aenderungen ausloest."),
        new("resubmitted_nomination", "Nominierung erneut eingereicht", true, 1, 360, "low", "Erstellt einen Hinweis, wenn ein User in derselben Kategorie erneut nominiert."),
        new("rapid_nomination_burst", "Nominierungs-Burst", true, 10, 10, "high", "Erstellt einen Hinweis, wenn ein User sehr viele Nominierungen in kurzer Zeit sendet."),
        new("duplicate_clip_submission", "Doppelter Clip", true, 1, 360, "medium", "Erstellt einen Hinweis, wenn ein User denselben Clip erneut einreicht."),
        new("rapid_clip_burst", "Clip-Burst", true, 5, 10, "high", "Erstellt einen Hinweis, wenn ein User sehr viele Clips in kurzer Zeit sendet."),
        new("rapid_login_ip", "Login-Burst pro IP", true, 3, 15, "medium", "Erstellt einen Hinweis, wenn mehrere neue Sessions von derselben IP entstehen."),
        new("rapid_demo_login_ip", "Demo-Login-Burst pro IP", true, 3, 15, "medium", "Erstellt einen Hinweis, wenn mehrere Demo-Admin-Sessions von derselben IP entstehen."),
    ];

    public static RiskRuleSetting[] Read(SiteSettings? settings)
    {
        var storedRules = Parse(settings?.RiskRulesJson);
        return Defaults
            .Select(defaultRule =>
            {
                var storedRule = storedRules.FirstOrDefault(item => string.Equals(item.Key, defaultRule.Key, StringComparison.OrdinalIgnoreCase));
                return storedRule is null ? defaultRule : Normalize(storedRule, defaultRule);
            })
            .ToArray();
    }

    public static string Serialize(IEnumerable<RiskRuleSetting> rules) =>
        JsonSerializer.Serialize(rules.Select(rule => Normalize(rule, Defaults.FirstOrDefault(item => item.Key == rule.Key) ?? rule)), JsonOptions);

    public static RiskRuleSetting Find(IEnumerable<RiskRuleSetting> rules, string key) =>
        rules.FirstOrDefault(item => string.Equals(item.Key, key, StringComparison.OrdinalIgnoreCase))
            ?? Defaults.First(item => item.Key == key);

    private static RiskRuleSetting[] Parse(string? json)
    {
        if (string.IsNullOrWhiteSpace(json))
        {
            return [];
        }

        try
        {
            return JsonSerializer.Deserialize<RiskRuleSetting[]>(json, JsonOptions) ?? [];
        }
        catch (JsonException)
        {
            return [];
        }
    }

    private static RiskRuleSetting Normalize(RiskRuleSetting rule, RiskRuleSetting fallback)
    {
        var severity = rule.Severity.Trim().ToLowerInvariant() switch
        {
            "high" => "high",
            "medium" => "medium",
            "low" => "low",
            _ => fallback.Severity,
        };

        return rule with
        {
            Key = fallback.Key,
            Label = string.IsNullOrWhiteSpace(rule.Label) ? fallback.Label : rule.Label.Trim(),
            Threshold = Math.Clamp(rule.Threshold, 1, 500),
            WindowMinutes = Math.Clamp(rule.WindowMinutes, 1, 1440),
            Severity = severity,
            Description = string.IsNullOrWhiteSpace(rule.Description) ? fallback.Description : rule.Description.Trim(),
        };
    }
}
