using System.Security.Claims;
using System.Text.Json;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using PlotLine.Data;
using PlotLine.Models;
using PlotLine.Services;
using PlotLine.ViewModels;

namespace PlotLine.Hubs;

[Authorize]
public sealed class StoryIntelligenceHub(
    IStoryIntelligenceService storyIntelligence,
    IStoryIntelligenceResultRepository persistedRuns,
    IOnboardingStoryIntelligenceService onboardingStoryIntelligence) : Hub
{
    public override async Task OnConnectedAsync()
    {
        if (TryGetUserId(out var userId))
        {
            await Groups.AddToGroupAsync(Context.ConnectionId, UserGroup(userId));
        }

        await base.OnConnectedAsync();
    }

    public async Task<StoryIntelligenceJobProgress?> WatchStoryIntelligenceJob(Guid jobId)
    {
        var userId = RequireUserId();
        await Groups.AddToGroupAsync(Context.ConnectionId, UserGroup(userId));
        return await storyIntelligence.GetProgressForUserAsync(userId, jobId);
    }

    public async Task<StoryIntelligenceRunProgressEvent?> WatchStoryIntelligenceRun(int runId)
    {
        var userId = RequireUserId();
        await Groups.AddToGroupAsync(Context.ConnectionId, UserGroup(userId));

        var run = await persistedRuns.GetRunAsync(runId);
        if (run is null || run.UserID != userId)
        {
            return null;
        }

        var sceneResults = await persistedRuns.ListSceneResultsAsync(runId);
        return ToRunProgress(run, "Current status", LatestSceneSummary(sceneResults));
    }

    public async Task<StoryIntelligenceProgressViewModel?> WatchOnboardingStoryIntelligenceBatch(Guid batchId)
    {
        var userId = RequireUserId();
        await Groups.AddToGroupAsync(Context.ConnectionId, UserGroup(userId));
        return await onboardingStoryIntelligence.GetProgressAsync(batchId);
    }

    public static string UserGroup(int userId) => $"story-intelligence:{userId}";

    private static StoryIntelligenceRunProgressEvent ToRunProgress(StoryIntelligenceSavedRun run, string eventType, string? latestSceneSummary)
    {
        var completedScenes = run.CompletedScenes ?? 0;
        var totalScenes = run.TotalDetectedScenes ?? 0;
        var elapsedMs = run.TotalDurationMs ?? Math.Max(0, Convert.ToInt64((DateTime.UtcNow - run.StartedUtc).TotalMilliseconds));

        return new StoryIntelligenceRunProgressEvent
        {
            RunID = run.StoryIntelligenceRunID,
            UserID = run.UserID,
            ProjectID = run.ProjectID,
            BookID = run.BookID,
            ChapterID = run.ChapterID,
            ChapterNumber = run.ChapterNumber,
            Status = run.Status,
            CurrentStage = run.CurrentStage ?? string.Empty,
            CurrentMessage = run.CurrentMessage ?? string.Empty,
            EventType = eventType,
            TotalDetectedScenes = run.TotalDetectedScenes,
            CompletedScenes = run.CompletedScenes,
            FailedScenes = run.FailedScenes,
            TotalTokens = run.TotalTokens,
            TotalDurationMs = elapsedMs,
            CurrentSceneNumber = run.CompletedScenes,
            ElapsedTime = FormatDuration(elapsedMs),
            EstimatedRemaining = EstimateRemaining(elapsedMs, completedScenes, totalScenes),
            LatestSceneSummary = latestSceneSummary,
            ErrorMessage = run.ErrorMessage,
            UpdatedUtc = run.UpdatedUtc,
            IsActive = run.Status is StoryIntelligenceRunStatuses.Pending or StoryIntelligenceRunStatuses.Running,
            IsCompleted = run.Status is StoryIntelligenceRunStatuses.Completed or StoryIntelligenceRunStatuses.CompletedWithWarnings
        };
    }

    private static string FormatDuration(long? milliseconds)
    {
        var elapsed = TimeSpan.FromMilliseconds(Math.Max(0, milliseconds ?? 0));
        if (elapsed.TotalMinutes >= 1)
        {
            return $"{Math.Max(1, (int)Math.Round(elapsed.TotalMinutes)):N0} min";
        }

        return $"{Math.Max(0, (int)Math.Round(elapsed.TotalSeconds)):N0} sec";
    }

    private static string? EstimateRemaining(long elapsedMs, int completedScenes, int totalScenes)
    {
        if (completedScenes <= 0 || totalScenes <= completedScenes)
        {
            return totalScenes > 0 && totalScenes == completedScenes ? "Nearly finished" : "Calculating";
        }

        var averageSceneMs = elapsedMs / Math.Max(1, completedScenes);
        return FormatDuration(averageSceneMs * (totalScenes - completedScenes));
    }

    private static string? LatestSceneSummary(IReadOnlyList<StoryIntelligenceSavedSceneResult> sceneResults)
    {
        foreach (var scene in sceneResults
                     .Where(scene => scene.ValidationErrorsCount == 0 && !string.IsNullOrWhiteSpace(scene.ParsedJson))
                     .OrderByDescending(scene => scene.TemporarySceneNumber))
        {
            var summary = ExtractShortSummary(scene.ParsedJson);
            if (!string.IsNullOrWhiteSpace(summary))
            {
                return Trim(summary, 220);
            }
        }

        return null;
    }

    private static string? ExtractShortSummary(string json)
    {
        try
        {
            using var document = JsonDocument.Parse(json);
            var root = document.RootElement;
            if (TryReadShortSummary(root, out var direct))
            {
                return direct;
            }

            if (root.TryGetProperty("parsedScene", out var parsedScene)
                && TryReadShortSummary(parsedScene, out var wrapped))
            {
                return wrapped;
            }
        }
        catch (JsonException)
        {
            return null;
        }

        return null;
    }

    private static bool TryReadShortSummary(JsonElement element, out string? summary)
    {
        summary = null;
        if (element.ValueKind != JsonValueKind.Object
            || !element.TryGetProperty("summary", out var summaryElement)
            || summaryElement.ValueKind != JsonValueKind.Object)
        {
            return false;
        }

        if (TryReadSummaryValue(summaryElement, "short", out summary))
        {
            return !string.IsNullOrWhiteSpace(summary);
        }

        return TryReadSummaryValue(summaryElement, "detailed", out summary)
            && !string.IsNullOrWhiteSpace(summary);
    }

    private static bool TryReadSummaryValue(JsonElement summaryElement, string propertyName, out string? value)
    {
        value = null;
        if (summaryElement.TryGetProperty(propertyName, out var textElement)
            && textElement.ValueKind == JsonValueKind.String)
        {
            value = textElement.GetString();
            return !string.IsNullOrWhiteSpace(value);
        }

        return false;
    }

    private static string Trim(string value, int maxLength)
    {
        var trimmed = value.Trim();
        return trimmed.Length <= maxLength ? trimmed : trimmed[..maxLength].TrimEnd();
    }

    private int RequireUserId()
        => TryGetUserId(out var userId) ? userId : throw new HubException("Sign in to use Story Intelligence.");

    private bool TryGetUserId(out int userId)
        => int.TryParse(Context.User?.FindFirstValue(ClaimTypes.NameIdentifier), out userId);
}
