using System.Collections.Concurrent;
using System.Text.RegularExpressions;
using Nalu.Web.Models;

namespace Nalu.Web.Services;

public class ValidatorLoader : IDisposable
{
    private readonly string _validatorsPath;
    private readonly ILogger<ValidatorLoader> _logger;
    private readonly CacheService _responseCache;
    private readonly ConcurrentDictionary<string, ValidatorDefinition> _cache = new();
    private FileSystemWatcher? _watcher;

    public ValidatorLoader(ILogger<ValidatorLoader> logger, IWebHostEnvironment env, CacheService responseCache)
    {
        _logger = logger;
        _responseCache = responseCache;
        _validatorsPath = Path.Combine(env.ContentRootPath, "Validators");

        if (Directory.Exists(_validatorsPath))
        {
            _watcher = new FileSystemWatcher(_validatorsPath, "*.md")
            {
                NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName,
                EnableRaisingEvents = true
            };

            _watcher.Changed += Invalidate;
            _watcher.Deleted += Invalidate;
            _watcher.Renamed += Invalidate;
        }
    }

    private void Invalidate(object _, FileSystemEventArgs e)
    {
        var id = Path.GetFileNameWithoutExtension(e.Name ?? "");
        if (_cache.TryRemove(id, out ValidatorDefinition? _))
        {
            _responseCache.InvalidateValidator(id);
            _logger.LogInformation("Invalidated validator and response cache for '{Id}'", id);
        }
    }

    public ValidatorDefinition Load(string validatorId)
    {
        return _cache.GetOrAdd(validatorId, id =>
        {
            var path = Path.Combine(_validatorsPath, $"{id}.md");
            if (!File.Exists(path))
                throw new FileNotFoundException($"Validator '{id}' not found at '{path}'");

            var content = File.ReadAllText(path);
            _logger.LogDebug("Parsed validator '{Id}' from disk", id);
            return ParseFromContent(id, content);
        });
    }

    public IEnumerable<ValidatorDefinition> LoadAll()
    {
        if (!Directory.Exists(_validatorsPath))
            return [];

        return Directory
            .GetFiles(_validatorsPath, "*.md")
            .Select(f => Load(Path.GetFileNameWithoutExtension(f)));
    }

    /// <summary>Parses a validator .md string. Public for unit testing.</summary>
    public static ValidatorDefinition ParseFromContent(string id, string content)
    {
        var def = new ValidatorDefinition { Id = id };

        // Description: text between # title line and first ## section
        var descMatch = Regex.Match(content,
            @"^#[^\n]+\n(.*?)(?=^##|\z)",
            RegexOptions.Multiline | RegexOptions.Singleline);
        if (descMatch.Success)
            def.Description = descMatch.Groups[1].Value.Trim();

        // Split into ## sections
        var sections = Regex.Split(content, @"^## ", RegexOptions.Multiline);

        foreach (var section in sections)
        {
            if (string.IsNullOrWhiteSpace(section)) continue;

            var nlIdx = section.IndexOf('\n');
            if (nlIdx < 0) continue;

            var sectionName = section[..nlIdx].Trim().ToLowerInvariant();
            var body = section[(nlIdx + 1)..];

            switch (sectionName)
            {
                case "config":
                    ParseConfig(def, body);
                    break;
                case "deterministic_rules":
                    ParseDeterministicRules(def, body);
                    break;
                case "prompt":
                    def.Prompt = body.Trim();
                    break;
                case "few_shot_examples":
                    ParseFewShots(def, body);
                    break;
                case "post_processors":
                    def.PostProcessors = ParseBulletList(body);
                    break;
                case "enrichment":
                    if (!body.Contains("nenhum", StringComparison.OrdinalIgnoreCase))
                        def.Enrichers = ParseBulletList(body);
                    break;
                case "suggestions":
                    ParseSuggestions(def, body);
                    break;
            }
        }

        return def;
    }

    private static void ParseConfig(ValidatorDefinition def, string body)
    {
        foreach (var line in body.Split('\n'))
        {
            var m = Regex.Match(line.Trim(), @"^-\s+(\w+):\s*(.+)$");
            if (!m.Success) continue;

            var key = m.Groups[1].Value;
            var value = m.Groups[2].Value.Trim();

            switch (key)
            {
                case "type": def.Type = value; break;
                case "version": def.Version = value; break;
                case "languages":
                    def.Languages = value.Split(',').Select(l => l.Trim()).ToList();
                    break;
                case "endpoint": def.Endpoint = value; break;
                case "mcp_tool": def.McpTool = value; break;
                case "mcp_description": def.McpDescription = value; break;
                case "name_lookup":
                    def.NameLookup = value.Equals("true", StringComparison.OrdinalIgnoreCase);
                    break;
            }
        }
    }

    private static void ParseDeterministicRules(ValidatorDefinition def, string body)
    {
        var subsections = Regex.Split(body, @"^### ", RegexOptions.Multiline);

        foreach (var sub in subsections)
        {
            if (string.IsNullOrWhiteSpace(sub)) continue;

            var nlIdx = sub.IndexOf('\n');
            if (nlIdx < 0) continue;

            var subName = sub[..nlIdx].Trim().ToLowerInvariant();
            var subBody = sub[(nlIdx + 1)..];

            switch (subName)
            {
                case "stop_words":
                    if (subBody.Contains("#### "))
                    {
                        // Localized stop words per language (#### pt-BR / #### en-US / ...)
                        var langSecs = Regex.Split(subBody, @"^#### ", RegexOptions.Multiline);
                        foreach (var langSec in langSecs)
                        {
                            if (string.IsNullOrWhiteSpace(langSec)) continue;
                            var lnl = langSec.IndexOf('\n');
                            if (lnl < 0) continue;
                            var lang = langSec[..lnl].Trim();
                            var words = langSec[(lnl + 1)..].Trim()
                                .Split(',')
                                .Select(s => s.Trim().ToLowerInvariant())
                                .Where(s => !string.IsNullOrEmpty(s))
                                .ToHashSet();
                            def.LocalizedStopWords[lang] = words;
                            foreach (var w in words) def.StopWords.Add(w); // flat fallback
                        }
                    }
                    else
                    {
                        def.StopWords = subBody
                            .Trim()
                            .Split(',')
                            .Select(s => s.Trim().ToLowerInvariant())
                            .Where(s => !string.IsNullOrEmpty(s))
                            .ToHashSet();
                    }
                    break;

                case "reject_patterns":
                    def.RejectPatterns = ParseBulletList(subBody);
                    break;

                case "accept_patterns":
                    def.AcceptPatterns = ParseBulletList(subBody);
                    break;

                case "constraints":
                    def.Constraints = ParseBulletList(subBody)
                        .Select(l => l.Split(':', 2))
                        .Where(p => p.Length == 2)
                        .ToDictionary(p => p[0].Trim(), p => p[1].Trim());
                    break;
            }
        }
    }

    private static void ParseFewShots(ValidatorDefinition def, string body)
    {
        var examples = Regex.Split(body, @"^### example \d+", RegexOptions.Multiline | RegexOptions.IgnoreCase);

        foreach (var ex in examples)
        {
            if (string.IsNullOrWhiteSpace(ex)) continue;

            var agentInput = ExtractBulletValue(ex, "agent_input");
            var userInput = ExtractBulletValue(ex, "user_input");
            var output = ExtractBulletValue(ex, "output");

            if (agentInput is not null && userInput is not null && output is not null)
            {
                def.FewShotExamples.Add(new FewShotExample
                {
                    AgentInput = agentInput,
                    UserInput = userInput,
                    Output = output
                });
            }
        }
    }

    private static string? ExtractBulletValue(string text, string key)
    {
        var m = Regex.Match(text, $@"^-\s+{Regex.Escape(key)}:\s*(.+)$", RegexOptions.Multiline);
        return m.Success ? m.Groups[1].Value.Trim() : null;
    }

    private static void ParseSuggestions(ValidatorDefinition def, string body)
    {
        var subsections = Regex.Split(body, @"^### ", RegexOptions.Multiline);

        foreach (var sub in subsections)
        {
            if (string.IsNullOrWhiteSpace(sub)) continue;

            var nlIdx = sub.IndexOf('\n');
            if (nlIdx < 0) continue;

            var key = sub[..nlIdx].Trim().ToLowerInvariant();
            var rawValue = sub[(nlIdx + 1)..];

            if (string.IsNullOrEmpty(key)) continue;

            if (rawValue.Contains("#### "))
            {
                // Localized suggestion per language
                var langSecs = Regex.Split(rawValue, @"^#### ", RegexOptions.Multiline);
                foreach (var langSec in langSecs)
                {
                    if (string.IsNullOrWhiteSpace(langSec)) continue;
                    var lnl = langSec.IndexOf('\n');
                    if (lnl < 0) continue;
                    var lang = langSec[..lnl].Trim();
                    var text = langSec[(lnl + 1)..].Trim();
                    if (string.IsNullOrWhiteSpace(text) || text.StartsWith('(')) continue;

                    if (!def.LocalizedSuggestions.ContainsKey(lang))
                        def.LocalizedSuggestions[lang] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
                    def.LocalizedSuggestions[lang][key] = text;
                }
            }
            else
            {
                var value = rawValue.Trim();
                if (value.StartsWith('(')) continue; // placeholder like "(sem sugestão)"
                def.Suggestions[key] = value;
            }
        }
    }

    private static List<string> ParseBulletList(string body) =>
        body.Split('\n')
            .Select(l => l.Trim())
            .Where(l => l.StartsWith("- "))
            .Select(l => l[2..].Trim())
            .Where(l => !string.IsNullOrEmpty(l))
            .ToList();

    public void Dispose()
    {
        _watcher?.Dispose();
        _watcher = null;
    }
}
