using System.Text;
using TornadoAce_CJOnStyle.Domain;

namespace TornadoAce_CJOnStyle.Services
{
    public sealed class PocAutomationEngine
    {
        private readonly AutomationWorkspace workspace;

        public PocAutomationEngine(AutomationWorkspace workspace)
        {
            this.workspace = workspace;
        }

        public CueSheetSelection SelectCueSheet(int index)
        {
            if (workspace.CueSheets.Count == 0)
            {
                throw new InvalidOperationException("큐시트 목 데이터가 없습니다.");
            }

            var normalizedIndex = ((index % workspace.CueSheets.Count) + workspace.CueSheets.Count) % workspace.CueSheets.Count;
            return new CueSheetSelection(workspace.CueSheets[normalizedIndex], normalizedIndex + 1, workspace.CueSheets.Count);
        }

        public SceneDataSnapshot GenerateScene(CueSheetRecord cueSheet, SceneTemplateDefinition template)
        {
            return BuildSnapshot(cueSheet, template, BuildVisibleTextObjects(cueSheet, template));
        }

        public PocInspectionSummary InspectWorkspace(SceneTemplateDefinition template)
        {
            var results = workspace.CueSheets
                .Select(cueSheet => Inspect(cueSheet, GenerateExtractedScene(cueSheet, template), template))
                .ToArray();

            var reviewCount = results.Count(result => result.Severity is "High" or "Medium");
            var passedCount = results.Length - reviewCount;
            var averageMatchRate = results.Length == 0 ? 100 : results.Average(result => result.MatchRate);

            return new PocInspectionSummary(results, passedCount, reviewCount, averageMatchRate);
        }

        private InspectionResult Inspect(CueSheetRecord cueSheet, SceneDataSnapshot snapshot, SceneTemplateDefinition template)
        {
            var expectedObjects = BuildVisibleTextObjects(cueSheet, template);
            var fieldScores = expectedObjects
                .Select(expected =>
                {
                    snapshot.VisibleTextObjects.TryGetValue(expected.Key, out var actualValue);
                    var score = CalculateSimilarity(expected.Value, actualValue ?? string.Empty);
                    return new FieldInspectionScore(expected.Key, score);
                })
                .ToArray();

            var matchRate = fieldScores.Length == 0 ? 100 : fieldScores.Average(field => field.Score);
            var hasImportantMismatch = fieldScores.Any(field => field.Score < 99.5 && IsImportantSceneObject(field.SceneObjectName));
            var mismatchCount = fieldScores.Count(field => field.Score < 99.5);

            var severity = hasImportantMismatch || mismatchCount > 1
                ? "High"
                : mismatchCount == 1
                    ? "Medium"
                    : "Low";

            var decision = severity switch
            {
                "High" => "운영자 확인 필요",
                "Medium" => "LLM 검토 후보",
                _ => "1차 통과"
            };

            return new InspectionResult(cueSheet.CueSheetId, cueSheet.ProgramTitle, snapshot.SceneName, matchRate, severity, decision);
        }

        private SceneDataSnapshot GenerateExtractedScene(CueSheetRecord cueSheet, SceneTemplateDefinition template)
        {
            var visibleObjects = BuildVisibleTextObjects(cueSheet, template);

            if (cueSheet.CueSheetId.EndsWith("-002", StringComparison.Ordinal))
            {
                visibleObjects["TXT_PRICE"] = "79,900원";
            }

            if (cueSheet.CueSheetId.EndsWith("-003", StringComparison.Ordinal))
            {
                visibleObjects["TXT_PROMOTION"] = "앱 주문 추가 적립 ※";
            }

            return BuildSnapshot(cueSheet, template, visibleObjects);
        }

        private static SceneDataSnapshot BuildSnapshot(
            CueSheetRecord cueSheet,
            SceneTemplateDefinition template,
            IReadOnlyDictionary<string, string> visibleObjects)
        {
            var sceneName = $"{template.TemplateId}_{cueSheet.BroadcastTime:HHmm}.tscn";
            return new SceneDataSnapshot(sceneName, template.TemplateId, visibleObjects, [BuildImagePath(cueSheet)]);
        }

        private static Dictionary<string, string> BuildVisibleTextObjects(CueSheetRecord cueSheet, SceneTemplateDefinition template)
        {
            return template.Mappings.ToDictionary(
                mapping => mapping.SceneObjectName,
                mapping => ReadCueSheetValue(cueSheet, mapping.CueSheetField),
                StringComparer.OrdinalIgnoreCase);
        }

        private static string ReadCueSheetValue(CueSheetRecord cueSheet, string fieldName)
        {
            return fieldName switch
            {
                "programTitle" => cueSheet.ProgramTitle,
                "productName" => cueSheet.ProductName,
                "priceText" => cueSheet.PriceText,
                "compositionText" => cueSheet.CompositionText,
                "promotionText" => cueSheet.PromotionText,
                _ => string.Empty
            };
        }

        private static string BuildImagePath(CueSheetRecord cueSheet)
        {
            var directoryName = new string(cueSheet.ProductName
                .Select(character => char.IsLetterOrDigit(character) ? character : '_')
                .ToArray()).Trim('_');

            return $@"D:\CJOnStyle\Products\{directoryName}\main.png";
        }

        private static bool IsImportantSceneObject(string sceneObjectName)
        {
            return sceneObjectName.Contains("PRICE", StringComparison.OrdinalIgnoreCase)
                || sceneObjectName.Contains("COMPOSITION", StringComparison.OrdinalIgnoreCase);
        }

        private static double CalculateSimilarity(string expected, string actual)
        {
            var normalizedExpected = Normalize(expected);
            var normalizedActual = Normalize(actual);

            if (normalizedExpected == normalizedActual)
            {
                return 100;
            }

            if (normalizedExpected.Length == 0 || normalizedActual.Length == 0)
            {
                return 0;
            }

            var distance = CalculateLevenshteinDistance(normalizedExpected, normalizedActual);
            var maxLength = Math.Max(normalizedExpected.Length, normalizedActual.Length);
            return Math.Max(0, 100 * (1 - distance / (double)maxLength));
        }

        private static string Normalize(string value)
        {
            var normalized = value.Normalize(NormalizationForm.FormKC);
            var chars = normalized.Where(character => !char.IsWhiteSpace(character)).ToArray();
            return new string(chars).ToUpperInvariant();
        }

        private static int CalculateLevenshteinDistance(string source, string target)
        {
            var previousRow = Enumerable.Range(0, target.Length + 1).ToArray();
            var currentRow = new int[target.Length + 1];

            for (var sourceIndex = 0; sourceIndex < source.Length; sourceIndex++)
            {
                currentRow[0] = sourceIndex + 1;

                for (var targetIndex = 0; targetIndex < target.Length; targetIndex++)
                {
                    var substitutionCost = source[sourceIndex] == target[targetIndex] ? 0 : 1;
                    currentRow[targetIndex + 1] = Math.Min(
                        Math.Min(
                            currentRow[targetIndex] + 1,
                            previousRow[targetIndex + 1] + 1),
                        previousRow[targetIndex] + substitutionCost);
                }

                (previousRow, currentRow) = (currentRow, previousRow);
            }

            return previousRow[target.Length];
        }

        private sealed record FieldInspectionScore(string SceneObjectName, double Score);
    }

    public sealed record CueSheetSelection(CueSheetRecord Record, int Position, int TotalCount);

    public sealed record PocInspectionSummary(
        IReadOnlyList<InspectionResult> Results,
        int PassedCount,
        int ReviewCount,
        double AverageMatchRate);
}
