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

namespace Backend.Endpoints;

public static partial class PublicEndpoints
{
    private static async Task<IResult> CreateClip(
        HttpContext context,
        CreateClipRequest request,
        AwardsDbContext db,
        IUserSessionService userSessionService,
        IRiskFlagService riskFlagService,
        IRiskRuleService riskRuleService)
    {
        if (!TryNormalizeExternalUrl(request.ClipUrl, out var clipUrl))
        {
            return Results.BadRequest(new { message = "A valid http(s) clip link is required." });
        }

        var platform = ResolveClipPlatform(clipUrl);
        if (platform is null)
        {
            return Results.BadRequest(new { message = "Only Twitch or YouTube clip links are supported." });
        }

        var siteSettings = await db.SiteSettings
            .AsNoTracking()
            .FirstOrDefaultAsync(item => item.Id == 1, context.RequestAborted);
        if (siteSettings is null)
        {
            return Results.Problem("Site settings are missing.");
        }

        if (!siteSettings.ClipSubmissionsEnabled)
        {
            var message = string.IsNullOrWhiteSpace(siteSettings.ClipSubmissionDisabledMessage)
                ? "Clip-Einreichungen sind aktuell geschlossen."
                : siteSettings.ClipSubmissionDisabledMessage;
            return Results.BadRequest(new { message });
        }

        var season = await db.Seasons.FirstOrDefaultAsync(item => item.Year == request.Year);
        var clipSeasonResolution = EnsurePublicWriteSeason(season, "nomination");
        if (clipSeasonResolution.Result is not null)
        {
            return clipSeasonResolution.Result;
        }

        season = clipSeasonResolution.Season!;

        var selectedCandidate = request.CandidateId is int candidateId
            ? await db.Candidates
                .AsNoTracking()
                .FirstOrDefaultAsync(item => item.Id == candidateId && item.SeasonId == season.Id)
            : null;
        if (request.CandidateId is not null && selectedCandidate is null)
        {
            return Results.BadRequest(new { message = "The selected candidate does not exist for this season." });
        }

        if (request.CategoryId is int requestedCategoryId
            && selectedCandidate is not null
            && selectedCandidate.CategoryId != requestedCategoryId)
        {
            return Results.BadRequest(new { message = "The selected candidate does not belong to the selected category." });
        }

        var resolvedCategoryId = request.CategoryId ?? selectedCandidate?.CategoryId;
        var normalizedTitle = request.Title?.Trim() ?? string.Empty;
        var submittedCreator = request.Creator?.Trim();
        var normalizedCreator = string.IsNullOrWhiteSpace(submittedCreator)
            ? selectedCandidate?.DisplayName ?? string.Empty
            : submittedCreator;
        if (normalizedTitle.Length > 160)
        {
            return Results.BadRequest(new { message = "Clip titles must stay below 160 characters." });
        }

        if (normalizedCreator.Length > 160)
        {
            return Results.BadRequest(new { message = "Creator names must stay below 160 characters." });
        }

        if (resolvedCategoryId is int categoryId)
        {
            var categoryExists = selectedCandidate?.CategoryId == categoryId
                || await db.Categories.AnyAsync(item => item.Id == categoryId && item.SeasonId == season.Id);
            if (!categoryExists)
            {
                return Results.BadRequest(new { message = "The selected category does not exist for this season." });
            }
        }

        var submitterIdResult = await ResolveSubmitterIdAsync(context, request.TwitchUserId, userSessionService);
        if (submitterIdResult.Result is not null)
        {
            return submitterIdResult.Result;
        }

        var submitterId = submitterIdResult.SubmitterId!;
        var requestMetadata = RequestMetadataReader.Read(context);
        var duplicateClipRule = await riskRuleService.GetRuleAsync("duplicate_clip_submission", context.RequestAborted);
        var rapidClipBurstRule = await riskRuleService.GetRuleAsync("rapid_clip_burst", context.RequestAborted);
        var recentClipSubmissions = await db.ClipSubmissions.CountAsync(item =>
            item.SubmittedByTwitchId == submitterId
            && item.CreatedAt >= DateTimeOffset.UtcNow.AddMinutes(-rapidClipBurstRule.WindowMinutes));
        var alreadySubmittedClip = await db.ClipSubmissions.AnyAsync(item =>
            item.SeasonId == season.Id
            && item.SubmittedByTwitchId == submitterId
            && item.ClipUrl == clipUrl);

        var clip = new ClipSubmission
        {
            SeasonId = season.Id,
            CategoryId = resolvedCategoryId,
            CandidateId = selectedCandidate?.Id,
            SubmittedByTwitchId = submitterId,
            ClipUrl = clipUrl,
            Title = normalizedTitle,
            Creator = normalizedCreator,
            Platform = platform,
            Status = "pending",
            CreatedFromIp = requestMetadata.ClientIp,
            CreatedAt = DateTimeOffset.UtcNow,
        };

        db.ClipSubmissions.Add(clip);
        await db.SaveChangesAsync(context.RequestAborted);

        var clipLink = new
        {
            label = "Clip öffnen",
            entityType = "clip",
            entityId = clip.Id.ToString(),
            to = $"/admin/clips?query={Uri.EscapeDataString(clip.Id.ToString())}",
        };

        if (alreadySubmittedClip && duplicateClipRule.Enabled)
        {
            await riskFlagService.AddIfMissingAsync(
                season.Id,
                submitterId,
                "clip",
                "duplicate_clip_submission",
                duplicateClipRule.Severity,
                "Ein User hat denselben Clip erneut eingereicht.",
                requestMetadata,
                new { clipId = clip.Id, clipUrl, CategoryId = resolvedCategoryId, CandidateId = selectedCandidate?.Id, entityLinks = new[] { clipLink } },
                context.RequestAborted);
        }

        if (rapidClipBurstRule.Enabled && recentClipSubmissions >= rapidClipBurstRule.Threshold)
        {
            await riskFlagService.AddIfMissingAsync(
                season.Id,
                submitterId,
                "clip",
                "rapid_clip_burst",
                rapidClipBurstRule.Severity,
                "Ungewoehnlich viele Clip-Einreichungen in kurzer Zeit erkannt.",
                requestMetadata,
                new { clipId = clip.Id, recentClipSubmissions, threshold = rapidClipBurstRule.Threshold, windowMinutes = rapidClipBurstRule.WindowMinutes, entityLinks = new[] { clipLink } },
                context.RequestAborted);
        }

        await db.SaveChangesAsync(context.RequestAborted);
        return Results.Ok(new { saved = true, clipId = clip.Id });
    }
}
