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

namespace Backend.Endpoints;

public static class AdminDashboardEndpoints
{
    public static RouteGroupBuilder MapAdminDashboardEndpoints(this RouteGroupBuilder group)
    {
        group.MapGet("/dashboard", GetDashboard)
            .AddEndpointFilter((context, next) => AdminEndpointConventions.RequirePermission(context, next, AdminPermissionCatalog.Dashboard))
            .WithName("GetAdminDashboard")
            .WithOpenApi();
        group.MapGet("/audit-entries", GetAuditEntries)
            .AddEndpointFilter((context, next) => AdminEndpointConventions.RequirePermission(context, next, AdminPermissionCatalog.Audit))
            .WithName("GetAdminAuditEntries")
            .WithOpenApi();
        return group;
    }

    private static async Task<IResult> GetDashboard(int? seasonId, AwardsDbContext db, HttpContext context)
    {
        var canViewAuditIp = CanViewAuditIp(context);
        var selectedSeason = seasonId.HasValue
            ? await db.Seasons.AsNoTracking().FirstOrDefaultAsync(item => item.Id == seasonId.Value)
            : await db.Seasons.AsNoTracking().FirstOrDefaultAsync(item => item.IsCurrent);
        if (selectedSeason is null)
        {
            return Results.NotFound();
        }

        var selectedSeasonId = selectedSeason.Id;
        var phaseKey = SeasonMappings.NormalizePhaseKey(selectedSeason.CurrentPhase);
        var nominationCount = await db.Nominations.CountAsync(item => item.SeasonId == selectedSeasonId);
        var voteCount = await db.VoteEntries.CountAsync(item => item.Ballot.SeasonId == selectedSeasonId);
        var categoryCount = await db.Categories.CountAsync(item => item.SeasonId == selectedSeasonId);
        var reviewCount = await db.Nominations.CountAsync(item => item.SeasonId == selectedSeasonId && item.Status == "pending");
        var riskFlagCount = await db.RiskFlags.CountAsync(item =>
            item.Status == "open" &&
            (item.SeasonId == selectedSeasonId || item.SeasonId == null));
        var globalRiskFlagCount = await db.RiskFlags.CountAsync(item =>
            item.Status == "open" &&
            item.SeasonId == null);

        var topCategories = phaseKey == "nomination"
            ? await BuildTopNominationCategoriesAsync(db, selectedSeasonId)
            : await BuildTopVotingCategoriesAsync(db, selectedSeasonId);

        var riskFlags = await db.RiskFlags
            .AsNoTracking()
            .Where(item =>
                item.Status == "open" &&
                (item.SeasonId == selectedSeasonId || item.SeasonId == null))
            .OrderByDescending(item => item.CreatedAt)
            .Take(8)
            .ToArrayAsync();
        var riskFlagDtos = riskFlags.Select(AdminRiskFlagMappings.ToDto).ToArray();

        var auditEntries = await db.AdminAuditEntries
            .AsNoTracking()
            .OrderByDescending(item => item.CreatedAt)
            .Take(8)
            .Select(item => new AdminAuditEntryDto(
                item.Id,
                item.AdminTwitchUserId,
                item.ActionType,
                item.EntityType,
                item.EntityId,
                item.Summary,
                item.CreatedAt,
                item.MetadataJson,
                canViewAuditIp ? item.CreatedFromIp : null,
                item.UserAgent))
            .ToArrayAsync();

        var activityItems = auditEntries
            .Take(6)
            .Select(item => new AdminActivityDto(item.Summary, $"{Math.Max(1, (int)Math.Round((DateTimeOffset.UtcNow - item.CreatedAt).TotalMinutes))} Min."))
            .ToArray();

        return Results.Ok(new AdminDashboardResponse(
            selectedSeason.Id,
            selectedSeason.Year,
            selectedSeason.Name,
            selectedSeason.IsCurrent,
            new[]
            {
                new AdminMetricDto("Nominierungen", nominationCount, $"Gespeicherte Einreichungen im Award-Jahr {selectedSeason.Year}"),
                new AdminMetricDto("Stimmen", voteCount, $"Abgegebene Stimmen im Award-Jahr {selectedSeason.Year}"),
                new AdminMetricDto("Kategorien", categoryCount, $"Aktive Kategorien im Award-Jahr {selectedSeason.Year}"),
                new AdminMetricDto("Reviews offen", reviewCount, "Offene Nominierungen mit Review-Bedarf in diesem Jahr"),
                new AdminMetricDto(
                    "Risikohinweise",
                    riskFlagCount,
                    globalRiskFlagCount > 0
                        ? $"Offene Hinweise fuer {selectedSeason.Year}, inklusive {globalRiskFlagCount} globaler Hinweise"
                        : $"Offene Hinweise fuer {selectedSeason.Year}"),
            },
            activityItems,
            topCategories,
            riskFlagDtos,
            auditEntries));
    }

    private static async Task<AdminTopCategoryDto[]> BuildTopVotingCategoriesAsync(AwardsDbContext db, int seasonId)
    {
        var categoryNames = await db.VoteEntries
            .AsNoTracking()
            .Where(item => item.Ballot.SeasonId == seasonId)
            .Select(item => item.Category.Name)
            .ToListAsync();

        return categoryNames
            .GroupBy(name => name)
            .Select(grouping => new AdminTopCategoryDto(grouping.Key, grouping.Count(), "Stimmen"))
            .OrderByDescending(item => item.Value)
            .Take(5)
            .ToArray();
    }

    private static async Task<AdminTopCategoryDto[]> BuildTopNominationCategoriesAsync(AwardsDbContext db, int seasonId)
    {
        var nominationCategories = await db.Nominations
            .AsNoTracking()
            .Where(item => item.SeasonId == seasonId)
            .Select(item => new
            {
                CategoryName = item.Category != null ? item.Category.Name : null,
                item.CategoryGroupName,
            })
            .ToListAsync();

        return nominationCategories
            .Select(item => string.IsNullOrWhiteSpace(item.CategoryName)
                ? string.IsNullOrWhiteSpace(item.CategoryGroupName) ? "Ohne Kategorie" : item.CategoryGroupName
                : item.CategoryName)
            .GroupBy(name => name)
            .Select(grouping => new AdminTopCategoryDto(grouping.Key, grouping.Count(), "Nominierungen"))
            .OrderByDescending(item => item.Value)
            .Take(5)
            .ToArray();
    }

    private static async Task<IResult> GetAuditEntries(
        int? limit,
        string? query,
        string? admin,
        string? action,
        string? entityType,
        DateTimeOffset? from,
        DateTimeOffset? to,
        string? cursor,
        AwardsDbContext db,
        HttpContext context)
    {
        var normalizedLimit = Math.Clamp(limit ?? 100, 1, 500);
        var search = query?.Trim();
        var canViewAuditIp = CanViewAuditIp(context);
        var auditQuery = db.AdminAuditEntries.AsNoTracking();

        if (!TryDecodeAuditCursor(cursor, out var decodedCursor))
        {
            return Results.BadRequest(new { message = "Invalid audit cursor." });
        }

        if (!string.IsNullOrWhiteSpace(search))
        {
            var pattern = $"%{search}%";
            auditQuery = auditQuery.Where(item =>
                EF.Functions.ILike(item.AdminTwitchUserId, pattern) ||
                EF.Functions.ILike(item.ActionType, pattern) ||
                EF.Functions.ILike(item.EntityType, pattern) ||
                EF.Functions.ILike(item.EntityId, pattern) ||
                EF.Functions.ILike(item.Summary, pattern) ||
                EF.Functions.ILike(item.MetadataJson, pattern) ||
                EF.Functions.ILike(item.UserAgent, pattern) ||
                (canViewAuditIp && EF.Functions.ILike(item.CreatedFromIp, pattern)));
        }

        if (!string.IsNullOrWhiteSpace(admin))
        {
            var normalizedAdmin = admin.Trim();
            auditQuery = auditQuery.Where(item => item.AdminTwitchUserId == normalizedAdmin);
        }

        if (!string.IsNullOrWhiteSpace(action))
        {
            var normalizedAction = action.Trim();
            auditQuery = auditQuery.Where(item => item.ActionType == normalizedAction);
        }

        if (!string.IsNullOrWhiteSpace(entityType))
        {
            var normalizedEntityType = entityType.Trim();
            auditQuery = auditQuery.Where(item => item.EntityType == normalizedEntityType);
        }

        if (from.HasValue)
        {
            auditQuery = auditQuery.Where(item => item.CreatedAt >= from.Value);
        }

        if (to.HasValue)
        {
            auditQuery = auditQuery.Where(item => item.CreatedAt <= to.Value);
        }

        var totalCount = await auditQuery.CountAsync();

        if (decodedCursor is not null)
        {
            auditQuery = auditQuery.Where(item =>
                item.CreatedAt < decodedCursor.CreatedAt ||
                (item.CreatedAt == decodedCursor.CreatedAt && item.Id < decodedCursor.Id));
        }

        var page = await auditQuery
            .OrderByDescending(item => item.CreatedAt)
            .ThenByDescending(item => item.Id)
            .Take(normalizedLimit + 1)
            .Select(item => new AdminAuditEntryDto(
                item.Id,
                item.AdminTwitchUserId,
                item.ActionType,
                item.EntityType,
                item.EntityId,
                item.Summary,
                item.CreatedAt,
                item.MetadataJson,
                canViewAuditIp ? item.CreatedFromIp : null,
                item.UserAgent))
            .ToArrayAsync();

        var hasMore = page.Length > normalizedLimit;
        var entries = page.Take(normalizedLimit).ToArray();
        var nextCursor = hasMore && entries.Length > 0
            ? EncodeAuditCursor(entries[^1])
            : null;

        return Results.Ok(new AdminAuditEntriesResponse(
            entries,
            totalCount,
            entries.Length,
            nextCursor,
            normalizedLimit));
    }

    private static string EncodeAuditCursor(AdminAuditEntryDto entry) =>
        $"{entry.CreatedAt.UtcTicks}:{entry.Id}";

    private static bool CanViewAuditIp(HttpContext context) =>
        AdminRoles.IsPrivilegedFullControlRole(context.GetCurrentSession()?.Role);

    private static bool TryDecodeAuditCursor(string? cursor, out AuditCursor? decodedCursor)
    {
        decodedCursor = null;
        if (string.IsNullOrWhiteSpace(cursor))
        {
            return true;
        }

        var parts = cursor.Split(':', 2);
        if (parts.Length != 2 ||
            !long.TryParse(parts[0], out var ticks) ||
            !int.TryParse(parts[1], out var id))
        {
            return false;
        }

        try
        {
            decodedCursor = new AuditCursor(new DateTimeOffset(ticks, TimeSpan.Zero), id);
            return true;
        }
        catch (ArgumentOutOfRangeException)
        {
            return false;
        }
    }

    private sealed record AuditCursor(DateTimeOffset CreatedAt, int Id);
}
