using System.Diagnostics;
using System.Text.Json;
using PlotLine.Models;
using PlotLine.ViewModels;

namespace PlotLine.Services;

public interface IChapterStoryIntelligenceDryRunService
{
    ChapterStoryIntelligenceDryRunViewModel GetDefault();
    Task<ChapterStoryIntelligenceDryRunViewModel> ExecuteAsync(ChapterStoryIntelligenceDryRunForm form, CancellationToken cancellationToken);
}

public sealed class ChapterStoryIntelligenceDryRunService(
    IStoryPromptRepository prompts,
    IStoryPromptVersionService versions,
    IStoryPromptBuilder scenePromptBuilder,
    IStoryIntelligenceClient client,
    IChapterStructureValidator chapterValidator,
    IStorySceneValidator sceneValidator,
    ILogger<ChapterStoryIntelligenceDryRunService> logger) : IChapterStoryIntelligenceDryRunService
{
    private const string ChapterPromptFile = "Chapter-Structure-Prompt-V2.md";
    private const string ScenePromptFile = "Scene-Prompt-V2.md";
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        PropertyNameCaseInsensitive = true,
        WriteIndented = true
    };

    public ChapterStoryIntelligenceDryRunViewModel GetDefault()
        => new();

    public async Task<ChapterStoryIntelligenceDryRunViewModel> ExecuteAsync(ChapterStoryIntelligenceDryRunForm form, CancellationToken cancellationToken)
    {
        var model = new ChapterStoryIntelligenceDryRunViewModel { Form = form };
        var totalStopwatch = Stopwatch.StartNew();
        var chapterPromptVersion = versions.GetPromptVersion(ChapterPromptFile);
        var scenePromptVersion = versions.GetPromptVersion(ScenePromptFile);
        var clientStatus = client.GetConfigurationStatus();
        var paragraphs = StoryIntelligenceParagraphs.Split(form.ChapterText);
        var chapterContextJson = BuildChapterContextJson(form);

        try
        {
            if (paragraphs.Count == 0)
            {
                throw new InvalidOperationException("Enter fictional chapter text before running the combined pipeline test.");
            }

            var chapterTemplate = await prompts.LoadPromptAsync(ChapterPromptFile, cancellationToken);
            var numberedChapterText = StoryIntelligenceParagraphs.Number(paragraphs);
            var chapterPrompt = BuildChapterPrompt(chapterTemplate, chapterContextJson, numberedChapterText);
            var chapterResult = await client.ExecutePromptAsync(
                chapterPrompt,
                chapterPromptVersion,
                cancellationToken,
                clientStatus.ChapterStructureModel);
            var chapterJson = ExtractOutputText(chapterResult.RawResponseText);
            var parsedChapter = JsonSerializer.Deserialize<ChapterStructureModel>(chapterJson, JsonOptions);
            var chapterNormalisation = ChapterStructureBoundaryNormaliser.Normalise(parsedChapter, paragraphs.Count);
            var chapterForProcessing = chapterNormalisation.ChapterStructure;
            var chapterValidation = chapterValidator.Validate(chapterForProcessing, paragraphs.Count);
            ChapterStructureBoundaryNormaliser.AddIssuesTo(chapterValidation, chapterNormalisation);
            var sceneBlocks = new List<ChapterStorySceneBlockViewModel>();

            if (!chapterValidation.IsValid || chapterForProcessing?.SceneBoundaries is null)
            {
                totalStopwatch.Stop();
                var stoppedResult = new ChapterStoryIntelligenceDryRunResultViewModel
                {
                    Success = false,
                    PipelineStopped = true,
                    StopReason = "Chapter Structure validation failed. Scene Intelligence was not run.",
                    ChapterPromptVersion = chapterPromptVersion,
                    ScenePromptVersion = scenePromptVersion,
                    Model = StoryIntelligenceOptions.BuildModelSummary(
                        chapterResult.Model,
                        clientStatus.SceneIntelligenceModel),
                    ChapterContextJson = chapterContextJson,
                    ParagraphCount = paragraphs.Count,
                    TotalDuration = FormatDuration(totalStopwatch.Elapsed),
                    TotalDurationMs = totalStopwatch.ElapsedMilliseconds,
                    TotalInputTokens = chapterResult.InputTokens,
                    TotalOutputTokens = chapterResult.OutputTokens,
                    ChapterDuration = FormatDuration(chapterResult.Duration),
                    ChapterDurationMs = Convert.ToInt64(chapterResult.Duration.TotalMilliseconds),
                    ChapterRetryCount = chapterResult.RetryCount,
                    ChapterInputTokens = chapterResult.InputTokens,
                    ChapterOutputTokens = chapterResult.OutputTokens,
                    ChapterRawResponseText = chapterResult.RawResponseText,
                    ChapterJsonText = chapterJson,
                    ParsedChapter = chapterForProcessing,
                    ChapterValidation = chapterValidation,
                    SceneBlocks = sceneBlocks
                };
                LogPipelineResult(stoppedResult);
                model.Result = stoppedResult;
                return model;
            }

            var sceneTemplate = await prompts.LoadPromptAsync(ScenePromptFile, cancellationToken);
            foreach (var boundary in chapterForProcessing.SceneBoundaries)
            {
                var block = BuildSceneBlock(form, paragraphs, boundary);
                if (!block.SplitValid)
                {
                    sceneBlocks.Add(block);
                    continue;
                }

                var sceneContextJson = BuildSceneContextJson(form, boundary);
                var completedScenePrompt = scenePromptBuilder.BuildPrompt(sceneTemplate, sceneContextJson, block.SceneText);
                sceneBlocks.Add(await ExecuteSceneAsync(
                    block,
                    sceneContextJson,
                    completedScenePrompt,
                    scenePromptVersion,
                    clientStatus.SceneIntelligenceModel,
                    cancellationToken));
            }

            totalStopwatch.Stop();
            var finalResult = new ChapterStoryIntelligenceDryRunResultViewModel
            {
                Success = sceneBlocks.All(block => block.Success || !block.SplitValid),
                PipelineStopped = false,
                ChapterPromptVersion = chapterPromptVersion,
                ScenePromptVersion = scenePromptVersion,
                Model = StoryIntelligenceOptions.BuildModelSummary(
                    chapterResult.Model,
                    clientStatus.SceneIntelligenceModel),
                ChapterContextJson = chapterContextJson,
                ParagraphCount = paragraphs.Count,
                TotalDuration = FormatDuration(totalStopwatch.Elapsed),
                TotalDurationMs = totalStopwatch.ElapsedMilliseconds,
                TotalInputTokens = SumTokens(chapterResult.InputTokens, sceneBlocks.Select(block => block.InputTokens)),
                TotalOutputTokens = SumTokens(chapterResult.OutputTokens, sceneBlocks.Select(block => block.OutputTokens)),
                ChapterDuration = FormatDuration(chapterResult.Duration),
                ChapterDurationMs = Convert.ToInt64(chapterResult.Duration.TotalMilliseconds),
                ChapterRetryCount = chapterResult.RetryCount,
                ChapterInputTokens = chapterResult.InputTokens,
                ChapterOutputTokens = chapterResult.OutputTokens,
                ChapterRawResponseText = chapterResult.RawResponseText,
                ChapterJsonText = chapterJson,
                ParsedChapter = chapterForProcessing,
                ChapterValidation = chapterValidation,
                SceneBlocks = sceneBlocks
            };

            LogPipelineResult(finalResult);
            model.Result = finalResult;
        }
        catch (Exception ex) when (ex is not OperationCanceledException)
        {
            totalStopwatch.Stop();
            model.Result = new ChapterStoryIntelligenceDryRunResultViewModel
            {
                Success = false,
                PipelineStopped = true,
                StopReason = "The combined pipeline could not complete.",
                ChapterPromptVersion = chapterPromptVersion,
                ScenePromptVersion = scenePromptVersion,
                Model = StoryIntelligenceOptions.BuildModelSummary(
                    clientStatus.ChapterStructureModel,
                    clientStatus.SceneIntelligenceModel),
                ChapterContextJson = chapterContextJson,
                ParagraphCount = paragraphs.Count,
                TotalDuration = FormatDuration(totalStopwatch.Elapsed),
                TotalDurationMs = totalStopwatch.ElapsedMilliseconds,
                ErrorMessage = ex.Message
            };

            logger.LogWarning(
                ex,
                "Admin Chapter Story Intelligence pipeline failed. ChapterPromptVersion={ChapterPromptVersion} ScenePromptVersion={ScenePromptVersion} Model={Model} TotalDurationMs={TotalDurationMs} Success={Success}",
                chapterPromptVersion,
                scenePromptVersion,
                StoryIntelligenceOptions.BuildModelSummary(
                    clientStatus.ChapterStructureModel,
                    clientStatus.SceneIntelligenceModel),
                totalStopwatch.ElapsedMilliseconds,
                false);
        }

        return model;
    }

    private async Task<ChapterStorySceneBlockViewModel> ExecuteSceneAsync(
        ChapterStorySceneBlockViewModel block,
        string sceneContextJson,
        string completedScenePrompt,
        string scenePromptVersion,
        string sceneModel,
        CancellationToken cancellationToken)
    {
        try
        {
            var result = await client.ExecutePromptAsync(
                completedScenePrompt,
                scenePromptVersion,
                cancellationToken,
                sceneModel);
            var sceneJson = ExtractOutputText(result.RawResponseText);
            var parsedScene = JsonSerializer.Deserialize<SceneIntelligenceScene>(sceneJson, JsonOptions);
            var validation = sceneValidator.Validate(parsedScene);

            logger.LogInformation(
                "Admin Chapter Story Intelligence scene completed. TemporarySceneNumber={TemporarySceneNumber} DurationMs={DurationMs} InputTokens={InputTokens} OutputTokens={OutputTokens} ValidationPassed={ValidationPassed} ValidationWarnings={ValidationWarnings} ValidationErrors={ValidationErrors} Success={Success}",
                block.TemporarySceneNumber,
                result.Duration.TotalMilliseconds,
                result.InputTokens,
                result.OutputTokens,
                validation.IsValid,
                validation.Warnings.Count,
                validation.Errors.Count,
                true);

            return block with
            {
                Success = true,
                SceneContextJson = sceneContextJson,
                Duration = FormatDuration(result.Duration),
                DurationMs = Convert.ToInt64(result.Duration.TotalMilliseconds),
                RetryCount = result.RetryCount,
                InputTokens = result.InputTokens,
                OutputTokens = result.OutputTokens,
                RawResponseText = result.RawResponseText,
                SceneJsonText = sceneJson,
                ParsedScene = parsedScene,
                Validation = validation
            };
        }
        catch (Exception ex) when (ex is not OperationCanceledException)
        {
            logger.LogWarning(
                ex,
                "Admin Chapter Story Intelligence scene failed. TemporarySceneNumber={TemporarySceneNumber} Success={Success}",
                block.TemporarySceneNumber,
                false);

            return block with
            {
                Success = false,
                SceneContextJson = sceneContextJson,
                ErrorMessage = ex.Message
            };
        }
    }

    private static ChapterStorySceneBlockViewModel BuildSceneBlock(
        ChapterStoryIntelligenceDryRunForm form,
        IReadOnlyList<string> paragraphs,
        ChapterSceneBoundary boundary)
    {
        var sceneNumber = boundary.SceneNumber ?? 0;
        var start = boundary.StartParagraph ?? 0;
        var end = boundary.EndParagraph ?? 0;

        if (sceneNumber <= 0 || start <= 0 || end <= 0 || start > end || end > paragraphs.Count)
        {
            return new ChapterStorySceneBlockViewModel
            {
                TemporarySceneNumber = sceneNumber,
                StartParagraph = start,
                EndParagraph = end,
                BoundaryReason = boundary.Reason ?? string.Empty,
                BoundaryConfidence = boundary.Confidence,
                SplitValid = false,
                SplitErrorMessage = "Scene boundary range is invalid for the submitted chapter paragraphs."
            };
        }

        var sceneParagraphs = paragraphs.Skip(start - 1).Take(end - start + 1).ToList();
        var sceneText = string.Join(Environment.NewLine + Environment.NewLine, sceneParagraphs);
        return new ChapterStorySceneBlockViewModel
        {
            TemporarySceneNumber = sceneNumber,
            StartParagraph = start,
            EndParagraph = end,
            BoundaryReason = boundary.Reason ?? string.Empty,
            BoundaryConfidence = boundary.Confidence,
            SceneText = sceneText,
            SceneTextPreview = Preview(sceneText),
            SplitValid = true,
            SceneContextJson = BuildSceneContextJson(form, boundary)
        };
    }

    private static string BuildChapterContextJson(ChapterStoryIntelligenceDryRunForm form)
        => JsonSerializer.Serialize(new
        {
            projectId = form.ProjectID,
            bookId = form.BookID,
            chapterId = form.ChapterID,
            chapterNumber = form.ChapterNumber,
            sourceLabel = string.IsNullOrWhiteSpace(form.SourceLabel) ? "Admin pipeline test chapter" : form.SourceLabel.Trim()
        }, JsonOptions);

    private static string BuildSceneContextJson(ChapterStoryIntelligenceDryRunForm form, ChapterSceneBoundary boundary)
        => JsonSerializer.Serialize(new
        {
            projectId = form.ProjectID,
            bookId = form.BookID,
            chapterId = form.ChapterID,
            sceneId = (int?)null,
            chapterNumber = form.ChapterNumber,
            sceneNumber = boundary.SceneNumber,
            sourceLabel = $"{(string.IsNullOrWhiteSpace(form.SourceLabel) ? "Admin pipeline test chapter" : form.SourceLabel.Trim())}, suggested scene {boundary.SceneNumber}"
        }, JsonOptions);

    private static string BuildChapterPrompt(string promptTemplate, string chapterContextJson, string chapterText)
    {
        if (string.IsNullOrWhiteSpace(promptTemplate))
        {
            throw new InvalidOperationException("Prompt template is empty.");
        }

        return promptTemplate
            .Replace("{{CHAPTER_CONTEXT_JSON}}", chapterContextJson ?? string.Empty, StringComparison.Ordinal)
            .Replace("{{CHAPTER_TEXT}}", chapterText ?? string.Empty, StringComparison.Ordinal);
    }

    private static string ExtractOutputText(string rawResponseText)
    {
        var response = JsonSerializer.Deserialize<OpenAIResponseEnvelope>(rawResponseText, JsonOptions)
            ?? throw new JsonException("OpenAI response envelope was empty.");
        var outputText = response.Output?
            .SelectMany(item => item.Content ?? [])
            .FirstOrDefault(content => string.Equals(content.Type, "output_text", StringComparison.OrdinalIgnoreCase)
                && !string.IsNullOrWhiteSpace(content.Text))
            ?.Text;

        if (string.IsNullOrWhiteSpace(outputText))
        {
            throw new JsonException("OpenAI response did not contain output_text content.");
        }

        return outputText.Trim();
    }

    private static string Preview(string value)
    {
        var cleaned = value.ReplaceLineEndings(" ").Trim();
        return cleaned.Length <= 260 ? cleaned : $"{cleaned[..260]}...";
    }

    private static int? SumTokens(int? first, IEnumerable<int?> rest)
    {
        var values = new[] { first }.Concat(rest).ToList();
        return values.Any(value => value.HasValue) ? values.Sum(value => value ?? 0) : null;
    }

    private static string FormatDuration(TimeSpan duration)
        => duration.TotalSeconds < 1
            ? $"{duration.TotalMilliseconds:0} ms"
            : $"{duration.TotalSeconds:0.00} sec";

    private void LogPipelineResult(ChapterStoryIntelligenceDryRunResultViewModel result)
    {
        logger.LogInformation(
            "Admin Chapter Story Intelligence pipeline completed. ChapterPromptVersion={ChapterPromptVersion} ScenePromptVersion={ScenePromptVersion} Model={Model} TotalDuration={TotalDuration} TotalInputTokens={TotalInputTokens} TotalOutputTokens={TotalOutputTokens} ChapterValidationErrors={ChapterValidationErrors} ChapterValidationWarnings={ChapterValidationWarnings} SceneCount={SceneCount} SceneValidationErrors={SceneValidationErrors} SceneValidationWarnings={SceneValidationWarnings} Success={Success}",
            result.ChapterPromptVersion,
            result.ScenePromptVersion,
            result.Model,
            result.TotalDuration,
            result.TotalInputTokens,
            result.TotalOutputTokens,
            result.ChapterValidation?.Errors.Count,
            result.ChapterValidation?.Warnings.Count,
            result.SceneBlocks.Count,
            result.SceneBlocks.Sum(block => block.Validation?.Errors.Count ?? 0),
            result.SceneBlocks.Sum(block => block.Validation?.Warnings.Count ?? 0),
            result.Success);
    }
}
