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

namespace GameList.Endpoints;

internal sealed class SuggestionWorkflowService(AppDbContext db, IHttpClientFactory httpFactory)
{
    public async Task<IResult> GetMineAsync(Player player)
    {
        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);
    }

    public async Task<IResult> CreateAsync(Player player, SuggestionRequest request)
    {
        var validationError = await SuggestionValidator.ValidateAsync(request, httpFactory);
        if (validationError is not null)
            return EndpointHelpers.BadRequestError(validationError);

        var phase = await EndpointHelpers.GetCurrentPhaseAsync(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 EndpointHelpers.BadRequestError("Set a display name before submitting suggestions.");

        var existingCount = await db.Suggestions.CountAsync(s => s.PlayerId == player.Id);
        if (!usingJoker && existingCount >= 5)
            return EndpointHelpers.BadRequestError("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
        };

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

        db.Suggestions.Add(suggestion);

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

        await db.SaveChangesAsync();
        await tx.CommitAsync();

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

    public async Task<IResult> DeleteAsync(Player player, bool isAdmin, int suggestionId)
    {
        if (!isAdmin)
        {
            var phase = await EndpointHelpers.GetCurrentPhaseAsync(db, player.Id);
            if (phase != Phase.Suggest)
                return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase);
        }

        var suggestion = isAdmin
            ? await db.Suggestions.FirstOrDefaultAsync(s => s.Id == suggestionId)
            : await db.Suggestions.FirstOrDefaultAsync(s => s.Id == suggestionId && s.PlayerId == player.Id);
        if (suggestion == null)
            return EndpointHelpers.NotFoundError("Suggestion not found.");

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

        await db.Suggestions
            .Where(s => s.ParentSuggestionId == suggestion.Id)
            .ExecuteUpdateAsync(s => s.SetProperty(x => x.ParentSuggestionId, (int?)null));

        await db.Votes.Where(v => v.SuggestionId == suggestion.Id).ExecuteDeleteAsync();

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

    public async Task<IResult> UpdateAsync(Player player, bool isAdmin, int suggestionId, SuggestionRequest request)
    {
        var validationError = await SuggestionValidator.ValidateAsync(request, httpFactory);
        if (validationError is not null)
            return EndpointHelpers.BadRequestError(validationError);

        var suggestion = await db.Suggestions.FirstOrDefaultAsync(s => s.Id == suggestionId);
        if (suggestion == null)
            return EndpointHelpers.NotFoundError("Suggestion not found.");

        if (!isAdmin)
        {
            if (suggestion.PlayerId != player.Id)
                return EndpointHelpers.UnauthorizedError();

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

            if (phase == Phase.Suggest)
            {
                suggestion.Name = request.Name.Trim();
            }
            else if (phase != Phase.Vote)
            {
                return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase);
            }

            ApplyEditableFields(suggestion, request);
        }
        else
        {
            suggestion.Name = request.Name.Trim();
            ApplyEditableFields(suggestion, request);
        }

        await db.SaveChangesAsync();

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

    public async Task<IResult> GetAllAsync(Player player)
    {
        var phase = await EndpointHelpers.GetCurrentPhaseAsync(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 void ApplyEditableFields(Suggestion suggestion, SuggestionRequest request)
    {
        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;
    }
}
