using GameList.Contracts;
using GameList.Data;
using GameList.Domain;
using Microsoft.EntityFrameworkCore;

namespace GameList.Endpoints;

internal sealed class VoteWorkflowService(AppDbContext db)
{
    public async Task<IResult> GetMineAsync(Player player)
    {
        var phase = await EndpointHelpers.GetCurrentPhaseAsync(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);
    }

    public async Task<IResult> UpsertAsync(Player player, VoteRequest request)
    {
        if (request.Score is < 0 or > 10)
            return EndpointHelpers.BadRequestError("Score must be between 0 and 10.");

        if (player.VotesFinal)
            return EndpointHelpers.BadRequestError("Votes are finalized. Unfinalize before changing scores.");

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

        if (string.IsNullOrWhiteSpace(player.DisplayName))
            return EndpointHelpers.BadRequestError("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 EndpointHelpers.BadRequestError("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 VoteUpsertResponse(linkedIds, request.Score));
    }

    public async Task<IResult> SetFinalizeAsync(Player player, VoteFinalizeRequest request)
    {
        var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
        if (phase != Phase.Vote)
            return EndpointHelpers.PhaseMismatch(Phase.Vote, phase);

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