using Backend.Common;
using Backend.Domain;
using Backend.Services;
using Microsoft.AspNetCore.Http.Extensions;

namespace Backend.Endpoints;

public static partial class PublicEndpoints
{
    private readonly record struct SubmitterIdResolution(string? SubmitterId, IResult? Result);
    private readonly record struct PublicWriteSeasonResolution(Season? Season, IResult? Result);
    private sealed record PublicCandidateClip(
        int? CategoryId,
        int? CandidateId,
        string Creator,
        string ClipUrl,
        string Title,
        string Platform,
        DateTimeOffset CreatedAt,
        DateTimeOffset? ReviewedAt);

    private static async Task<SubmitterIdResolution> ResolveSubmitterIdAsync(
        HttpContext context,
        string? fallbackTwitchUserId,
        IUserSessionService userSessionService)
    {
        var session = await userSessionService.ResolveSessionAsync(context, context.RequestAborted);
        if (session is null)
        {
            return new SubmitterIdResolution(
                null,
                Results.Json(
                    new { message = "A logged in user is required to submit this action." },
                    statusCode: StatusCodes.Status401Unauthorized));
        }

        var submittedTwitchUserId = NormalizeSubmittedTwitchUserId(fallbackTwitchUserId);
        if (submittedTwitchUserId is not null
            && !string.Equals(submittedTwitchUserId, session.TwitchUserId, StringComparison.OrdinalIgnoreCase))
        {
            return new SubmitterIdResolution(
                null,
                Results.BadRequest(new { message = "Submitted user identity does not match the active session." }));
        }

        return new SubmitterIdResolution(session.TwitchUserId, null);
    }

    private static string? NormalizeSubmittedTwitchUserId(string? twitchUserId)
    {
        var normalized = twitchUserId?.Trim();
        return string.IsNullOrWhiteSpace(normalized) ? null : normalized;
    }

    private static PublicWriteSeasonResolution EnsurePublicWriteSeason(
        Season? season,
        params string[] allowedPhaseKeys)
    {
        if (season is null)
        {
            return new PublicWriteSeasonResolution(null, Results.BadRequest(new { message = "The selected season does not exist." }));
        }

        if (!season.IsCurrent)
        {
            return new PublicWriteSeasonResolution(
                null,
                Results.BadRequest(new { message = "Submissions are only allowed for the active season." }));
        }

        var currentPhaseKey = SeasonMappings.NormalizePhaseKey(season.CurrentPhase);
        if (!allowedPhaseKeys.Contains(currentPhaseKey, StringComparer.OrdinalIgnoreCase))
        {
            var phaseLabel = DescribePublicPhase(currentPhaseKey);
            return new PublicWriteSeasonResolution(
                null,
                Results.BadRequest(new
                {
                    message = $"This action is not available during the current season phase ({phaseLabel}).",
                }));
        }

        return new PublicWriteSeasonResolution(season, null);
    }

    private static bool TryNormalizeExternalUrl(string? rawUrl, out string normalizedUrl)
    {
        normalizedUrl = string.Empty;
        if (string.IsNullOrWhiteSpace(rawUrl))
        {
            return false;
        }

        if (!Uri.TryCreate(rawUrl.Trim(), UriKind.Absolute, out var uri))
        {
            return false;
        }

        if (uri.Scheme is not ("http" or "https"))
        {
            return false;
        }

        normalizedUrl = uri.GetComponents(UriComponents.HttpRequestUrl, UriFormat.UriEscaped);
        return true;
    }

    private static string? ResolveClipPlatform(string clipUrl)
    {
        if (!Uri.TryCreate(clipUrl, UriKind.Absolute, out var uri))
        {
            return null;
        }

        var host = uri.Host.ToLowerInvariant();
        if (host is "twitch.tv" or "www.twitch.tv" or "clips.twitch.tv")
        {
            return "Twitch";
        }

        if (host is "youtube.com" or "www.youtube.com" or "m.youtube.com" or "youtu.be")
        {
            return "YouTube";
        }

        return null;
    }

    private static bool ShouldExposePublicCategory(string phaseKey, int candidateCount) =>
        string.Equals(phaseKey, "nomination", StringComparison.OrdinalIgnoreCase) || candidateCount > 0;

    private static Dictionary<int, PublicCandidateClip> BuildCandidateClipLookup(IEnumerable<PublicCandidateClip> clips) =>
        clips
            .Where(clip => clip.CandidateId is not null)
            .GroupBy(clip => clip.CandidateId!.Value)
            .ToDictionary(
                grouping => grouping.Key,
                grouping => grouping
                    .OrderByDescending(clip => clip.ReviewedAt ?? clip.CreatedAt)
                    .First());

    private static Dictionary<string, PublicCandidateClip> BuildCreatorClipLookup(IEnumerable<PublicCandidateClip> clips) =>
        clips
            .Where(clip => clip.CategoryId is not null && !string.IsNullOrWhiteSpace(clip.Creator))
            .GroupBy(clip => BuildCandidateClipLookupKey(clip.CategoryId!.Value, clip.Creator))
            .Where(grouping => !string.IsNullOrWhiteSpace(grouping.Key))
            .ToDictionary(
                grouping => grouping.Key,
                grouping => grouping
                    .OrderByDescending(clip => clip.ReviewedAt ?? clip.CreatedAt)
                    .First());

    private static PublicCandidateClip? ResolveCandidateClip(
        Candidate candidate,
        IReadOnlyDictionary<int, PublicCandidateClip> clipsByCandidateId,
        IReadOnlyDictionary<string, PublicCandidateClip> clipsByCreatorKey)
    {
        if (clipsByCandidateId.TryGetValue(candidate.Id, out var directClip))
        {
            return directClip;
        }

        foreach (var key in BuildCandidateClipLookupKeys(candidate))
        {
            if (clipsByCreatorKey.TryGetValue(key, out var fallbackClip))
            {
                return fallbackClip;
            }
        }

        return null;
    }

    private static IEnumerable<string> BuildCandidateClipLookupKeys(Candidate candidate)
    {
        yield return BuildCandidateClipLookupKey(candidate.CategoryId, candidate.DisplayName);
        yield return BuildCandidateClipLookupKey(candidate.CategoryId, candidate.ChannelSlug);
        yield return BuildCandidateClipLookupKey(candidate.CategoryId, candidate.ChannelSlug.TrimStart('@'));
    }

    private static string BuildCandidateClipLookupKey(int categoryId, string value)
    {
        var key = NormalizeCandidateClipKey(value);
        return string.IsNullOrWhiteSpace(key) ? string.Empty : $"{categoryId}:{key}";
    }

    private static string NormalizeCandidateClipKey(string value)
    {
        var normalizedCharacters = value
            .Trim()
            .TrimStart('@')
            .ToLowerInvariant()
            .Where(char.IsLetterOrDigit)
            .ToArray();

        return new string(normalizedCharacters);
    }

    private static string DescribePublicPhase(string phaseKey) =>
        phaseKey switch
        {
            "nomination" => "nomination",
            "voting" => "voting",
            "review" => "review",
            "show" => "show",
            _ => "current",
        };
}
