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 VoteEndpoints
{
    public static void MapVoteEndpoints(this IEndpointRouteBuilder app)
    {
        var group = app.MapGroup("/api/votes").RequireAuthorization().AddEndpointFilter(new PhaseRequirementFilter(Phase.Vote));

        group.MapGet("/mine", 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 votes = await db.Votes.AsNoTracking().Where(v => v.PlayerId == player.Id).Select(v => new
            {
                v.SuggestionId,
                v.Score
            }).ToListAsync();

            return Results.Ok(votes);
        });

        group.MapPost("/", async ([FromBody] VoteRequest request, HttpContext ctx, AppDbContext db) =>
        {
            if (request.Score is < 0 or > 10)
                return Results.BadRequest(new { error = "Score must be between 0 and 10." });

            var player = await EndpointHelpers.GetAuthenticatedPlayer(ctx, db);
            if (player is null)
                return Results.Unauthorized();
            if (player.VotesFinal)
                return Results.BadRequest(new { error = "Votes are finalized. Unfinalize before changing scores." });

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

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

            var linkMap = await db.Suggestions.AsNoTracking().Select(s => new
            {
                s.Id,
                s.ParentSuggestionId
            }).ToListAsync();
            var rootIndex = EndpointHelpers.BuildLinkRoots(linkMap.Select(s => (s.Id, s.ParentSuggestionId)));
            if (!rootIndex.ContainsKey(request.SuggestionId))
                return Results.BadRequest(new { error = "Suggestion not found." });

            var linkedIds = EndpointHelpers.LinkedIdsFor(request.SuggestionId, rootIndex);
            if (linkedIds.Count == 0)
                linkedIds.Add(request.SuggestionId);

            var existingVotes = await db.Votes.Where(v => v.PlayerId == player.Id && linkedIds.Contains(v.SuggestionId)).ToListAsync();

            foreach (var suggestionId in linkedIds)
            {
                var vote = existingVotes.FirstOrDefault(v => v.SuggestionId == suggestionId);
                if (vote == null)
                {
                    db.Votes.Add(new Vote
                    {
                        PlayerId = player.Id,
                        SuggestionId = suggestionId,
                        Score = request.Score
                    });
                }
                else
                {
                    vote.Score = request.Score;
                }
            }

            await db.SaveChangesAsync();
            return Results.Ok(new
            {
                SuggestionIds = linkedIds,
                request.Score
            });
        });

        group.MapPost("/finalize", async ([FromBody] VoteFinalizeRequest request, 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);

            player.VotesFinal = request.Final;
            await db.SaveChangesAsync();
            return Results.Ok(new { player.VotesFinal });
        });
    }
}
