using Backend.Contracts;
using Backend.Common;
using Backend.Data;
using Backend.Domain;
using Backend.Services;
using Microsoft.EntityFrameworkCore;

namespace Backend.Endpoints;

public static partial class AdminSeasonManagementEndpoints
{
    private static async Task<IResult> SetResult(
        HttpContext context,
        int seasonId,
        SetAwardResultRequest request,
        AwardsDbContext db,
        IAdminAuditService adminAuditService)
    {
        var session = AdminEndpointConventions.CurrentSession(context);
        var category = await db.Categories
            .AsNoTracking()
            .FirstOrDefaultAsync(item => item.Id == request.CategoryId && item.SeasonId == seasonId);
        if (category is null)
        {
            return Results.BadRequest(new { message = "The selected category does not exist in this season." });
        }

        var candidate = await db.Candidates
            .AsNoTracking()
            .FirstOrDefaultAsync(item =>
                item.Id == request.CandidateId
                && item.SeasonId == seasonId
                && item.CategoryId == request.CategoryId);
        if (candidate is null)
        {
            return Results.BadRequest(new { message = "The selected candidate does not belong to the selected category." });
        }

        var workflowRules = await LoadWorkflowRulesAsync(db, seasonId, context.RequestAborted);
        var winnerRequiresClipRule = WorkflowRuleSettings.Find(workflowRules, WorkflowRuleSettings.WinnerRequiresClip);
        if (WorkflowRuleSettings.ShouldBlock(winnerRequiresClipRule)
            && string.IsNullOrWhiteSpace(candidate.ClipCompilationUrl))
        {
            return CreateWorkflowRuleError(
                "Dieser Kandidat hat noch keinen gepflegten Clip-Link. Bitte zuerst die Clip-Compilation am Kandidaten hinterlegen oder die Workflow-Regel umstellen.");
        }

        var winnerPlacementsRule = WorkflowRuleSettings.Find(workflowRules, WorkflowRuleSettings.MaxWinnerPlacements);
        if (WorkflowRuleSettings.ShouldBlock(winnerPlacementsRule))
        {
            var candidateIdentityKey = WorkflowRuleSettings.CandidateIdentityKey(candidate);
            var existingWinnerIdentities = await db.Results
                .AsNoTracking()
                .Include(item => item.Candidate)
                .Where(item => item.SeasonId == seasonId && item.CategoryId != request.CategoryId)
                .Select(item => new
                {
                    item.CategoryId,
                    item.Candidate.StreamerIdentityId,
                    item.Candidate.DisplayName,
                    item.Candidate.ChannelSlug,
                })
                .ToArrayAsync(context.RequestAborted);
            var existingWinnerCount = existingWinnerIdentities.Count(item =>
                candidate.StreamerIdentityId.HasValue && item.StreamerIdentityId == candidate.StreamerIdentityId
                ||
                string.Equals(WorkflowRuleSettings.CandidateIdentityKey(item.DisplayName, item.ChannelSlug), candidateIdentityKey, StringComparison.Ordinal));

            if (existingWinnerCount >= winnerPlacementsRule.Limit)
            {
                return CreateWorkflowRuleError(
                    $"Diese Person hat bereits {existingWinnerCount} Gewinnerplatz(e). Limit: {winnerPlacementsRule.Limit}.");
            }
        }

        var season = await db.Seasons.FirstOrDefaultAsync(item => item.Id == seasonId);
        var existingResult = await db.Results.FirstOrDefaultAsync(item =>
            item.SeasonId == seasonId
            && item.CategoryId == request.CategoryId);

        if (existingResult is null)
        {
            existingResult = new AwardResult
            {
                SeasonId = seasonId,
                CategoryId = request.CategoryId,
                CandidateId = request.CandidateId,
                CategoryName = category.Name,
            };
            db.Results.Add(existingResult);
        }
        else
        {
            existingResult.CandidateId = request.CandidateId;
            existingResult.CategoryName = category.Name;
        }

        var wasPublished = season?.WinnersPublishedAt is not null;
        if (wasPublished && season is not null)
        {
            season.WinnersPublishedAt = null;
            season.WinnersPublishedByTwitchId = null;
        }

        adminAuditService.AddEntry(
            session.TwitchUserId,
            "result.set",
            "result",
            $"{seasonId}:{request.CategoryId}",
            $"Gewinner für {category.Name} wurde gesetzt.",
            new
            {
                seasonId,
                categoryId = request.CategoryId,
                candidateId = request.CandidateId,
                candidateName = candidate.DisplayName,
                unpublishedWinners = wasPublished,
            },
            RequestMetadataReader.Read(context));

        await db.SaveChangesAsync(context.RequestAborted);
        return Results.Ok(new
        {
            saved = true,
            resultId = existingResult.Id,
            seasonId,
            categoryId = request.CategoryId,
            candidateId = request.CandidateId,
        });
    }

    private static async Task<IResult> DeleteResult(
        HttpContext context,
        int resultId,
        AwardsDbContext db,
        IAdminAuditService adminAuditService)
    {
        var session = AdminEndpointConventions.CurrentSession(context);
        var result = await db.Results
            .Include(item => item.Category)
            .Include(item => item.Candidate)
            .Include(item => item.Season)
            .FirstOrDefaultAsync(item => item.Id == resultId);
        if (result is null)
        {
            return Results.NotFound();
        }

        var wasPublished = result.Season.WinnersPublishedAt is not null;
        if (wasPublished)
        {
            result.Season.WinnersPublishedAt = null;
            result.Season.WinnersPublishedByTwitchId = null;
        }

        db.Results.Remove(result);
        adminAuditService.AddEntry(
            session.TwitchUserId,
            "result.delete",
            "result",
            result.Id.ToString(),
            $"Gewinner für {result.Category.Name} wurde entfernt.",
            new
            {
                result.SeasonId,
                result.CategoryId,
                result.CandidateId,
                candidateName = result.Candidate.DisplayName,
                unpublishedWinners = wasPublished,
            },
            RequestMetadataReader.Read(context));

        await db.SaveChangesAsync(context.RequestAborted);
        return Results.Ok(new { deleted = true, resultId });
    }

    private static async Task<IResult> PublishWinners(
        HttpContext context,
        int seasonId,
        AwardsDbContext db,
        IAdminAuditService adminAuditService)
    {
        var session = AdminEndpointConventions.CurrentSession(context);
        var season = await db.Seasons.FirstOrDefaultAsync(item => item.Id == seasonId, context.RequestAborted);
        if (season is null)
        {
            return Results.NotFound();
        }

        var issues = await BuildWinnerPublicationIssuesAsync(db, seasonId, context.RequestAborted);
        if (issues.Length > 0)
        {
            return CreateWinnerPublicationError(issues);
        }

        season.WinnersPublishedAt = DateTimeOffset.UtcNow;
        season.WinnersPublishedByTwitchId = session.TwitchUserId;

        adminAuditService.AddEntry(
            session.TwitchUserId,
            "winners.publish",
            "season",
            season.Id.ToString(),
            $"Gewinner für {season.Year} wurden veröffentlicht.",
            new
            {
                seasonId,
                season.Year,
                season.WinnersPublishedAt,
            },
            RequestMetadataReader.Read(context));

        await db.SaveChangesAsync(context.RequestAborted);
        return Results.Ok(new
        {
            saved = true,
            seasonId,
            winnersPublishedAt = season.WinnersPublishedAt,
            winnersPublishedByTwitchId = season.WinnersPublishedByTwitchId,
        });
    }

    private static async Task<IResult> UnpublishWinners(
        HttpContext context,
        int seasonId,
        AwardsDbContext db,
        IAdminAuditService adminAuditService)
    {
        var session = AdminEndpointConventions.CurrentSession(context);
        var season = await db.Seasons.FirstOrDefaultAsync(item => item.Id == seasonId, context.RequestAborted);
        if (season is null)
        {
            return Results.NotFound();
        }

        var previousPublishedAt = season.WinnersPublishedAt;
        season.WinnersPublishedAt = null;
        season.WinnersPublishedByTwitchId = null;

        adminAuditService.AddEntry(
            session.TwitchUserId,
            "winners.unpublish",
            "season",
            season.Id.ToString(),
            $"Gewinner für {season.Year} wurden von der Landingpage zurückgenommen.",
            new
            {
                seasonId,
                season.Year,
                previousPublishedAt,
            },
            RequestMetadataReader.Read(context));

        await db.SaveChangesAsync(context.RequestAborted);
        return Results.Ok(new
        {
            saved = true,
            seasonId,
            winnersPublishedAt = season.WinnersPublishedAt,
            winnersPublishedByTwitchId = season.WinnersPublishedByTwitchId,
        });
    }
}
