using Backend.Contracts;
using Backend.Common;
using Backend.Data;
using Backend.Security;
using Backend.Services;
using Microsoft.EntityFrameworkCore;

namespace Backend.Endpoints;

public static partial class AdminModerationEndpoints
{
    private static async Task<IResult> GetRiskFlags(
        int? limit,
        int? offset,
        string? status,
        string? severity,
        string? query,
        bool? reviewedOnly,
        AwardsDbContext db)
    {
        var normalizedLimit = Math.Clamp(limit ?? 25, 1, 100);
        var normalizedOffset = Math.Max(offset ?? 0, 0);
        var normalizedStatus = NormalizeRiskFlagStatus(status, allowAll: true);
        if (normalizedStatus is null)
        {
            return Results.BadRequest(new { message = "Risk flag status must be open, resolved, dismissed or all." });
        }

        var riskQuery = db.RiskFlags.AsNoTracking();

        if (reviewedOnly == true)
        {
            riskQuery = riskQuery.Where(item => item.Status != "open");
        }

        if (!string.IsNullOrWhiteSpace(severity) && !string.Equals(severity, "all", StringComparison.OrdinalIgnoreCase))
        {
            var normalizedSeverity = severity.Trim().ToLowerInvariant();
            if (normalizedSeverity is not ("low" or "medium" or "high"))
            {
                return Results.BadRequest(new { message = "Risk flag severity must be low, medium, high or all." });
            }

            riskQuery = riskQuery.Where(item => item.Severity == normalizedSeverity);
        }

        if (normalizedStatus != "all")
        {
            riskQuery = riskQuery.Where(item => item.Status == normalizedStatus);
        }

        if (!string.IsNullOrWhiteSpace(query))
        {
            var pattern = $"%{query.Trim()}%";
            riskQuery = riskQuery.Where(item =>
                EF.Functions.ILike(item.Source, pattern) ||
                EF.Functions.ILike(item.Type, pattern) ||
                EF.Functions.ILike(item.Severity, pattern) ||
                EF.Functions.ILike(item.Status, pattern) ||
                EF.Functions.ILike(item.Summary, pattern) ||
                (item.TwitchUserId != null && EF.Functions.ILike(item.TwitchUserId, pattern)) ||
                EF.Functions.ILike(item.CreatedFromIp, pattern) ||
                (item.ReviewNote != null && EF.Functions.ILike(item.ReviewNote, pattern)) ||
                EF.Functions.ILike(item.MetadataJson, pattern));
        }

        var totalCount = await riskQuery.CountAsync();
        var severityCounts = await riskQuery
            .GroupBy(item => item.Severity)
            .Select(group => new AdminRiskCountDto(group.Key, group.Count()))
            .ToArrayAsync();
        var statusCounts = await riskQuery
            .GroupBy(item => item.Status)
            .Select(group => new AdminRiskCountDto(group.Key, group.Count()))
            .ToArrayAsync();

        var flags = await riskQuery
            .OrderByDescending(item => item.CreatedAt)
            .Skip(normalizedOffset)
            .Take(normalizedLimit)
            .ToArrayAsync();

        var items = flags.Select(AdminRiskFlagMappings.ToDto).ToArray();

        return Results.Ok(new AdminRiskFlagsResponse(
            items,
            totalCount,
            items.Length,
            normalizedOffset,
            normalizedLimit,
            normalizedOffset + items.Length < totalCount,
            severityCounts,
            statusCounts));
    }

    private static async Task<IResult> ResolveRiskFlag(
        HttpContext context,
        int riskFlagId,
        ResolveRiskFlagRequest request,
        AwardsDbContext db,
        IAdminAuditService adminAuditService)
    {
        var session = AdminEndpointConventions.CurrentSession(context);
        var riskFlag = await db.RiskFlags.FirstOrDefaultAsync(item => item.Id == riskFlagId);
        if (riskFlag is null)
        {
            return Results.NotFound();
        }

        var normalizedStatus = NormalizeRiskFlagStatus(request.Status, allowAll: false);
        if (normalizedStatus is null)
        {
            return Results.BadRequest(new { message = "Risk flag status must be open, resolved or dismissed." });
        }

        var previousStatus = riskFlag.Status;
        var previousReviewNote = riskFlag.ReviewNote;
        var normalizedReviewNote = NormalizeReviewNote(request.ReviewNote);

        riskFlag.Status = normalizedStatus;
        if (normalizedStatus == "open")
        {
            riskFlag.ReviewedAt = null;
            riskFlag.ReviewedByTwitchId = null;
            riskFlag.ReviewNote = null;
        }
        else
        {
            riskFlag.ReviewedAt = DateTimeOffset.UtcNow;
            riskFlag.ReviewedByTwitchId = session.TwitchUserId;
            riskFlag.ReviewNote = normalizedReviewNote;
        }

        adminAuditService.AddEntry(
            session.TwitchUserId,
            "risk.resolve",
            "risk-flag",
            riskFlag.Id.ToString(),
            $"Risk Flag {riskFlag.Id} wurde als {riskFlag.Status} markiert.",
            new
            {
                riskFlag.Type,
                riskFlag.Source,
                changes = BuildRiskResolutionChanges(previousStatus, riskFlag.Status, previousReviewNote, riskFlag.ReviewNote),
            },
            RequestMetadataReader.Read(context));

        await db.SaveChangesAsync(context.RequestAborted);
        return Results.Ok(new { saved = true, riskFlagId = riskFlag.Id, status = riskFlag.Status });
    }

    private static async Task<IResult> BulkResolveRiskFlags(
        HttpContext context,
        BulkResolveRiskFlagsRequest request,
        AwardsDbContext db,
        IAdminAuditService adminAuditService)
    {
        var session = AdminEndpointConventions.CurrentSession(context);
        var normalizedIds = request.RiskFlagIds
            .Distinct()
            .Take(100)
            .ToArray();
        if (normalizedIds.Length == 0)
        {
            return Results.BadRequest(new { message = "Bitte waehle mindestens einen Risikohinweis aus." });
        }

        var normalizedStatus = NormalizeRiskFlagStatus(request.Status, allowAll: false);
        if (normalizedStatus is null || normalizedStatus == "open")
        {
            return Results.BadRequest(new { message = "Bulk-Verarbeitung unterstuetzt erledigt oder verworfen." });
        }

        var normalizedReviewNote = NormalizeReviewNote(request.ReviewNote);
        if (string.IsNullOrWhiteSpace(normalizedReviewNote))
        {
            return Results.BadRequest(new { message = "Bulk-Verarbeitung braucht eine Review-Notiz." });
        }

        var riskFlags = await db.RiskFlags
            .Where(item => normalizedIds.Contains(item.Id))
            .ToArrayAsync(context.RequestAborted);

        if (riskFlags.Length != normalizedIds.Length)
        {
            return Results.BadRequest(new { message = "Mindestens ein Risikohinweis wurde nicht gefunden." });
        }

        if (riskFlags.Any(item => item.Severity != "low"))
        {
            return Results.BadRequest(new { message = "Bulk-Verarbeitung ist nur fuer Low-Severity-Hinweise erlaubt." });
        }

        if (riskFlags.Any(item => item.Status != "open"))
        {
            return Results.BadRequest(new { message = "Bulk-Verarbeitung ist nur fuer offene Hinweise erlaubt." });
        }

        var reviewedAt = DateTimeOffset.UtcNow;
        foreach (var riskFlag in riskFlags)
        {
            riskFlag.Status = normalizedStatus;
            riskFlag.ReviewNote = normalizedReviewNote;
            riskFlag.ReviewedAt = reviewedAt;
            riskFlag.ReviewedByTwitchId = session.TwitchUserId;
        }

        adminAuditService.AddEntry(
            session.TwitchUserId,
            "risk.bulk-resolve",
            "risk-flag",
            string.Join(",", normalizedIds),
            $"{normalizedIds.Length} Low-Risk Flags wurden als {normalizedStatus} markiert.",
            new
            {
                status = normalizedStatus,
                count = normalizedIds.Length,
                riskFlagIds = normalizedIds,
                changes = new[]
                {
                    new
                    {
                        field = "status",
                        label = "Status",
                        from = "open",
                        to = normalizedStatus,
                        sensitive = false,
                    },
                    new
                    {
                        field = "reviewNote",
                        label = "Review-Notiz",
                        from = "keine Notiz",
                        to = "Notiz vorhanden",
                        sensitive = true,
                    },
                },
            },
            RequestMetadataReader.Read(context));

        await db.SaveChangesAsync(context.RequestAborted);
        return Results.Ok(new { saved = true, count = normalizedIds.Length, status = normalizedStatus });
    }

    private static async Task<IResult> GetRiskRules(AwardsDbContext db)
    {
        var settings = await db.SiteSettings
            .AsNoTracking()
            .FirstOrDefaultAsync(item => item.Id == 1);
        if (settings is null)
        {
            return Results.NotFound();
        }

        return Results.Ok(new AdminRiskRulesResponse(RiskRuleSettings.Read(settings).Select(ToRiskRuleDto).ToArray()));
    }

    private static async Task<IResult> UpdateRiskRules(
        HttpContext context,
        UpdateRiskRulesRequest request,
        AwardsDbContext db,
        IAdminAuditService adminAuditService)
    {
        var session = AdminEndpointConventions.CurrentSession(context);
        var settings = await db.SiteSettings.FirstOrDefaultAsync(item => item.Id == 1);
        if (settings is null)
        {
            return Results.NotFound();
        }

        var before = RiskRuleSettings.Read(settings);
        var mergedRules = RiskRuleSettings.Defaults
            .Select(defaultRule =>
            {
                var requestRule = request.Rules.FirstOrDefault(item => item.Key == defaultRule.Key);
                return requestRule is null
                    ? defaultRule
                    : new RiskRuleSetting(
                        defaultRule.Key,
                        defaultRule.Label,
                        requestRule.Enabled,
                        requestRule.Threshold,
                        requestRule.WindowMinutes,
                        requestRule.Severity,
                        defaultRule.Description);
            })
            .ToArray();

        settings.RiskRulesJson = RiskRuleSettings.Serialize(mergedRules);
        var after = RiskRuleSettings.Read(settings);
        var changes = after
            .Select(rule =>
            {
                var previous = before.First(item => item.Key == rule.Key);
                return new
                {
                    field = rule.Key,
                    label = rule.Label,
                    from = $"{previous.Enabled}/{previous.Threshold}/{previous.WindowMinutes}/{previous.Severity}",
                    to = $"{rule.Enabled}/{rule.Threshold}/{rule.WindowMinutes}/{rule.Severity}",
                    sensitive = false,
                };
            })
            .Where(change => change.from != change.to)
            .ToArray();

        adminAuditService.AddEntry(
            session.TwitchUserId,
            "risk-rules.update",
            "site-settings",
            settings.Id.ToString(),
            "Risk-Regeln wurden aktualisiert.",
            new { changes },
            RequestMetadataReader.Read(context));

        await db.SaveChangesAsync(context.RequestAborted);
        return Results.Ok(new AdminRiskRulesResponse(after.Select(ToRiskRuleDto).ToArray()));
    }

    private static AdminRiskRuleDto ToRiskRuleDto(RiskRuleSetting rule) =>
        new(rule.Key, rule.Label, rule.Enabled, rule.Threshold, rule.WindowMinutes, rule.Severity, rule.Description);

    private static string? NormalizeReviewNote(string? reviewNote)
    {
        if (string.IsNullOrWhiteSpace(reviewNote))
        {
            return null;
        }

        var trimmedReviewNote = reviewNote.Trim();
        return trimmedReviewNote.Length <= 500 ? trimmedReviewNote : trimmedReviewNote[..500];
    }

    private static object[] BuildRiskResolutionChanges(
        string previousStatus,
        string currentStatus,
        string? previousReviewNote,
        string? currentReviewNote)
    {
        var changes = new List<object>
        {
            new
            {
                field = "status",
                label = "Status",
                from = previousStatus,
                to = currentStatus,
                sensitive = false,
            },
        };

        if (!string.Equals(previousReviewNote, currentReviewNote, StringComparison.Ordinal))
        {
            changes.Add(new
            {
                field = "reviewNote",
                label = "Review-Notiz",
                from = string.IsNullOrWhiteSpace(previousReviewNote) ? "keine Notiz" : "Notiz vorhanden",
                to = string.IsNullOrWhiteSpace(currentReviewNote) ? "keine Notiz" : "Notiz vorhanden",
                sensitive = true,
            });
        }

        return changes.ToArray();
    }

    private static string? NormalizeRiskFlagStatus(string? status, bool allowAll)
    {
        var normalizedStatus = string.IsNullOrWhiteSpace(status) ? "open" : status.Trim().ToLowerInvariant();
        return normalizedStatus switch
        {
            "open" or "resolved" or "dismissed" => normalizedStatus,
            "all" when allowAll => normalizedStatus,
            _ => null,
        };
    }
}
