using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Tornado3_2026Election.Services;

public sealed class KarismaSceneVariableCatalog
{
    private static readonly string[] PreferredReportNames =
    [
        "TSCN_VARIABLE_DISCOVERY_E_DRIVE.md",
        "TSCN_VARIABLE_DISCOVERY_ELECT2026_NORMAL.md",
        "TSCN_VARIABLE_DISCOVERY_ONE.md",
        "TSCN_VARIABLE_DISCOVERY.md"
    ];

    private static readonly IReadOnlyDictionary<string, KarismaSceneVariableDefinition> EmptySceneVariables =
        new Dictionary<string, KarismaSceneVariableDefinition>(StringComparer.OrdinalIgnoreCase);

    private readonly IReadOnlyDictionary<string, IReadOnlyDictionary<string, KarismaSceneVariableDefinition>> _scenes;

    private KarismaSceneVariableCatalog(
        IReadOnlyDictionary<string, IReadOnlyDictionary<string, KarismaSceneVariableDefinition>> scenes)
    {
        _scenes = scenes;
    }

    public static KarismaSceneVariableCatalog Load(LogService logService)
    {
        var reportPaths = FindDiscoveryReportPaths().ToArray();
        if (reportPaths.Length == 0)
        {
            logService.Warning("Karisma scene variable catalog report was not found. Falling back to runtime value heuristics.");
            return new KarismaSceneVariableCatalog(
                new Dictionary<string, IReadOnlyDictionary<string, KarismaSceneVariableDefinition>>(StringComparer.OrdinalIgnoreCase));
        }

        try
        {
            var mergedScenes = new Dictionary<string, Dictionary<string, KarismaSceneVariableDefinition>>(StringComparer.OrdinalIgnoreCase);
            foreach (var reportPath in reportPaths)
            {
                MergeScenes(mergedScenes, ParseReport(reportPath));
            }

            var scenes = mergedScenes.ToDictionary(
                pair => pair.Key,
                pair => (IReadOnlyDictionary<string, KarismaSceneVariableDefinition>)pair.Value,
                StringComparer.OrdinalIgnoreCase);
            logService.Info($"Karisma scene variable catalog loaded: scenes={scenes.Count} sources={reportPaths.Length}.");
            return new KarismaSceneVariableCatalog(scenes);
        }
        catch (Exception ex)
        {
            logService.Warning($"Failed to load Karisma scene variable catalog: {ex.Message}");
            return new KarismaSceneVariableCatalog(
                new Dictionary<string, IReadOnlyDictionary<string, KarismaSceneVariableDefinition>>(StringComparer.OrdinalIgnoreCase));
        }
    }

    public IReadOnlyDictionary<string, KarismaSceneVariableDefinition> GetSceneVariables(string t3CutPath, string scenePath)
    {
        if (_scenes.Count == 0 ||
            string.IsNullOrWhiteSpace(t3CutPath) ||
            string.IsNullOrWhiteSpace(scenePath))
        {
            return EmptySceneVariables;
        }

        var relativePath = NormalizeRelativePath(Path.GetRelativePath(t3CutPath, scenePath));
        if (_scenes.TryGetValue(relativePath, out var variables))
        {
            return variables;
        }

        var fileName = Path.GetFileName(relativePath);
        if (!string.IsNullOrWhiteSpace(fileName))
        {
            var fileNameMatches = _scenes
                .Where(pair => string.Equals(Path.GetFileName(pair.Key), fileName, StringComparison.OrdinalIgnoreCase))
                .Take(2)
                .ToArray();
            if (fileNameMatches.Length == 1)
            {
                return fileNameMatches[0].Value;
            }
        }

        return EmptySceneVariables;
    }

    private static IReadOnlyDictionary<string, IReadOnlyDictionary<string, KarismaSceneVariableDefinition>> ParseReport(string reportPath)
    {
        var scenes = new Dictionary<string, Dictionary<string, KarismaSceneVariableDefinition>>(StringComparer.OrdinalIgnoreCase);
        string? currentScene = null;
        string? reportRootRelativePath = null;

        foreach (var rawLine in File.ReadLines(reportPath, Encoding.UTF8))
        {
            var line = rawLine.Trim();
            if (TryParseReportRoot(line, out var reportRoot))
            {
                reportRootRelativePath = NormalizeReportRootRelativePath(reportRoot);
                continue;
            }

            if (TryParseSceneHeader(line, out var sceneRelativePath))
            {
                currentScene = NormalizeSceneKey(reportRootRelativePath, sceneRelativePath);
                if (!scenes.ContainsKey(currentScene))
                {
                    scenes[currentScene] = new Dictionary<string, KarismaSceneVariableDefinition>(StringComparer.OrdinalIgnoreCase);
                }

                continue;
            }

            if (string.IsNullOrWhiteSpace(currentScene) || !line.StartsWith('|'))
            {
                continue;
            }

            var cells = SplitMarkdownRow(line);
            if (cells.Count < 4 ||
                string.Equals(cells[0], "Variable", StringComparison.OrdinalIgnoreCase) ||
                string.Equals(cells[0], "---", StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }

            var variableName = cells[0];
            if (string.IsNullOrWhiteSpace(variableName))
            {
                continue;
            }

            var method = cells[1];
            var payload = cells[2];
            var result = cells[3];
            if (!string.Equals(result, "RESULT_SUCCESS", StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }

            scenes[currentScene][variableName] = new KarismaSceneVariableDefinition(
                variableName,
                ResolveKind(variableName, method, payload),
                method,
                payload);
        }

        return scenes.ToDictionary(
            pair => pair.Key,
            pair => (IReadOnlyDictionary<string, KarismaSceneVariableDefinition>)pair.Value,
            StringComparer.OrdinalIgnoreCase);
    }

    private static void MergeScenes(
        IDictionary<string, Dictionary<string, KarismaSceneVariableDefinition>> target,
        IReadOnlyDictionary<string, IReadOnlyDictionary<string, KarismaSceneVariableDefinition>> source)
    {
        foreach (var (scenePath, variables) in source)
        {
            if (!target.TryGetValue(scenePath, out var mergedVariables))
            {
                mergedVariables = new Dictionary<string, KarismaSceneVariableDefinition>(StringComparer.OrdinalIgnoreCase);
                target[scenePath] = mergedVariables;
            }

            foreach (var (variableName, definition) in variables)
            {
                mergedVariables[variableName] = definition;
            }
        }
    }

    private static bool TryParseSceneHeader(string line, out string sceneRelativePath)
    {
        sceneRelativePath = string.Empty;
        if (!line.StartsWith("### `", StringComparison.Ordinal) || !line.EndsWith('`'))
        {
            return false;
        }

        sceneRelativePath = line.Substring(5, line.Length - 6);
        return !string.IsNullOrWhiteSpace(sceneRelativePath);
    }

    private static bool TryParseReportRoot(string line, out string reportRoot)
    {
        reportRoot = string.Empty;
        const string prefix = "- Root: `";
        if (!line.StartsWith(prefix, StringComparison.Ordinal) || !line.EndsWith('`'))
        {
            return false;
        }

        reportRoot = line.Substring(prefix.Length, line.Length - prefix.Length - 1);
        return !string.IsNullOrWhiteSpace(reportRoot);
    }

    private static List<string> SplitMarkdownRow(string line)
    {
        var cells = line.Split('|');
        if (cells.Length <= 2)
        {
            return [];
        }

        return cells
            .Skip(1)
            .Take(cells.Length - 2)
            .Select(cell => cell.Trim())
            .ToList();
    }

    private static KarismaSceneVariableKind ResolveKind(string variableName, string method, string payload)
    {
        if (string.Equals(method, "SetCounterNumberKey", StringComparison.OrdinalIgnoreCase))
        {
            return KarismaSceneVariableKind.Counter;
        }

        if (variableName.StartsWith("\uC720\uD655\uB2F9", StringComparison.OrdinalIgnoreCase))
        {
            return KarismaSceneVariableKind.VideoResource;
        }

        if (payload.EndsWith(".vrv", StringComparison.OrdinalIgnoreCase))
        {
            return KarismaSceneVariableKind.VideoResource;
        }

        if (payload.EndsWith(".png", StringComparison.OrdinalIgnoreCase) ||
            payload.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) ||
            payload.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) ||
            payload.EndsWith(".webp", StringComparison.OrdinalIgnoreCase))
        {
            return KarismaSceneVariableKind.Image;
        }

        if (IsLikelyCounterVariableName(variableName))
        {
            return KarismaSceneVariableKind.Counter;
        }

        return KarismaSceneVariableKind.Text;
    }

    private static bool IsLikelyCounterVariableName(string variableName)
    {
        return variableName.StartsWith("득표율", StringComparison.Ordinal) ||
               variableName.StartsWith("투표율", StringComparison.Ordinal) ||
               variableName.StartsWith("전국투표율", StringComparison.Ordinal) ||
               variableName.StartsWith("의석수", StringComparison.Ordinal);
    }

    private static IEnumerable<string> FindDiscoveryReportPaths()
    {
        var seenPaths = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        var reportPaths = new List<string>();

        foreach (var startPath in EnumerateSearchRoots())
        {
            var current = startPath;
            for (var depth = 0; depth < 8 && !string.IsNullOrWhiteSpace(current); depth++)
            {
                foreach (var reportName in PreferredReportNames)
                {
                    var candidate = Path.Combine(current, reportName);
                    if (File.Exists(candidate) && seenPaths.Add(candidate))
                    {
                        reportPaths.Add(candidate);
                    }
                }

                foreach (var wildcardCandidate in TryFindDiscoveryReports(current))
                {
                    if (seenPaths.Add(wildcardCandidate))
                    {
                        reportPaths.Add(wildcardCandidate);
                    }
                }

                current = Path.GetDirectoryName(current);
            }
        }

        return reportPaths;
    }

    private static IEnumerable<string> EnumerateSearchRoots()
    {
        var roots = new List<string> { AppContext.BaseDirectory };
        try
        {
            roots.Add(Directory.GetCurrentDirectory());
        }
        catch
        {
        }

        return roots;
    }

    private static IEnumerable<string> TryFindDiscoveryReports(string directoryPath)
    {
        try
        {
            return Directory.EnumerateFiles(directoryPath, "TSCN_VARIABLE_DISCOVERY*.md", SearchOption.TopDirectoryOnly)
                .Where(path => !Path.GetFileName(path).Contains("SAMPLE", StringComparison.OrdinalIgnoreCase))
                .OrderByDescending(path => File.GetLastWriteTimeUtc(path))
                .ThenBy(path => path, StringComparer.OrdinalIgnoreCase)
                .ToArray();
        }
        catch
        {
            return [];
        }
    }

    private static string NormalizeSceneKey(string? reportRootRelativePath, string sceneRelativePath)
    {
        var normalizedScenePath = NormalizeRelativePath(sceneRelativePath);
        if (string.IsNullOrWhiteSpace(reportRootRelativePath) ||
            string.IsNullOrWhiteSpace(normalizedScenePath) ||
            normalizedScenePath.Contains('\\'))
        {
            return normalizedScenePath;
        }

        return NormalizeRelativePath(Path.Combine(reportRootRelativePath, normalizedScenePath));
    }

    private static string? NormalizeReportRootRelativePath(string reportRootPath)
    {
        if (string.IsNullOrWhiteSpace(reportRootPath))
        {
            return null;
        }

        var normalized = reportRootPath.Replace('/', '\\').Trim().TrimEnd('\\');
        if (normalized.EndsWith("\\T3_Cut", StringComparison.OrdinalIgnoreCase))
        {
            return string.Empty;
        }

        const string marker = "\\T3_Cut\\";
        var markerIndex = normalized.IndexOf(marker, StringComparison.OrdinalIgnoreCase);
        if (markerIndex >= 0)
        {
            return NormalizeRelativePath(normalized[(markerIndex + marker.Length)..]);
        }

        var leafFolder = Path.GetFileName(normalized);
        return string.IsNullOrWhiteSpace(leafFolder)
            ? null
            : NormalizeRelativePath(leafFolder);
    }

    private static string NormalizeRelativePath(string relativePath)
    {
        return relativePath
            .Replace('/', '\\')
            .Trim();
    }
}

public sealed record KarismaSceneVariableDefinition(
    string Name,
    KarismaSceneVariableKind Kind,
    string Method,
    string Payload);

public enum KarismaSceneVariableKind
{
    Text,
    Image,
    VideoResource,
    Counter
}
