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

namespace Backend.Endpoints;

public static class AdminArchiveEndpoints
{
    public static RouteGroupBuilder MapAdminArchiveEndpoints(this RouteGroupBuilder group)
    {
        group.MapGet("/archived-winners", GetArchivedWinners)
            .AddEndpointFilter((context, next) => AdminEndpointConventions.RequirePermission(context, next, AdminPermissionCatalog.Winners))
            .WithName("GetAdminArchivedWinners");

        group.MapPost("/archived-winners", CreateArchivedWinner)
            .AddEndpointFilter((context, next) => AdminEndpointConventions.RequireWritePermission(context, next, AdminPermissionCatalog.Winners))
            .WithName("CreateAdminArchivedWinner");

        group.MapPut("/archived-winners/{archivedWinnerId:int}", UpdateArchivedWinner)
            .AddEndpointFilter((context, next) => AdminEndpointConventions.RequireWritePermission(context, next, AdminPermissionCatalog.Winners))
            .WithName("UpdateAdminArchivedWinner");

        group.MapDelete("/archived-winners/{archivedWinnerId:int}", DeleteArchivedWinner)
            .AddEndpointFilter((context, next) => AdminEndpointConventions.RequireWritePermission(context, next, AdminPermissionCatalog.Winners))
            .WithName("DeleteAdminArchivedWinner");

        return group;
    }

    private static async Task<IResult> GetArchivedWinners(AwardsDbContext db, CancellationToken cancellationToken)
    {
        var items = await db.ArchivedWinners
            .AsNoTracking()
            .OrderByDescending(item => item.Year)
            .ThenBy(item => item.Category)
            .ThenBy(item => item.Subcategory)
            .ThenBy(item => item.WinnerName)
            .Select(item => ToDto(item))
            .ToArrayAsync(cancellationToken);

        return Results.Ok(items);
    }

    private static async Task<IResult> CreateArchivedWinner(
        HttpContext context,
        UpsertArchivedWinnerRequest request,
        AwardsDbContext db,
        IAdminAuditService adminAuditService)
    {
        var session = AdminEndpointConventions.CurrentSession(context);
        var validation = ValidateRequest(request);
        if (validation is not null)
        {
            return validation;
        }

        var normalized = NormalizeRequest(request);
        var duplicateExists = await db.ArchivedWinners.AnyAsync(item =>
            item.Year == normalized.Year
            && item.Category == normalized.Category
            && item.Subcategory == normalized.Subcategory,
            context.RequestAborted);
        if (duplicateExists)
        {
            return Results.BadRequest(new { message = "Für dieses Jahr, diese Kategorie und Unterkategorie existiert bereits ein Archivgewinner." });
        }

        var archivedWinner = new ArchivedWinner
        {
            Year = normalized.Year,
            Category = normalized.Category,
            Subcategory = normalized.Subcategory,
            WinnerName = normalized.WinnerName,
            WinnerUrl = normalized.WinnerUrl,
        };

        db.ArchivedWinners.Add(archivedWinner);

        adminAuditService.AddEntry(
            session.TwitchUserId,
            "archived-winner.create",
            "archivedWinner",
            $"{normalized.Year}:{normalized.Category}:{normalized.Subcategory}",
            $"Archivgewinner {normalized.Year} · {normalized.Category} · {normalized.Subcategory} angelegt.",
            new
            {
                normalized.Year,
                normalized.Category,
                normalized.Subcategory,
                normalized.WinnerName,
                normalized.WinnerUrl,
            },
            RequestMetadataReader.Read(context));

        await db.SaveChangesAsync(context.RequestAborted);
        return Results.Ok(new { saved = true, entry = ToDto(archivedWinner) });
    }

    private static async Task<IResult> UpdateArchivedWinner(
        HttpContext context,
        int archivedWinnerId,
        UpsertArchivedWinnerRequest request,
        AwardsDbContext db,
        IAdminAuditService adminAuditService)
    {
        var session = AdminEndpointConventions.CurrentSession(context);
        var validation = ValidateRequest(request);
        if (validation is not null)
        {
            return validation;
        }

        var archivedWinner = await db.ArchivedWinners.FirstOrDefaultAsync(item => item.Id == archivedWinnerId, context.RequestAborted);
        if (archivedWinner is null)
        {
            return Results.NotFound();
        }

        var normalized = NormalizeRequest(request);
        var duplicateExists = await db.ArchivedWinners.AnyAsync(item =>
            item.Id != archivedWinnerId
            && item.Year == normalized.Year
            && item.Category == normalized.Category
            && item.Subcategory == normalized.Subcategory,
            context.RequestAborted);
        if (duplicateExists)
        {
            return Results.BadRequest(new { message = "Für dieses Jahr, diese Kategorie und Unterkategorie existiert bereits ein Archivgewinner." });
        }

        archivedWinner.Year = normalized.Year;
        archivedWinner.Category = normalized.Category;
        archivedWinner.Subcategory = normalized.Subcategory;
        archivedWinner.WinnerName = normalized.WinnerName;
        archivedWinner.WinnerUrl = normalized.WinnerUrl;
        archivedWinner.UpdatedAt = DateTimeOffset.UtcNow;

        adminAuditService.AddEntry(
            session.TwitchUserId,
            "archived-winner.update",
            "archivedWinner",
            archivedWinner.Id.ToString(),
            $"Archivgewinner {normalized.Year} · {normalized.Category} · {normalized.Subcategory} aktualisiert.",
            new
            {
                archivedWinner.Id,
                normalized.Year,
                normalized.Category,
                normalized.Subcategory,
                normalized.WinnerName,
                normalized.WinnerUrl,
            },
            RequestMetadataReader.Read(context));

        await db.SaveChangesAsync(context.RequestAborted);
        return Results.Ok(new { saved = true, entry = ToDto(archivedWinner) });
    }

    private static async Task<IResult> DeleteArchivedWinner(
        HttpContext context,
        int archivedWinnerId,
        AwardsDbContext db,
        IAdminAuditService adminAuditService)
    {
        var session = AdminEndpointConventions.CurrentSession(context);
        var archivedWinner = await db.ArchivedWinners.FirstOrDefaultAsync(item => item.Id == archivedWinnerId, context.RequestAborted);
        if (archivedWinner is null)
        {
            return Results.NotFound();
        }

        db.ArchivedWinners.Remove(archivedWinner);

        adminAuditService.AddEntry(
            session.TwitchUserId,
            "archived-winner.delete",
            "archivedWinner",
            archivedWinner.Id.ToString(),
            $"Archivgewinner {archivedWinner.Year} · {archivedWinner.Category} · {archivedWinner.Subcategory} gelöscht.",
            new
            {
                archivedWinner.Id,
                archivedWinner.Year,
                archivedWinner.Category,
                archivedWinner.Subcategory,
                archivedWinner.WinnerName,
                archivedWinner.WinnerUrl,
            },
            RequestMetadataReader.Read(context));

        await db.SaveChangesAsync(context.RequestAborted);
        return Results.Ok(new { deleted = true, archivedWinnerId });
    }

    private static AdminArchivedWinnerItemDto ToDto(ArchivedWinner item) =>
        new(
            item.Id,
            item.Year,
            item.Category,
            item.Subcategory,
            item.WinnerName,
            item.WinnerUrl,
            item.CreatedAt,
            item.UpdatedAt);

    private static IResult? ValidateRequest(UpsertArchivedWinnerRequest request)
    {
        if (request.Year < 2000 || request.Year > 3000)
        {
            return Results.BadRequest(new { message = "Bitte ein gültiges Archivjahr angeben." });
        }

        if (string.IsNullOrWhiteSpace(request.Category))
        {
            return Results.BadRequest(new { message = "Die Kategorie darf nicht leer sein." });
        }

        if (string.IsNullOrWhiteSpace(request.Subcategory))
        {
            return Results.BadRequest(new { message = "Die Unterkategorie darf nicht leer sein." });
        }

        if (string.IsNullOrWhiteSpace(request.WinnerName))
        {
            return Results.BadRequest(new { message = "Der Gewinnername darf nicht leer sein." });
        }

        var winnerUrl = request.WinnerUrl?.Trim() ?? string.Empty;
        if (!Uri.TryCreate(winnerUrl, UriKind.Absolute, out var uri)
            || (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps))
        {
            return Results.BadRequest(new { message = "Bitte einen gültigen http- oder https-Link angeben." });
        }

        return null;
    }

    private static (int Year, string Category, string Subcategory, string WinnerName, string WinnerUrl) NormalizeRequest(UpsertArchivedWinnerRequest request) =>
        (
            request.Year,
            SeasonMappings.NormalizePlainTextContent(request.Category),
            SeasonMappings.NormalizePlainTextContent(request.Subcategory),
            SeasonMappings.NormalizePlainTextContent(request.WinnerName),
            request.WinnerUrl.Trim());
}
