using System.Text.Json;
using PlotLine.Models;
using PlotLine.Models.StoryIntelligence;

namespace PlotLine.Services;

public static class ChapterStructureBoundaryNormaliser
{
    public static ChapterStructureBoundaryNormalisationResult Normalise(ChapterStructureModel? chapterStructure, int paragraphCount)
    {
        if (chapterStructure?.SceneBoundaries is null || chapterStructure.SceneBoundaries.Count < 2 || paragraphCount <= 0)
        {
            return new ChapterStructureBoundaryNormalisationResult(chapterStructure, []);
        }

        var boundaries = chapterStructure.SceneBoundaries;
        var finalIndex = boundaries.Count - 1;
        var final = boundaries[finalIndex];
        var previous = boundaries[finalIndex - 1];
        var phantomParagraph = paragraphCount + 1;

        if (IsTrailingEmptyBoundary(final, previous, paragraphCount, phantomParagraph)
            || IsTrailingInvalidPlaceholder(final, previous, paragraphCount, phantomParagraph))
        {
            var normalisedBoundaries = boundaries
                .Take(finalIndex)
                .Select((boundary, index) => CopyBoundary(boundary, index + 1))
                .ToList();

            var normalised = new ChapterStructureModel
            {
                SchemaVersion = chapterStructure.SchemaVersion,
                ChapterSummary = chapterStructure.ChapterSummary,
                SceneBoundaries = normalisedBoundaries,
                ExtensionData = chapterStructure.ExtensionData
            };

            var issue = new ValidationIssue
            {
                Severity = "Warning",
                Path = $"sceneBoundaries[{finalIndex}]",
                Message = BuildNormalisationMessage(final, finalIndex, paragraphCount, phantomParagraph),
                SuggestedFix = "Do not return a final scene boundary unless it references an actual supplied paragraph."
            };

            return new ChapterStructureBoundaryNormalisationResult(normalised, [issue]);
        }

        return new ChapterStructureBoundaryNormalisationResult(chapterStructure, []);
    }

    public static void AddIssuesTo(ValidationResult validation, ChapterStructureBoundaryNormalisationResult normalisation)
    {
        foreach (var issue in normalisation.Issues)
        {
            validation.Warnings.Add(issue);
        }
    }

    public static string SerialiseParsedModel(ChapterStructureModel? chapterStructure, ChapterStructureBoundaryNormalisationResult normalisation, JsonSerializerOptions jsonOptions)
    {
        if (chapterStructure is null)
        {
            return string.Empty;
        }

        if (normalisation.Issues.Count == 0)
        {
            return JsonSerializer.Serialize(chapterStructure, jsonOptions);
        }

        return JsonSerializer.Serialize(
            new
            {
                chapterStructure.SchemaVersion,
                chapterStructure.ChapterSummary,
                chapterStructure.SceneBoundaries,
                Normalisation = normalisation.Issues.Select(issue => new
                {
                    issue.Severity,
                    issue.Path,
                    issue.Message,
                    issue.SuggestedFix
                })
            },
            jsonOptions);
    }

    private static ChapterSceneBoundary CopyBoundary(ChapterSceneBoundary boundary, int sceneNumber)
        => new()
        {
            SceneNumber = sceneNumber,
            StartParagraph = boundary.StartParagraph,
            EndParagraph = boundary.EndParagraph,
            Confidence = boundary.Confidence,
            Reason = boundary.Reason,
            ExtensionData = boundary.ExtensionData
        };

    private static bool IsTrailingEmptyBoundary(
        ChapterSceneBoundary final,
        ChapterSceneBoundary previous,
        int paragraphCount,
        int phantomParagraph)
        => final.StartParagraph == phantomParagraph
            && final.EndParagraph == phantomParagraph
            && previous.EndParagraph == paragraphCount
            && final.StartParagraph > paragraphCount
            && final.EndParagraph > paragraphCount;

    private static bool IsTrailingInvalidPlaceholder(
        ChapterSceneBoundary final,
        ChapterSceneBoundary previous,
        int paragraphCount,
        int phantomParagraph)
    {
        if (!final.StartParagraph.HasValue || !final.EndParagraph.HasValue || previous.EndParagraph != paragraphCount)
        {
            return false;
        }

        var invalidTrailingRange = final.StartParagraph.Value > paragraphCount
            && (final.EndParagraph.Value <= paragraphCount
                || final.EndParagraph.Value == phantomParagraph
                || final.EndParagraph.Value < final.StartParagraph.Value);

        return invalidTrailingRange && HasPlaceholderSignal(final);
    }

    private static bool HasPlaceholderSignal(ChapterSceneBoundary boundary)
    {
        if (boundary.Confidence <= 0.05m)
        {
            return true;
        }

        var reason = boundary.Reason ?? string.Empty;
        return reason.Contains("invalid", StringComparison.OrdinalIgnoreCase)
            || reason.Contains("placeholder", StringComparison.OrdinalIgnoreCase)
            || reason.Contains("ensure coverage", StringComparison.OrdinalIgnoreCase)
            || reason.Contains("coverage", StringComparison.OrdinalIgnoreCase);
    }

    private static string BuildNormalisationMessage(
        ChapterSceneBoundary final,
        int finalIndex,
        int paragraphCount,
        int phantomParagraph)
    {
        var start = final.StartParagraph ?? 0;
        var end = final.EndParagraph ?? 0;
        if (start == phantomParagraph && end == phantomParagraph)
        {
            return $"Discarded trailing empty scene boundary {phantomParagraph}-{phantomParagraph} because submitted chapter has {paragraphCount} paragraphs and previous scene already ended at {paragraphCount}.";
        }

        var sceneNumber = final.SceneNumber ?? finalIndex + 1;
        return $"Discarded trailing invalid placeholder scene {sceneNumber} with range {start}-{end} because previous scene already ended at paragraph {paragraphCount}.";
    }
}

public sealed record ChapterStructureBoundaryNormalisationResult(
    ChapterStructureModel? ChapterStructure,
    IReadOnlyList<ValidationIssue> Issues);
