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

namespace GameList.Endpoints;

public static class AdminEndpoints
{
    public static void MapAdminEndpoints(this IEndpointRouteBuilder app)
    {
        var admin = app.MapGroup("/api/admin")
            .RequireAuthorization()
            .AddEndpointFilter<AdminOnlyFilter>();

        admin.MapPost("/results", async ([FromBody] Contracts.ResultsOpenRequest request, HttpContext ctx, AppDbContext db) =>
        {
            var state = await db.AppState.FirstAsync();
            state.ResultsOpen = request.ResultsOpen;
            state.UpdatedAt = DateTimeOffset.UtcNow;

            if (request.ResultsOpen)
            {
                await db.Players.ExecuteUpdateAsync(p => p.SetProperty(x => x.CurrentPhase, Phase.Results));
            }
            else
            {
                await db.Players.ExecuteUpdateAsync(p => p.SetProperty(x => x.CurrentPhase, Phase.Vote)
                                                         .SetProperty(x => x.VotesFinal, false));
            }

            await db.SaveChangesAsync();
            var currentState = await db.AppState.AsNoTracking().FirstAsync();
            return Results.Ok(new { currentState.ResultsOpen, currentState.UpdatedAt });
        });

        admin.MapGet("/vote-status", async (HttpContext ctx, AppDbContext db) =>
        {
            var voters = await db.Players
                .AsNoTracking()
                .Include(p => p.Suggestions)
                .OrderBy(p => p.DisplayName ?? p.Username)
                .Select(p => new VoteStatusDto(p.Id,
                                               p.DisplayName ?? p.Username,
                                               p.Username,
                                               p.CurrentPhase,
                                               p.VotesFinal,
                                               p.HasJoker,
                                               p.Suggestions.Count,
                                               p.Suggestions.Select(s => s.Name).ToList()))
                .ToListAsync();

            var waiting = voters.Where(v => !v.Finalized).Select(v => v.Name).ToList();
            var ready = waiting.Count == 0;
            return Results.Ok(new { voters, ready, waiting });
        });

        admin.MapPost("/joker", async ([FromBody] GrantJokerRequest request, HttpContext ctx, AppDbContext db) =>
        {
            var player = await db.Players.FirstOrDefaultAsync(p => p.Id == request.PlayerId);
            if (player is null) return Results.NotFound(new { error = "Player not found." });

            var phase = await EndpointHelpers.GetPhase(db, player.Id);
            if (phase != Phase.Vote)
                return Results.BadRequest(new { error = "Player must be in the Vote phase to receive a joker." });

            player.HasJoker = true;
            player.VotesFinal = false;
            await db.SaveChangesAsync();

            return Results.Ok(new { player.Id, player.HasJoker });
        });

        admin.MapDelete("/players/{playerId:guid}", async (Guid playerId, HttpContext ctx, AppDbContext db) =>
        {
            var player = await db.Players
                .Include(p => p.Suggestions)
                .FirstOrDefaultAsync(p => p.Id == playerId);
            if (player is null) return Results.NotFound(new { error = "Player not found." });

            await using var tx = await db.Database.BeginTransactionAsync();

            // Remove votes cast by the player
            await db.Votes.Where(v => v.PlayerId == playerId).ExecuteDeleteAsync();

            // Collect suggestions authored by the player
            var suggestionIds = player.Suggestions.Select(s => s.Id).ToList();
            if (suggestionIds.Count > 0)
            {
                // Break links pointing to these suggestions
                await db.Suggestions
                    .Where(s => s.ParentSuggestionId != null && suggestionIds.Contains(s.ParentSuggestionId.Value))
                    .ExecuteUpdateAsync(s => s.SetProperty(x => x.ParentSuggestionId, (int?)null));

                // Remove votes for these suggestions to avoid orphaned rows
                await db.Votes.Where(v => suggestionIds.Contains(v.SuggestionId)).ExecuteDeleteAsync();
            }

            // Delete player (cascades suggestions)
            db.Players.Remove(player);
            await db.SaveChangesAsync();
            await tx.CommitAsync();

            return Results.Ok(new { DeletedPlayerId = playerId });
        });

        admin.MapPost("/link-suggestions", async ([FromBody] LinkSuggestionsRequest 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);

            if (request.SourceSuggestionId == request.TargetSuggestionId)
                return Results.BadRequest(new { error = "Pick two different games to link." });

            var suggestions = await db.Suggestions.ToListAsync();
            var source = suggestions.FirstOrDefault(s => s.Id == request.SourceSuggestionId);
            var target = suggestions.FirstOrDefault(s => s.Id == request.TargetSuggestionId);
            if (source is null || target is null)
                return Results.NotFound(new { error = "Suggestion not found." });

            var rootIndex = EndpointHelpers.BuildLinkRoots(suggestions.Select(s => (s.Id, s.ParentSuggestionId)));
            if (!rootIndex.TryGetValue(source.Id, out var sourceRoot) || !rootIndex.TryGetValue(target.Id, out var targetRoot))
                return Results.NotFound(new { error = "Suggestion not found." });

            if (sourceRoot == targetRoot)
                return Results.BadRequest(new { error = "These games are already linked." });

            var affectedRootIds = new HashSet<int> { sourceRoot, targetRoot };
            var affectedIds = rootIndex
                .Where(kv => affectedRootIds.Contains(kv.Value))
                .Select(kv => kv.Key)
                .ToList();

            await using var tx = await db.Database.BeginTransactionAsync();

            foreach (var suggestion in suggestions)
            {
                var root = rootIndex.GetValueOrDefault(suggestion.Id);
                if (root == targetRoot)
                {
                    suggestion.ParentSuggestionId = suggestion.Id == targetRoot ? null : targetRoot;
                }
                else if (root == sourceRoot)
                {
                    suggestion.ParentSuggestionId = targetRoot;
                }
            }

            await db.SaveChangesAsync();

            var affectedPlayerIds = await db.Votes
                .Where(v => affectedIds.Contains(v.SuggestionId))
                .Select(v => v.PlayerId)
                .Distinct()
                .ToListAsync();

            await db.Votes.Where(v => affectedIds.Contains(v.SuggestionId)).ExecuteDeleteAsync();

            if (affectedPlayerIds.Count > 0)
            {
                await db.Players.Where(p => affectedPlayerIds.Contains(p.Id))
                    .ExecuteUpdateAsync(p => p.SetProperty(x => x.VotesFinal, false));
            }

            await tx.CommitAsync();

            return Results.Ok(new
            {
                RootId = targetRoot,
                LinkedSuggestionIds = affectedIds,
                UnfinalizedPlayers = affectedPlayerIds.Count
            });
        });

        admin.MapPost("/unlink-suggestions", async ([FromBody] UnlinkSuggestionsRequest 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);

            var suggestions = await db.Suggestions.ToListAsync();
            var target = suggestions.FirstOrDefault(s => s.Id == request.SuggestionId);
            if (target is null)
                return Results.NotFound(new { error = "Suggestion not found." });

            var rootIndex = EndpointHelpers.BuildLinkRoots(suggestions.Select(s => (s.Id, s.ParentSuggestionId)));
            if (!rootIndex.TryGetValue(target.Id, out var rootId))
                return Results.Ok(new { UnlinkedSuggestionIds = Array.Empty<int>(), UnfinalizedPlayers = 0 });

            var groupIds = rootIndex
                .Where(kv => kv.Value == rootId)
                .Select(kv => kv.Key)
                .ToList();

            await using var tx = await db.Database.BeginTransactionAsync();

            foreach (var suggestion in suggestions.Where(s => groupIds.Contains(s.Id)))
            {
                suggestion.ParentSuggestionId = null;
            }

            await db.SaveChangesAsync();

            var affectedPlayerIds = await db.Votes
                .Where(v => groupIds.Contains(v.SuggestionId))
                .Select(v => v.PlayerId)
                .Distinct()
                .ToListAsync();

            await db.Votes.Where(v => groupIds.Contains(v.SuggestionId)).ExecuteDeleteAsync();

            if (affectedPlayerIds.Count > 0)
            {
                await db.Players.Where(p => affectedPlayerIds.Contains(p.Id))
                    .ExecuteUpdateAsync(p => p.SetProperty(x => x.VotesFinal, false));
            }

            await tx.CommitAsync();

            return Results.Ok(new
            {
                UnlinkedSuggestionIds = groupIds,
                UnfinalizedPlayers = affectedPlayerIds.Count
            });
        });

        admin.MapPost("/reset", async (HttpContext ctx, AppDbContext db) =>
        {
            await db.Votes.ExecuteDeleteAsync();
            await db.Suggestions.ExecuteDeleteAsync();

            await db.Players.ExecuteUpdateAsync(p => p.SetProperty(x => x.CurrentPhase, Phase.Suggest)
                                                     .SetProperty(x => x.VotesFinal, false)
                                                     .SetProperty(x => x.HasJoker, false));
            var state = await db.AppState.FirstAsync();
            state.ResultsOpen = false;
            state.UpdatedAt = DateTimeOffset.UtcNow;
            await db.SaveChangesAsync();

            return Results.Ok(new { Phase = Phase.Suggest, state.ResultsOpen, state.UpdatedAt });
        });

        admin.MapPost("/factory-reset", async (HttpContext ctx, AppDbContext db) =>
        {
            await using var tx = await db.Database.BeginTransactionAsync();

            await db.Votes.ExecuteDeleteAsync();
            await db.Suggestions.ExecuteDeleteAsync();
            await db.Players.ExecuteDeleteAsync();
            await db.AppState.ExecuteDeleteAsync();

            var fresh = EndpointHelpers.NewAppState();
            db.AppState.Add(fresh);
            await db.SaveChangesAsync();

            await tx.CommitAsync();

            return Results.Ok(new { Phase = Phase.Suggest, fresh.ResultsOpen, fresh.UpdatedAt });
        });
    }
}
