using GameList.Contracts;
using GameList.Data;
using GameList.Domain;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using GameList.Infrastructure;

namespace GameList.Endpoints;

public static class SuggestEndpoints
{
    public static void MapSuggestEndpoints(this IEndpointRouteBuilder app)
    {
        var group = app.MapGroup("/api/suggestions").RequireAuthorization();

        group.MapGet("/mine", async (HttpContext ctx, AppDbContext db) =>
        {
            var player = await EndpointHelpers.GetAuthenticatedPlayer(ctx, db);
            if (player is null)
                return Results.Unauthorized();

            var mine = await db.Suggestions.AsNoTracking().Where(s => s.PlayerId == player.Id).Select(s => new
            {
                s.Id,
                s.PlayerId,
                s.Name,
                s.Genre,
                s.Description,
                s.ScreenshotUrl,
                s.YoutubeUrl,
                s.GameUrl,
                s.CreatedAt,
                s.MinPlayers,
                s.MaxPlayers,
                s.ParentSuggestionId
            }).ToListAsync();

            var ordered = mine.OrderBy(s => s.CreatedAt).Select(s => new SuggestionDto(s.Id, s.Name, s.Genre, s.Description, s.ScreenshotUrl, s.YoutubeUrl, s.GameUrl, s.MinPlayers, s.MaxPlayers, s.ParentSuggestionId));

            return Results.Ok(ordered);
        });

        group.MapPost("/", async ([FromBody] SuggestionRequest request, HttpContext ctx, AppDbContext db, IHttpClientFactory http) =>
        {
            if (string.IsNullOrWhiteSpace(request.Name) || request.Name.Length > 100)
            {
                return Results.BadRequest(new { error = "Name is required and must be <= 100 characters." });
            }

            if (!EndpointHelpers.IsValidImageUrl(request.ScreenshotUrl))
            {
                return Results.BadRequest(new { error = "Screenshot URL must be http(s) and end with an image file extension." });
            }

            if (!await EndpointHelpers.IsReachableImageAsync(request.ScreenshotUrl, http))
            {
                return Results.BadRequest(new { error = "Screenshot URL could not be validated as an image. Use a public image link (http/https, no redirects, max 5 MB)." });
            }

            if (!EndpointHelpers.IsValidHttpUrl(request.GameUrl))
                return Results.BadRequest(new { error = "Game URL must be http or https." });
            if (!EndpointHelpers.IsValidHttpUrl(request.YoutubeUrl))
                return Results.BadRequest(new { error = "YouTube URL must be http or https." });

            if (!ValidatePlayers(request.MinPlayers, request.MaxPlayers, out var playersError))
                return Results.BadRequest(new { error = playersError });

            var player = await EndpointHelpers.GetAuthenticatedPlayer(ctx, db);
            if (player is null)
                return Results.Unauthorized();

            var phase = await EndpointHelpers.GetPhase(db, player.Id);
            var usingJoker = phase == Phase.Vote && player.HasJoker;
            if (phase != Phase.Suggest && !usingJoker)
                return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase);

            if (string.IsNullOrWhiteSpace(player.DisplayName))
            {
                return Results.BadRequest(new { error = "Set a display name before submitting suggestions." });
            }

            var existingCount = await db.Suggestions.CountAsync(s => s.PlayerId == player.Id);
            if (!usingJoker && existingCount >= 5)
            {
                return Results.BadRequest(new { error = "You have reached the 5 suggestion limit." });
            }

            var suggestion = new Suggestion
            {
                PlayerId = player.Id,
                Name = request.Name.Trim(),
                Genre = EndpointHelpers.TrimTo(request.Genre, 50),
                Description = EndpointHelpers.TrimTo(request.Description, 500),
                ScreenshotUrl = EndpointHelpers.TrimTo(request.ScreenshotUrl, 2048),
                YoutubeUrl = EndpointHelpers.TrimTo(request.YoutubeUrl, 2048),
                GameUrl = EndpointHelpers.TrimTo(request.GameUrl, 2048),
                MinPlayers = request.MinPlayers,
                MaxPlayers = request.MaxPlayers
            };

            db.Suggestions.Add(suggestion);

            if (usingJoker)
            {
                player.HasJoker = false;
                await db.Players.ExecuteUpdateAsync(p => p.SetProperty(x => x.VotesFinal, false));
            }

            await db.SaveChangesAsync();

            return Results.Created($"/api/suggestions/{suggestion.Id}", new { suggestion.Id });
        }).AddEndpointFilter(new PhaseOrJokerFilter());

        group.MapDelete("/{id:int}", async (int id, HttpContext ctx, AppDbContext db) =>
        {
            var player = await EndpointHelpers.GetAuthenticatedPlayer(ctx, db);
            if (player is null)
                return Results.Unauthorized();

            var isAdmin = await EndpointHelpers.IsAdmin(ctx, db);

            if (!isAdmin)
            {
                var phase = await EndpointHelpers.GetPhase(db, player.Id);
                if (phase != Phase.Suggest)
                    return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase);
            }

            var suggestion = isAdmin ? await db.Suggestions.FirstOrDefaultAsync(s => s.Id == id) : await db.Suggestions.FirstOrDefaultAsync(s => s.Id == id && s.PlayerId == player.Id);
            if (suggestion == null)
                return Results.NotFound(new { error = "Suggestion not found." });

            // Break any links that pointed at this suggestion
            await db.Suggestions.Where(s => s.ParentSuggestionId == suggestion.Id).ExecuteUpdateAsync(s => s.SetProperty(x => x.ParentSuggestionId, (int?)null));

            // Remove votes for this suggestion to avoid orphaned vote rows or FK errors
            await db.Votes.Where(v => v.SuggestionId == suggestion.Id).ExecuteDeleteAsync();

            db.Suggestions.Remove(suggestion);
            await db.SaveChangesAsync();
            return Results.NoContent();
        });

        group.MapPut("/{id:int}", async (int id, [FromBody] SuggestionRequest request, HttpContext ctx, AppDbContext db, IHttpClientFactory http) =>
        {
            var player = await EndpointHelpers.GetAuthenticatedPlayer(ctx, db);
            var isAdmin = await EndpointHelpers.IsAdmin(ctx, db);

            if (!isAdmin && player is null)
                return Results.Unauthorized();

            if (string.IsNullOrWhiteSpace(request.Name) || request.Name.Length > 100)
            {
                return Results.BadRequest(new { error = "Name is required and must be <= 100 characters." });
            }

            if (!EndpointHelpers.IsValidImageUrl(request.ScreenshotUrl))
            {
                return Results.BadRequest(new { error = "Screenshot URL must be http(s) and end with an image file extension." });
            }

            if (!await EndpointHelpers.IsReachableImageAsync(request.ScreenshotUrl, http))
            {
                return Results.BadRequest(new { error = "Screenshot URL could not be validated as an image. Use a public image link (http/https, no redirects, max 5 MB)." });
            }

            if (!EndpointHelpers.IsValidHttpUrl(request.GameUrl))
                return Results.BadRequest(new { error = "Game URL must be http or https." });
            if (!EndpointHelpers.IsValidHttpUrl(request.YoutubeUrl))
                return Results.BadRequest(new { error = "YouTube URL must be http or https." });

            if (!ValidatePlayers(request.MinPlayers, request.MaxPlayers, out var playersError))
                return Results.BadRequest(new { error = playersError });

            var suggestion = await db.Suggestions.FirstOrDefaultAsync(s => s.Id == id);
            if (suggestion == null)
                return Results.NotFound(new { error = "Suggestion not found." });

            if (!isAdmin)
            {
                if (suggestion.PlayerId != player!.Id)
                    return Results.Unauthorized();

                var phase = await EndpointHelpers.GetPhase(db, player.Id);
                if (phase == Phase.Results)
                    return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase);

                var inSuggest = phase == Phase.Suggest;
                var inVote = phase == Phase.Vote;

                if (inSuggest)
                {
                    suggestion.Name = request.Name.Trim();
                }
                else if (inVote)
                {
                    // Title locked in vote; allow other fields
                }
                else
                {
                    return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase);
                }

                suggestion.Genre = EndpointHelpers.TrimTo(request.Genre, 50);
                suggestion.Description = EndpointHelpers.TrimTo(request.Description, 500);
                suggestion.ScreenshotUrl = EndpointHelpers.TrimTo(request.ScreenshotUrl, 2048);
                suggestion.YoutubeUrl = EndpointHelpers.TrimTo(request.YoutubeUrl, 2048);
                suggestion.GameUrl = EndpointHelpers.TrimTo(request.GameUrl, 2048);
                suggestion.MinPlayers = request.MinPlayers;
                suggestion.MaxPlayers = request.MaxPlayers;
            }
            else
            {
                // Admins can edit anytime
                suggestion.Name = request.Name.Trim();
                suggestion.Genre = EndpointHelpers.TrimTo(request.Genre, 50);
                suggestion.Description = EndpointHelpers.TrimTo(request.Description, 500);
                suggestion.ScreenshotUrl = EndpointHelpers.TrimTo(request.ScreenshotUrl, 2048);
                suggestion.YoutubeUrl = EndpointHelpers.TrimTo(request.YoutubeUrl, 2048);
                suggestion.GameUrl = EndpointHelpers.TrimTo(request.GameUrl, 2048);
                suggestion.MinPlayers = request.MinPlayers;
                suggestion.MaxPlayers = request.MaxPlayers;
            }

            await db.SaveChangesAsync();

            return Results.Ok(new
            {
                suggestion.Id,
                suggestion.Name,
                suggestion.Genre,
                suggestion.Description,
                suggestion.ScreenshotUrl,
                suggestion.YoutubeUrl,
                suggestion.GameUrl,
                suggestion.MinPlayers,
                suggestion.MaxPlayers
            });
        });

        group.MapGet("/all", async (HttpContext ctx, AppDbContext db) =>
        {
            var player = await EndpointHelpers.GetAuthenticatedPlayer(ctx, db);
            if (player is null)
                return Results.Unauthorized();

            var phase = await EndpointHelpers.GetPhase(db, player.Id);
            if (phase < Phase.Vote)
                return EndpointHelpers.PhaseMismatch(Phase.Vote, phase);

            var all = await db.Suggestions.AsNoTracking().Include(s => s.Player).Select(s => new
            {
                s.Id,
                s.Name,
                s.Genre,
                s.Description,
                s.ScreenshotUrl,
                s.YoutubeUrl,
                s.GameUrl,
                s.MinPlayers,
                s.MaxPlayers,
                Author = s.Player!.DisplayName,
                s.CreatedAt,
                s.ParentSuggestionId,
                IsOwner = s.PlayerId == player.Id
            }).ToListAsync();

            var rootIndex = EndpointHelpers.BuildLinkRoots(all.Select(s => (s.Id, s.ParentSuggestionId)));
            var nameLookup = all.ToDictionary(s => s.Id, s => s.Name);

            var ordered = all.OrderBy(s => s.CreatedAt).Select(s =>
            {
                var linkedIds = EndpointHelpers.LinkedIdsFor(s.Id, rootIndex).Where(id => id != s.Id).ToList();

                return new
                {
                    s.Id,
                    s.Name,
                    s.Genre,
                    s.Description,
                    s.ScreenshotUrl,
                    s.YoutubeUrl,
                    s.GameUrl,
                    s.MinPlayers,
                    s.MaxPlayers,
                    s.Author,
                    s.ParentSuggestionId,
                    s.IsOwner,
                    LinkedIds = linkedIds,
                    LinkedTitles = linkedIds.Where(nameLookup.ContainsKey).Select(id => nameLookup[id]).ToList()
                };
            });

            return Results.Ok(ordered);
        });
    }

    private static bool ValidatePlayers(int? minPlayers, int? maxPlayers, out string? error)
    {
        error = null;
        if (minPlayers is null && maxPlayers is null)
            return true;

        if (minPlayers is < 1 or > 32)
        {
            error = "Min players must be between 1 and 32.";
            return false;
        }

        if (maxPlayers is < 1 or > 32)
        {
            error = "Max players must be between 1 and 32.";
            return false;
        }

        if (minPlayers is null || maxPlayers is null)
        {
            error = "Provide both min and max players.";
            return false;
        }

        if (minPlayers > maxPlayers)
        {
            error = "Min players cannot exceed max players.";
            return false;
        }

        return true;
    }
}
