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

namespace Backend.Endpoints;

public static class AdminExtrasEndpoints
{
    private static readonly string[] ContentPermissions = ["content", "settings"];
    private static readonly HashSet<string> AllowedShowactStatuses = new(StringComparer.OrdinalIgnoreCase)
    {
        "pending",
        "shortlisted",
        "accepted",
        "rejected",
    };

    public static RouteGroupBuilder MapAdminExtrasEndpoints(this RouteGroupBuilder group)
    {
        group.MapGet("/seasons/{seasonId:int}/showacts", GetShowactApplications)
            .AddEndpointFilter((context, next) => AdminEndpointConventions.RequireAnyPermission(context, next, ContentPermissions))
            .WithName("GetAdminShowactApplications")
            .WithOpenApi();

        group.MapPost("/showacts/{applicationId:int}/status", UpdateShowactStatus)
            .AddEndpointFilter((context, next) => AdminEndpointConventions.RequireWritePermission(context, next, "content"))
            .WithName("UpdateAdminShowactStatus")
            .WithOpenApi();

        group.MapDelete("/showacts/{applicationId:int}", DeleteShowactApplication)
            .AddEndpointFilter((context, next) => AdminEndpointConventions.RequireWritePermission(context, next, "content"))
            .WithName("DeleteAdminShowactApplication")
            .WithOpenApi();

        group.MapGet("/seasons/{seasonId:int}/sponsors", GetSponsors)
            .AddEndpointFilter((context, next) => AdminEndpointConventions.RequireAnyPermission(context, next, ContentPermissions))
            .WithName("GetAdminSponsors")
            .WithOpenApi();

        group.MapPost("/seasons/{seasonId:int}/sponsors", CreateSponsor)
            .AddEndpointFilter((context, next) => AdminEndpointConventions.RequireWritePermission(context, next, "content"))
            .WithName("CreateAdminSponsor")
            .WithOpenApi();

        group.MapPut("/sponsors/{sponsorId:int}", UpdateSponsor)
            .AddEndpointFilter((context, next) => AdminEndpointConventions.RequireWritePermission(context, next, "content"))
            .WithName("UpdateAdminSponsor")
            .WithOpenApi();

        group.MapDelete("/sponsors/{sponsorId:int}", DeleteSponsor)
            .AddEndpointFilter((context, next) => AdminEndpointConventions.RequireWritePermission(context, next, "content"))
            .WithName("DeleteAdminSponsor")
            .WithOpenApi();

        return group;
    }

    private static async Task<IResult> GetShowactApplications(int seasonId, AwardsDbContext db)
    {
        var applications = await db.ShowactApplications
            .AsNoTracking()
            .Where(item => item.SeasonId == seasonId)
            .OrderBy(item => item.Status == "pending" ? 0 : 1)
            .ThenByDescending(item => item.CreatedAt)
            .Select(item => ToDto(item))
            .ToArrayAsync();

        return Results.Ok(applications);
    }

    private static async Task<IResult> UpdateShowactStatus(
        HttpContext context,
        int applicationId,
        UpdateShowactStatusRequest request,
        AwardsDbContext db,
        IAdminAuditService adminAuditService)
    {
        var application = await db.ShowactApplications.FirstOrDefaultAsync(item => item.Id == applicationId);
        if (application is null)
        {
            return Results.NotFound();
        }

        var status = NormalizeStatus(request.Status);
        if (!AllowedShowactStatuses.Contains(status))
        {
            return Results.BadRequest(new { message = "Status muss pending, shortlisted, accepted oder rejected sein." });
        }

        var session = AdminEndpointConventions.CurrentSession(context);
        var before = new { application.Status, application.ReviewNote };
        application.Status = status;
        application.ReviewNote = NormalizeText(request.ReviewNote, 500);
        application.ReviewedByTwitchId = session.TwitchUserId;
        application.ReviewedAt = DateTimeOffset.UtcNow;

        adminAuditService.AddEntry(
            session.TwitchUserId,
            "showact.status",
            "showact-application",
            application.Id.ToString(),
            $"Showact-Bewerbung von {application.ArtistName} wurde auf {status} gesetzt.",
            new { before, after = new { application.Status, application.ReviewNote } },
            RequestMetadataReader.Read(context));

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

    private static async Task<IResult> DeleteShowactApplication(
        HttpContext context,
        int applicationId,
        AwardsDbContext db,
        IAdminAuditService adminAuditService)
    {
        var application = await db.ShowactApplications.FirstOrDefaultAsync(item => item.Id == applicationId);
        if (application is null)
        {
            return Results.NotFound();
        }

        var session = AdminEndpointConventions.CurrentSession(context);
        db.ShowactApplications.Remove(application);
        adminAuditService.AddEntry(
            session.TwitchUserId,
            "showact.delete",
            "showact-application",
            application.Id.ToString(),
            $"Showact-Bewerbung von {application.ArtistName} wurde geloescht.",
            new { application.ArtistName, application.ContactEmail, application.Status },
            RequestMetadataReader.Read(context));

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

    private static async Task<IResult> GetSponsors(int seasonId, AwardsDbContext db)
    {
        var sponsors = await db.Sponsors
            .AsNoTracking()
            .Where(item => item.SeasonId == seasonId)
            .OrderBy(item => item.SortOrder)
            .ThenBy(item => item.Name)
            .Select(item => ToDto(item))
            .ToArrayAsync();

        return Results.Ok(sponsors);
    }

    private static async Task<IResult> CreateSponsor(
        HttpContext context,
        int seasonId,
        UpsertSponsorRequest request,
        AwardsDbContext db,
        IAdminAuditService adminAuditService)
    {
        if (!await db.Seasons.AnyAsync(item => item.Id == seasonId, context.RequestAborted))
        {
            return Results.NotFound();
        }

        var sponsor = new Sponsor { SeasonId = seasonId, CreatedAt = DateTimeOffset.UtcNow };
        var validation = ApplySponsorRequest(sponsor, request);
        if (validation is not null)
        {
            return validation;
        }

        db.Sponsors.Add(sponsor);
        var session = AdminEndpointConventions.CurrentSession(context);
        adminAuditService.AddEntry(
            session.TwitchUserId,
            "sponsor.create",
            "sponsor",
            "new",
            $"Sponsor {sponsor.Name} wurde angelegt.",
            ToDto(sponsor),
            RequestMetadataReader.Read(context));

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

    private static async Task<IResult> UpdateSponsor(
        HttpContext context,
        int sponsorId,
        UpsertSponsorRequest request,
        AwardsDbContext db,
        IAdminAuditService adminAuditService)
    {
        var sponsor = await db.Sponsors.FirstOrDefaultAsync(item => item.Id == sponsorId, context.RequestAborted);
        if (sponsor is null)
        {
            return Results.NotFound();
        }

        var before = ToDto(sponsor);
        var validation = ApplySponsorRequest(sponsor, request);
        if (validation is not null)
        {
            return validation;
        }

        sponsor.UpdatedAt = DateTimeOffset.UtcNow;
        var session = AdminEndpointConventions.CurrentSession(context);
        adminAuditService.AddEntry(
            session.TwitchUserId,
            "sponsor.update",
            "sponsor",
            sponsor.Id.ToString(),
            $"Sponsor {sponsor.Name} wurde aktualisiert.",
            new { before, after = ToDto(sponsor) },
            RequestMetadataReader.Read(context));

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

    private static async Task<IResult> DeleteSponsor(
        HttpContext context,
        int sponsorId,
        AwardsDbContext db,
        IAdminAuditService adminAuditService)
    {
        var sponsor = await db.Sponsors.FirstOrDefaultAsync(item => item.Id == sponsorId, context.RequestAborted);
        if (sponsor is null)
        {
            return Results.NotFound();
        }

        var session = AdminEndpointConventions.CurrentSession(context);
        db.Sponsors.Remove(sponsor);
        adminAuditService.AddEntry(
            session.TwitchUserId,
            "sponsor.delete",
            "sponsor",
            sponsor.Id.ToString(),
            $"Sponsor {sponsor.Name} wurde geloescht.",
            ToDto(sponsor),
            RequestMetadataReader.Read(context));

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

    private static IResult? ApplySponsorRequest(Sponsor sponsor, UpsertSponsorRequest request)
    {
        var name = NormalizeText(request.Name, 120);
        if (string.IsNullOrWhiteSpace(name))
        {
            return Results.BadRequest(new { message = "Sponsor-Name ist erforderlich." });
        }

        var websiteUrl = NormalizeText(request.WebsiteUrl, 500);
        var logoUrl = NormalizeText(request.LogoUrl, 500);
        if (!IsBlankOrHttpUrl(websiteUrl) || !IsBlankOrHttpUrl(logoUrl))
        {
            return Results.BadRequest(new { message = "Sponsor-Links muessen gueltige http(s)-URLs sein." });
        }

        sponsor.Name = name;
        sponsor.WebsiteUrl = websiteUrl;
        sponsor.LogoUrl = logoUrl;
        sponsor.Description = NormalizeText(request.Description, 500);
        sponsor.Tier = NormalizeText(request.Tier, 80);
        if (string.IsNullOrWhiteSpace(sponsor.Tier))
        {
            sponsor.Tier = "Partner";
        }

        sponsor.SortOrder = Math.Clamp(request.SortOrder, 0, 9999);
        sponsor.IsVisible = request.IsVisible;
        return null;
    }

    private static SponsorDto ToDto(Sponsor sponsor) =>
        new(
            sponsor.Id,
            sponsor.SeasonId,
            sponsor.Name,
            sponsor.WebsiteUrl,
            sponsor.LogoUrl,
            sponsor.Description,
            sponsor.Tier,
            sponsor.SortOrder,
            sponsor.IsVisible);

    private static ShowactApplicationDto ToDto(ShowactApplication application) =>
        new(
            application.Id,
            application.SeasonId,
            application.ArtistName,
            application.ContactEmail,
            application.ContactDiscord,
            application.PlatformUrl,
            application.PerformanceType,
            application.Description,
            application.TechnicalNotes,
            application.ReferenceUrl,
            application.Status,
            application.ReviewNote,
            application.CreatedAt,
            application.ReviewedAt,
            application.FieldResponsesJson ?? "{}");

    private static string NormalizeStatus(string? value) =>
        string.IsNullOrWhiteSpace(value) ? "pending" : value.Trim().ToLowerInvariant();

    private static string NormalizeText(string? value, int maxLength)
    {
        var trimmed = (value ?? string.Empty).Trim();
        return trimmed.Length <= maxLength ? trimmed : trimmed[..maxLength];
    }

    private static bool IsBlankOrHttpUrl(string value) =>
        string.IsNullOrWhiteSpace(value)
        || (Uri.TryCreate(value, UriKind.Absolute, out var uri)
            && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps));
}
