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

namespace PlotLine.Services;

public interface IStoryIntelligenceResultPersistenceService
{
    Task<int> SaveDisplayedRunAsync(int userId, string payload);
    Task<int> QueueAdminTextRunAsync(int userId, ChapterStoryIntelligenceDryRunForm form);
    Task CancelRunAsync(int runId);
    Task<StoryIntelligenceSavedRunsViewModel> ListRunsAsync();
    Task<StoryIntelligenceSavedRunDetailViewModel?> GetRunDetailAsync(int runId);
}

public sealed class StoryIntelligenceResultPersistenceService(
    IStoryIntelligenceResultRepository repository,
    IStoryIntelligenceClient client,
    ISceneImportResolver importResolver,
    ILogger<StoryIntelligenceResultPersistenceService> logger) : IStoryIntelligenceResultPersistenceService
{
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        PropertyNameCaseInsensitive = true,
        WriteIndented = true
    };

    public async Task<int> SaveDisplayedRunAsync(int userId, string payload)
    {
        if (string.IsNullOrWhiteSpace(payload))
        {
            throw new InvalidOperationException("No dry-run result was submitted for saving.");
        }

        var dryRun = JsonSerializer.Deserialize<ChapterStoryIntelligenceDryRunViewModel>(payload, JsonOptions)
            ?? throw new InvalidOperationException("The submitted dry-run result could not be read.");
        if (dryRun.Result is null)
        {
            throw new InvalidOperationException("The submitted dry-run result is empty.");
        }

        var request = BuildSaveRequest(userId, dryRun);
        var runId = await repository.SaveAsync(request);

        logger.LogInformation(
            "Saved Story Intelligence admin dry-run. StoryIntelligenceRunID={StoryIntelligenceRunID} UserID={UserID} Status={Status} ChapterPromptVersion={ChapterPromptVersion} ScenePromptVersion={ScenePromptVersion} Model={Model} SceneCount={SceneCount} ValidationErrors={ValidationErrors} ValidationWarnings={ValidationWarnings} TotalDurationMs={TotalDurationMs} TotalTokens={TotalTokens}",
            runId,
            userId,
            request.Status,
            dryRun.Result.ChapterPromptVersion,
            dryRun.Result.ScenePromptVersion,
            dryRun.Result.Model,
            request.SceneResults.Count,
            CountErrors(dryRun.Result),
            CountWarnings(dryRun.Result),
            request.TotalDurationMs,
            request.TotalTokens);

        return runId;
    }

    public async Task<int> QueueAdminTextRunAsync(int userId, ChapterStoryIntelligenceDryRunForm form)
    {
        if (string.IsNullOrWhiteSpace(form.ChapterText))
        {
            throw new InvalidOperationException("Enter chapter text before queueing a persisted Story Intelligence run.");
        }

        var promptVersionsSummary = "Chapter=Chapter-Structure-Prompt-V2; Scene=Scene-Prompt-V2";
        var clientStatus = client.GetConfigurationStatus();
        return await repository.QueueAdminTextAsync(new StoryIntelligenceRunQueueRequest
        {
            UserID = userId,
            ProjectID = form.ProjectID,
            BookID = form.BookID,
            ChapterID = form.ChapterID,
            ChapterNumber = form.ChapterNumber,
            SourceType = "AdminText",
            SourceLabel = CleanSourceLabel(form.SourceLabel),
            ChapterText = form.ChapterText,
            SourceWordCount = CountWords(form.ChapterText),
            SourceCharacterCount = form.ChapterText.Length,
            SourceParagraphCount = StoryIntelligenceParagraphs.Split(form.ChapterText).Count,
            SourceChapterCount = 1,
            Model = StoryIntelligenceOptions.BuildModelSummary(
                clientStatus.ChapterStructureModel,
                clientStatus.SceneIntelligenceModel),
            PromptVersionsSummary = promptVersionsSummary
        });
    }

    public Task CancelRunAsync(int runId)
        => repository.RequestCancelAsync(runId);

    public async Task<StoryIntelligenceSavedRunsViewModel> ListRunsAsync()
        => new() { Runs = await repository.ListRunsAsync() };

    public async Task<StoryIntelligenceSavedRunDetailViewModel?> GetRunDetailAsync(int runId)
    {
        var run = await repository.GetRunAsync(runId);
        if (run is null)
        {
            return null;
        }

        var sceneResults = await repository.ListSceneResultsAsync(runId);
        return new StoryIntelligenceSavedRunDetailViewModel
        {
            Run = run,
            ChapterResult = await repository.GetChapterResultAsync(runId),
            SceneResults = sceneResults,
            ImportPreview = await importResolver.ResolveAsync(run, sceneResults),
            ImportCommit = await repository.GetImportCommitAsync(runId)
        };
    }

    private static StoryIntelligenceRunSaveRequest BuildSaveRequest(int userId, ChapterStoryIntelligenceDryRunViewModel dryRun)
    {
        var form = dryRun.Form;
        var result = dryRun.Result!;
        var completedUtc = DateTime.UtcNow;
        var startedUtc = result.TotalDurationMs.HasValue
            ? completedUtc.AddMilliseconds(-result.TotalDurationMs.Value)
            : completedUtc;
        var errorCount = CountErrors(result);
        var warningCount = CountWarnings(result);
        var sceneFailed = result.SceneBlocks.Any(block => !block.Success && block.SplitValid);
        var status = !string.IsNullOrWhiteSpace(result.ErrorMessage) || result.PipelineStopped || errorCount > 0 || sceneFailed
            ? StoryIntelligenceRunStatuses.Failed
            : warningCount > 0
                ? StoryIntelligenceRunStatuses.CompletedWithWarnings
                : StoryIntelligenceRunStatuses.Completed;
        var promptVersionsSummary = $"Chapter={result.ChapterPromptVersion}; Scene={result.ScenePromptVersion}";

        return new StoryIntelligenceRunSaveRequest
        {
            UserID = userId,
            ProjectID = form.ProjectID,
            BookID = form.BookID,
            Status = status,
            SourceType = "AdminDryRun",
            SourceWordCount = CountWords(form.ChapterText),
            SourceCharacterCount = string.IsNullOrEmpty(form.ChapterText) ? null : form.ChapterText.Length,
            SourceChapterCount = 1,
            PromptVersion = promptVersionsSummary,
            PromptVersionsSummary = promptVersionsSummary,
            Model = StoryIntelligenceOptions.ParseModelSummary(result.Model, result.Model, result.Model).ChapterStructureModel,
            StartedUtc = startedUtc,
            CompletedUtc = completedUtc,
            FailureStage = DetermineFailureStage(result),
            TotalInputTokens = result.TotalInputTokens,
            TotalOutputTokens = result.TotalOutputTokens,
            TotalTokens = SumTokens(result.TotalInputTokens, result.TotalOutputTokens),
            TotalDurationMs = result.TotalDurationMs,
            ErrorMessage = FirstError(result),
            ErrorDetail = BuildErrorDetail(result),
            ChapterResult = BuildChapterResult(form, result),
            SceneResults = result.SceneBlocks.Select(block => BuildSceneResult(form, result, block)).ToList()
        };
    }

    private static StoryIntelligenceChapterResultSaveRequest BuildChapterResult(
        ChapterStoryIntelligenceDryRunForm form,
        ChapterStoryIntelligenceDryRunResultViewModel result)
        => new()
        {
            ProjectID = form.ProjectID,
            BookID = form.BookID,
            ChapterID = form.ChapterID,
            ChapterNumber = form.ChapterNumber,
            SourceLabel = CleanSourceLabel(form.SourceLabel),
            PromptVersion = result.ChapterPromptVersion,
            Model = StoryIntelligenceOptions.ParseModelSummary(result.Model, result.Model, result.Model).SceneIntelligenceModel,
            RawResponseJson = result.ChapterRawResponseText,
            OutputTextJson = result.ChapterJsonText,
            ParsedJson = SerializeParsed(result.ParsedChapter),
            ValidationErrorsCount = result.ChapterValidation?.Errors.Count ?? 0,
            ValidationWarningsCount = result.ChapterValidation?.Warnings.Count ?? 0,
            InputTokens = result.ChapterInputTokens,
            OutputTokens = result.ChapterOutputTokens,
            TotalTokens = SumTokens(result.ChapterInputTokens, result.ChapterOutputTokens),
            DurationMs = result.ChapterDurationMs
        };

    private static StoryIntelligenceSceneResultSaveRequest BuildSceneResult(
        ChapterStoryIntelligenceDryRunForm form,
        ChapterStoryIntelligenceDryRunResultViewModel result,
        ChapterStorySceneBlockViewModel block)
        => new()
        {
            ProjectID = form.ProjectID,
            BookID = form.BookID,
            ChapterID = form.ChapterID,
            SceneID = null,
            TemporarySceneNumber = block.TemporarySceneNumber,
            StartParagraph = block.StartParagraph > 0 ? block.StartParagraph : null,
            EndParagraph = block.EndParagraph > 0 ? block.EndParagraph : null,
            SourceLabel = $"{CleanSourceLabel(form.SourceLabel)}, suggested scene {block.TemporarySceneNumber}",
            PromptVersion = result.ScenePromptVersion,
            Model = result.Model,
            RawResponseJson = block.RawResponseText,
            OutputTextJson = block.SceneJsonText,
            ParsedJson = SerializeParsed(block.ParsedScene),
            ValidationErrorsCount = block.Validation?.Errors.Count ?? 0,
            ValidationWarningsCount = block.Validation?.Warnings.Count ?? 0,
            InputTokens = block.InputTokens,
            OutputTokens = block.OutputTokens,
            TotalTokens = SumTokens(block.InputTokens, block.OutputTokens),
            DurationMs = block.DurationMs
        };

    private static string SerializeParsed<T>(T? value)
        => value is null ? string.Empty : JsonSerializer.Serialize(value, JsonOptions);

    private static string CleanSourceLabel(string? value)
        => string.IsNullOrWhiteSpace(value) ? "Admin pipeline test chapter" : value.Trim();

    private static int? SumTokens(int? inputTokens, int? outputTokens)
        => inputTokens.HasValue || outputTokens.HasValue ? (inputTokens ?? 0) + (outputTokens ?? 0) : null;

    private static int? CountWords(string? value)
        => string.IsNullOrWhiteSpace(value)
            ? null
            : value.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries).Length;

    private static int CountErrors(ChapterStoryIntelligenceDryRunResultViewModel result)
        => (result.ChapterValidation?.Errors.Count ?? 0)
            + result.SceneBlocks.Sum(block => block.Validation?.Errors.Count ?? 0);

    private static int CountWarnings(ChapterStoryIntelligenceDryRunResultViewModel result)
        => (result.ChapterValidation?.Warnings.Count ?? 0)
            + result.SceneBlocks.Sum(block => block.Validation?.Warnings.Count ?? 0);

    private static string? FirstError(ChapterStoryIntelligenceDryRunResultViewModel result)
        => !string.IsNullOrWhiteSpace(result.ErrorMessage)
            ? result.ErrorMessage
            : result.SceneBlocks.FirstOrDefault(block => !string.IsNullOrWhiteSpace(block.ErrorMessage))?.ErrorMessage;

    private static string? DetermineFailureStage(ChapterStoryIntelligenceDryRunResultViewModel result)
    {
        if (string.IsNullOrWhiteSpace(result.ErrorMessage)
            && !result.PipelineStopped
            && result.ChapterValidation?.Errors.Count is not > 0
            && result.SceneBlocks.All(block => block.Success || !block.SplitValid)
            && result.SceneBlocks.All(block => block.Validation?.Errors.Count is not > 0))
        {
            return null;
        }

        if (result.ChapterValidation?.Errors.Count is > 0 || result.StopReason.Contains("validation", StringComparison.OrdinalIgnoreCase))
        {
            return StoryIntelligenceFailureStages.Validation;
        }

        if (result.SceneBlocks.Any(block => !block.SplitValid))
        {
            return StoryIntelligenceFailureStages.SceneSplit;
        }

        if (result.SceneBlocks.Any(block => !block.Success || !string.IsNullOrWhiteSpace(block.ErrorMessage)))
        {
            return StoryIntelligenceFailureStages.SceneIntelligence;
        }

        return StoryIntelligenceFailureStages.ChapterStructure;
    }

    private static string? BuildErrorDetail(ChapterStoryIntelligenceDryRunResultViewModel result)
    {
        var details = new List<string>();
        if (!string.IsNullOrWhiteSpace(result.StopReason))
        {
            details.Add(result.StopReason);
        }

        if (!string.IsNullOrWhiteSpace(result.ErrorMessage))
        {
            details.Add(result.ErrorMessage);
        }

        details.AddRange(result.ChapterValidation?.Errors.Select(issue => $"{issue.Path}: {issue.Message}") ?? []);
        details.AddRange(result.SceneBlocks
            .Where(block => !string.IsNullOrWhiteSpace(block.ErrorMessage))
            .Select(block => $"Scene {block.TemporarySceneNumber}: {block.ErrorMessage}"));
        details.AddRange(result.SceneBlocks
            .Where(block => block.Validation is not null)
            .SelectMany(block => block.Validation!.Errors.Select(issue => $"Scene {block.TemporarySceneNumber} {issue.Path}: {issue.Message}")));

        return details.Count == 0 ? null : string.Join(Environment.NewLine, details);
    }
}
