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

namespace Nalu.Web.Services;

public class ExtractionPipeline
{
    private readonly ValidatorLoader _loader;
    private readonly DeterministicLayer _deterministic;
    private readonly LlmExtractionService _llm;
    private readonly PostProcessorRegistry _postProcessors;
    private readonly EnrichmentService _enrichment;
    private readonly SuggestionBuilder _suggestions;
    private readonly SmartSuggestionService _smartSuggestion;
    private readonly CacheService _cache;
    private readonly SuspiciousRateLimiter _rateLimiter;
    private readonly IHttpContextAccessor _httpContext;
    private readonly NameLookupService _nameLookup;
    private readonly ILogger<ExtractionPipeline> _logger;

    public ExtractionPipeline(
        ValidatorLoader loader,
        DeterministicLayer deterministic,
        LlmExtractionService llm,
        PostProcessorRegistry postProcessors,
        EnrichmentService enrichment,
        SuggestionBuilder suggestions,
        SmartSuggestionService smartSuggestion,
        CacheService cache,
        SuspiciousRateLimiter rateLimiter,
        IHttpContextAccessor httpContext,
        NameLookupService nameLookup,
        ILogger<ExtractionPipeline> logger)
    {
        _loader = loader;
        _deterministic = deterministic;
        _llm = llm;
        _postProcessors = postProcessors;
        _enrichment = enrichment;
        _suggestions = suggestions;
        _smartSuggestion = smartSuggestion;
        _cache = cache;
        _rateLimiter = rateLimiter;
        _httpContext = httpContext;
        _nameLookup = nameLookup;
        _logger = logger;
    }

    public async Task<ExtractionResponse> ExecuteAsync(
        string validatorId,
        ExtractionRequest request,
        CancellationToken ct = default)
    {
        if (_cache.TryGet(validatorId, request, out var cached) && cached is not null)
            return cached;

        // Detect suspicious inputs — log and track (fail-open, never block)
        bool suspicious =
            InputGuard.IsSuspicious(request.UserInput,    "user_input",    _logger, validatorId) |
            InputGuard.IsSuspicious(request.AgentInput,   "agent_input",   _logger, validatorId) |
            InputGuard.IsSuspicious(request.AgentContext, "agent_context", _logger, validatorId);

        // Rate limit keys with repeated suspicious inputs
        if (suspicious)
        {
            var apiKey = _httpContext.HttpContext?.User.FindFirst("api_key")?.Value ?? "";
            if (_rateLimiter.RecordAndCheck(apiKey))
            {
                // Key is throttled — return safe fail immediately, skip LLM
                _logger.LogWarning("Request blocked by suspicious rate limiter for validator {Validator}", validatorId);
                return new ExtractionResponse
                {
                    Obtained = false, ExtractedValue = null,
                    Confidence = "low", Certain = false,
                    SuggestionToAgent = null
                };
            }
        }

        var validator = _loader.Load(validatorId);
        var det = _deterministic.Evaluate(validator, request.UserInput, request.Language);

        bool providedButInvalid = false;

        // Pre-fetch LLM result for Unresolved cases so we can capture the situation signal
        LlmExtractionResult? llmResult = null;
        if (det.Outcome == DeterministicOutcome.Unresolved)
            llmResult = await _llm.ExtractAsync(validator, request, ct);

        ExtractionResponse response = det.Outcome switch
        {
            DeterministicOutcome.Accepted when validator.NameLookup =>
                await BuildWithNameLookupAsync(validator, request, det.ExtractedValue, ct),

            DeterministicOutcome.Accepted =>
                await BuildFromValueAsync(validator, request, det.ExtractedValue,
                    highConfidence: true, ct),

            DeterministicOutcome.Rejected =>
                BuildFailed(validator, request, wasInvalidated: false),

            DeterministicOutcome.ConstraintFailed =>
                BuildFailed(validator, request, wasInvalidated: true),

            _ => await BuildFromValueAsync(validator, request,
                    llmResult!.ExtractedValue, highConfidence: llmResult!.Certain, ct)
        };

        // Stamp engine sigla — "det" for deterministic paths, model sigla for LLM paths
        var engineCode = det.Outcome == DeterministicOutcome.Unresolved
            ? llmResult?.Engine ?? "unk"
            : "det";
        response = response with { Engine = engineCode };

        // Suspicious input → downgrade certain to false (LLM may have been fooled)
        if (suspicious && response.Certain)
            response = response with { Certain = false, Confidence = "medium" };

        // Deterministic accepted but post-processor rejected (e.g. CPF digit check failed)
        if (det.Outcome == DeterministicOutcome.Accepted && !response.Obtained)
            providedButInvalid = true;
        // Constraint failed = user provided something but it didn't meet format requirements
        if (det.Outcome == DeterministicOutcome.ConstraintFailed)
            providedButInvalid = true;

        // Smart suggestion: replace static template with a contextual LLM re-ask
        // when obtained=false and the request carries agent context.
        if (!response.Obtained && HasContext(request))
        {
            var smart = await _smartSuggestion.GenerateAsync(validator, request, providedButInvalid, llmResult?.Situation, ct);
            if (smart is not null)
            {
                var validated = InputGuard.ValidateSuggestion(smart, request.UserInput, _logger, validatorId);
                if (validated is not null)
                    response = response with { SuggestionToAgent = validated };
            }
        }

        _cache.Set(validatorId, request, response);
        return response;
    }

    private async Task<ExtractionResponse> BuildFromValueAsync(
        ValidatorDefinition validator,
        ExtractionRequest request,
        string? rawValue,
        bool highConfidence,
        CancellationToken ct)
    {
        // value_pattern constraint: sanity-check LLM output before post-processors
        if (rawValue is not null
            && validator.Constraints.TryGetValue("value_pattern", out var pattern))
        {
            bool matches;
            try { matches = Regex.IsMatch(rawValue, pattern, RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(50)); }
            catch { matches = true; } // on timeout/error, pass through
            if (!matches)
            {
                _logger.LogWarning("value_pattern mismatch for {Validator}: '{Value}' !~ /{Pattern}/",
                    validator.Id, rawValue[..Math.Min(rawValue.Length, 100)], pattern);
                var sug = _suggestions.Build(validator, request, null,
                    obtained: false, certain: false, wasInvalidated: true);
                return new ExtractionResponse
                {
                    Obtained = false, ExtractedValue = null, Confidence = "low",
                    Certain = false, SuggestionToAgent = sug
                };
            }
        }

        var procOutput = _postProcessors.Apply(validator.PostProcessors, rawValue);

        if (!procOutput.IsValid)
        {
            var sug = _suggestions.Build(validator, request, null,
                obtained: false, certain: false, wasInvalidated: true);
            return new ExtractionResponse
            {
                Obtained = false, ExtractedValue = null, Confidence = "low",
                Certain = false, SuggestionToAgent = sug
            };
        }

        var enrichResult = await _enrichment.EnrichAsync(validator, procOutput.Value, ct);

        if (enrichResult.WasInvalidated)
        {
            var sug = _suggestions.Build(validator, request, null,
                obtained: false, certain: false, wasInvalidated: true);
            return new ExtractionResponse
            {
                Obtained = false, ExtractedValue = null, Confidence = "low",
                Certain = false, SuggestionToAgent = sug
            };
        }

        var value = enrichResult.Value;
        var obtained = value is not null;
        var certain = obtained && highConfidence && !procOutput.WasCorrected;

        var suggestion = _suggestions.Build(validator, request, value,
            obtained, certain,
            wasInvalidated: false,
            wasCorrected: procOutput.WasCorrected,
            originalValue: procOutput.OriginalValue,
            suggestionKeyOverride: procOutput.SuggestionKeyOverride);

        var confidence = obtained
            ? (certain ? (highConfidence ? "high" : "medium") : "medium")
            : "low";

        var valueFormat = value is null ? null
            : value.TrimStart().StartsWith('{') ? "object"
            : "scalar";

        return new ExtractionResponse
        {
            Obtained = obtained,
            ExtractedValue = value,
            Confidence = confidence,
            Certain = certain,
            SuggestionToAgent = suggestion,
            ValueFormat = valueFormat
        };
    }

    private ExtractionResponse BuildFailed(
        ValidatorDefinition validator,
        ExtractionRequest request,
        bool wasInvalidated)
    {
        var suggestion = _suggestions.Build(validator, request, null,
            obtained: false, certain: false, wasInvalidated: wasInvalidated);

        return new ExtractionResponse
        {
            Obtained = false, ExtractedValue = null, Confidence = "low",
            Certain = false, SuggestionToAgent = suggestion
        };
    }

    private async Task<ExtractionResponse> BuildWithNameLookupAsync(
        ValidatorDefinition validator,
        ExtractionRequest request,
        string? extractedValue,
        CancellationToken ct)
    {
        if (extractedValue is not null)
        {
            var allKnown = await _nameLookup.AllTokensKnownAsync(extractedValue, ct);
            if (allKnown)
                return await BuildFromValueAsync(validator, request, extractedValue, highConfidence: true, ct);
        }

        // Token(s) not in nomes_br — let LLM decide
        var llm = await _llm.ExtractAsync(validator, request, ct);
        return await BuildFromValueAsync(validator, request, llm.ExtractedValue, highConfidence: llm.Certain, ct);
    }

    private async Task<ExtractionResponse> BuildFromLlmAsync(
        ValidatorDefinition validator,
        ExtractionRequest request,
        CancellationToken ct)
    {
        var llm = await _llm.ExtractAsync(validator, request, ct);

        return await BuildFromValueAsync(
            validator, request, llm.ExtractedValue,
            highConfidence: llm.Certain, ct);
    }

    private static bool HasContext(ExtractionRequest request) =>
        !string.IsNullOrWhiteSpace(request.AgentInput) ||
        !string.IsNullOrWhiteSpace(request.AgentContext);
}
