using System.Text;
using TornadoAce_CJOnStyle.Domain;

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

        public PocAutomationEngine(AutomationWorkspace workspace, ITornadoSceneAdapter? tornadoSceneAdapter = null)
        {
            this.workspace = workspace;
            this.tornadoSceneAdapter = tornadoSceneAdapter ?? new MockTornadoSceneAdapter(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 tornadoSceneAdapter
                .CreateOrUpdateSceneAsync(cueSheet, template, CancellationToken.None)
                .GetAwaiter()
                .GetResult();
        }

        public IReadOnlyList<TornadoAdapterCheck> GetTornadoAdapterChecks()
        {
            return tornadoSceneAdapter is MockTornadoSceneAdapter mockAdapter
                ? mockAdapter.GetAdapterChecks()
                :
                [
                    new TornadoAdapterCheck("SDK-01", "Tornado Scene Adapter", "준비", tornadoSceneAdapter.GetType().Name)
                ];
        }

        public IReadOnlyList<MappingValidationResult> ValidateMappingProfile(SceneTemplateDefinition template)
        {
            var requiredFields = new[] { "programTitle", "productName", "priceText", "compositionText", "promotionText" };
            var supportedAttributes = new[] { "Text", "Font", "Color", "Style", "Image" };
            var mappedFields = template.Mappings
                .Select(mapping => mapping.CueSheetField)
                .ToHashSet(StringComparer.OrdinalIgnoreCase);
            var missingFields = requiredFields
                .Where(field => !mappedFields.Contains(field))
                .ToArray();
            var duplicateSceneObjects = template.Mappings
                .GroupBy(mapping => mapping.SceneObjectName, StringComparer.OrdinalIgnoreCase)
                .Where(group => group.Count() > 1)
                .Select(group => group.Key)
                .ToArray();
            var unsupportedAttributes = template.Mappings
                .Where(mapping => !supportedAttributes.Contains(mapping.Attribute, StringComparer.OrdinalIgnoreCase))
                .Select(mapping => $"{mapping.SceneObjectName}:{mapping.Attribute}")
                .ToArray();

            return
            [
                BuildValidationResult(
                    "MAP-01",
                    "필수 큐시트 필드",
                    missingFields.Length == 0,
                    missingFields.Length == 0 ? "5개 필드 매핑 완료" : $"누락: {string.Join(", ", missingFields)}",
                    "High"),
                BuildValidationResult(
                    "MAP-02",
                    "Tornado 객체 중복",
                    duplicateSceneObjects.Length == 0,
                    duplicateSceneObjects.Length == 0 ? "중복 없음" : $"중복: {string.Join(", ", duplicateSceneObjects)}",
                    "High"),
                BuildValidationResult(
                    "MAP-03",
                    "지원 속성",
                    unsupportedAttributes.Length == 0,
                    unsupportedAttributes.Length == 0 ? "Text 속성 기준 통과" : $"확인: {string.Join(", ", unsupportedAttributes)}",
                    "Medium")
            ];
        }

        public ErpScheduleRecord? FindSchedule(CueSheetRecord cueSheet)
        {
            return workspace.ErpSchedules
                .OrderBy(schedule => Math.Abs((schedule.BroadcastTime - cueSheet.BroadcastTime).TotalMinutes))
                .FirstOrDefault(schedule =>
                    string.Equals(schedule.ProgramTitle, cueSheet.ProgramTitle, StringComparison.OrdinalIgnoreCase)
                    && Math.Abs((schedule.BroadcastTime - cueSheet.BroadcastTime).TotalMinutes) <= 15);
        }

        public PocInspectionSummary InspectWorkspace(SceneTemplateDefinition template)
        {
            var results = workspace.CueSheets
                .Select(cueSheet =>
                {
                    var generatedScene = GenerateScene(cueSheet, template);
                    var extractedScene = tornadoSceneAdapter
                        .ExtractSceneDataAsync(generatedScene.SceneName, CancellationToken.None)
                        .GetAwaiter()
                        .GetResult();

                    return Inspect(cueSheet, extractedScene, 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);
        }

        public IReadOnlyList<LlmReviewRequest> BuildLlmReviewRequests(IEnumerable<InspectionResult> inspectionResults)
        {
            return inspectionResults
                .Select(result =>
                {
                    var failedFields = result.FieldResults
                        .Where(field => field.MatchRate < 99.5)
                        .Select(field => new LlmReviewField(
                            field.CueSheetField,
                            field.SceneObjectName,
                            field.ExpectedText,
                            field.ActualText,
                            field.MatchRate,
                            field.Severity))
                        .ToArray();

                    if (failedFields.Length == 0)
                    {
                        return null;
                    }

                    var priority = failedFields.Any(field => field.Severity == "High") ? "High" : "Medium";
                    return new LlmReviewRequest(
                        $"LLM-{result.CueSheetId[^3..]}",
                        result.CueSheetId,
                        result.ProgramTitle,
                        result.SceneName,
                        "cj-onstyle-cg-inspection-v1",
                        "사내 LLM 우선, 외부 LLM은 정책 승인 후 사용",
                        priority,
                        failedFields);
                })
                .OfType<LlmReviewRequest>()
                .ToArray();
        }

        public PocRunResult RunFullPoc(int selectedCueSheetIndex, SceneTemplateDefinition template)
        {
            var selection = SelectCueSheet(selectedCueSheetIndex);
            var schedule = FindSchedule(selection.Record);
            var generatedScene = GenerateScene(selection.Record, template);
            var mappingValidations = ValidateMappingProfile(template);
            var adapterChecks = GetTornadoAdapterChecks();
            var inspectionSummary = InspectWorkspace(template);
            var llmRequests = BuildLlmReviewRequests(inspectionSummary.Results);

            return new PocRunResult(
                selection,
                schedule,
                generatedScene,
                mappingValidations,
                adapterChecks,
                inspectionSummary,
                llmRequests);
        }

        private InspectionResult Inspect(CueSheetRecord cueSheet, SceneDataSnapshot snapshot, SceneTemplateDefinition template)
        {
            var expectedObjects = BuildVisibleTextObjects(cueSheet, template);
            var fieldResults = template.Mappings
                .Select(mapping =>
                {
                    var expectedValue = expectedObjects[mapping.SceneObjectName];
                    snapshot.VisibleTextObjects.TryGetValue(mapping.SceneObjectName, out var actualValue);
                    var score = CalculateSimilarity(expectedValue, actualValue ?? string.Empty);
                    var severity = ResolveFieldSeverity(mapping.SceneObjectName, score);

                    return new InspectionFieldResult(
                        mapping.CueSheetField,
                        mapping.SceneObjectName,
                        expectedValue,
                        actualValue ?? string.Empty,
                        score,
                        severity,
                        ResolveFieldDecision(severity));
                })
                .ToArray();

            var matchRate = fieldResults.Length == 0 ? 100 : fieldResults.Average(field => field.MatchRate);
            var hasImportantMismatch = fieldResults.Any(field => field.Severity == "High");
            var mismatchCount = fieldResults.Count(field => field.MatchRate < 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, fieldResults);
        }

        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 MappingValidationResult BuildValidationResult(
            string checkId,
            string title,
            bool isPassed,
            string detail,
            string failureSeverity)
        {
            return new MappingValidationResult(
                checkId,
                title,
                isPassed ? "통과" : "확인",
                detail,
                isPassed ? "Low" : failureSeverity);
        }

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

        private static string ResolveFieldSeverity(string sceneObjectName, double matchRate)
        {
            if (matchRate >= 99.5)
            {
                return "Low";
            }

            return IsImportantSceneObject(sceneObjectName) ? "High" : "Medium";
        }

        private static string ResolveFieldDecision(string severity)
        {
            return severity switch
            {
                "High" => "운영자 확인",
                "Medium" => "LLM 후보",
                _ => "일치"
            };
        }

        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];
        }
    }

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

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

    public sealed record PocRunResult(
        CueSheetSelection Selection,
        ErpScheduleRecord? Schedule,
        SceneDataSnapshot GeneratedScene,
        IReadOnlyList<MappingValidationResult> MappingValidations,
        IReadOnlyList<TornadoAdapterCheck> AdapterChecks,
        PocInspectionSummary InspectionSummary,
        IReadOnlyList<LlmReviewRequest> LlmReviewRequests);
}
