using System.Text.Json;
using System.Text.Json.Nodes;
using Nalu.Web.Models;
using Nalu.Web.Services;
using Nalu.Web.Services.LlmRouter;

namespace Nalu.Web.Mcp;

/// <summary>
/// Manual JSON-RPC 2.0 MCP server (Streamable HTTP transport).
/// Handles: initialize, initialized, tools/list, tools/call.
/// Tools are registered dynamically from Validators/*.md files.
/// </summary>
public class McpServer
{
    private readonly ValidatorLoader _loader;
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly CreditService _credits;
    private readonly ILogger<McpServer> _logger;

    // Shared JSON schema for standard extraction tools
    private static readonly object InputSchema = new
    {
        type = "object",
        properties = new
        {
            agent_input   = new { type = "string", description = "Última mensagem enviada pelo agente ao usuário." },
            user_input    = new { type = "string", description = "Resposta do usuário a ser analisada." },
            agent_context = new { type = "string", description = "Contexto do agente (opcional). Ex: negociador de crédito." },
            language      = new { type = "string", description = "Idioma da conversa.", @default = "pt-BR" }
        },
        required = new[] { "agent_input", "user_input" }
    };

    // Schema for nalu_extract_reply (different field names)
    private static readonly object ReplyInputSchema = new
    {
        type = "object",
        properties = new
        {
            agent_message = new { type = "string", description = "Mensagem enviada pelo agente ao usuário." },
            user_reply    = new { type = "string", description = "Resposta do usuário à mensagem do agente." },
            language      = new { type = "string", description = "Idioma da conversa.", @default = "pt-BR" }
        },
        required = new[] { "agent_message", "user_reply" }
    };

    public McpServer(
        ValidatorLoader loader,
        IServiceScopeFactory scopeFactory,
        CreditService credits,
        ILogger<McpServer> logger)
    {
        _loader = loader;
        _scopeFactory = scopeFactory;
        _credits = credits;
        _logger = logger;
    }

    public async Task<IResult> HandleAsync(HttpContext ctx, CancellationToken ct)
    {
        // Credit check deferred to per-tool — MCP calls are handled individually below

        // Parse JSON-RPC body
        JsonDocument doc;
        try
        {
            doc = await JsonDocument.ParseAsync(ctx.Request.Body, cancellationToken: ct);
        }
        catch
        {
            return JsonRpcError(null, -32700, "Parse error");
        }

        using (doc)
        {
            var root = doc.RootElement;

            var method = root.TryGetProperty("method", out var m) ? m.GetString() : null;
            var hasId  = root.TryGetProperty("id", out var idProp);
            var id     = hasId ? ReadId(idProp) : null;

            // Notifications have no id — return 202 Accepted after processing
            if (method == "initialized" || (!hasId && method != null))
            {
                ctx.Response.StatusCode = 202;
                return Results.Empty;
            }

            object? result;
            try
            {
                result = method switch
                {
                    "initialize" => HandleInitialize(),
                    "tools/list" => HandleToolsList(),
                    "tools/call" => await HandleToolsCallAsync(root, ct),
                    _ => throw new McpException(-32601, $"Method not found: {method}")
                };
            }
            catch (McpException ex)
            {
                return JsonRpcError(id, ex.Code, ex.Message);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "MCP handler error for method '{Method}'", method);
                return JsonRpcError(id, -32603, "Internal error");
            }

            return Results.Json(new { jsonrpc = "2.0", result, id });
        }
    }

    // ── Method handlers ───────────────────────────────────────────────────────

    private static object HandleInitialize() => new
    {
        protocolVersion = "2024-11-05",
        capabilities = new
        {
            tools = new { listChanged = false }
        },
        serverInfo = new { name = "nalu-ai", version = "1.0.0" }
    };

    private object HandleToolsList()
    {
        var tools = _loader.LoadAll()
            .Where(v => !string.IsNullOrEmpty(v.McpTool))
            .Select(v => new
            {
                name        = v.McpTool,
                description = v.McpDescription,
                inputSchema = v.McpTool == "nalu_extract_reply" ? ReplyInputSchema : InputSchema
            });

        return new { tools };
    }

    private async Task<object> HandleToolsCallAsync(JsonElement root, CancellationToken ct)
    {
        if (!root.TryGetProperty("params", out var paramsEl))
            throw new McpException(-32602, "Missing params");

        var toolName = paramsEl.TryGetProperty("name", out var n) ? n.GetString() : null;
        if (string.IsNullOrEmpty(toolName))
            throw new McpException(-32602, "Missing params.name");

        var validator = _loader.LoadAll().FirstOrDefault(v => v.McpTool == toolName);
        if (validator is null)
            throw new McpException(-32602, $"Unknown tool: {toolName}");

        var args = paramsEl.TryGetProperty("arguments", out var a) ? a : default;
        var language = GetString(args, "language") ?? "pt-BR";

        using var scope = _scopeFactory.CreateScope();
        string json;

        if (toolName == "nalu_extract_reply")
        {
            // validate_reply has different input schema
            var replyReq = new ReplyRequest
            {
                AgentMessage = GetString(args, "agent_message") ?? throw new McpException(-32602, "Missing agent_message"),
                UserReply    = GetString(args, "user_reply")    ?? throw new McpException(-32602, "Missing user_reply"),
                Language     = language
            };

            var replyService = scope.ServiceProvider.GetRequiredService<ReplyService>();
            var replyResp = await replyService.AnalyzeAsync(replyReq, ct);
            json = JsonSerializer.Serialize(replyResp, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower });
        }
        else
        {
            var request = new ExtractionRequest
            {
                AgentInput   = GetString(args, "agent_input")  ?? throw new McpException(-32602, "Missing agent_input"),
                UserInput    = GetString(args, "user_input")   ?? throw new McpException(-32602, "Missing user_input"),
                AgentContext = GetString(args, "agent_context"),
                Language     = language
            };

            var pipeline = scope.ServiceProvider.GetRequiredService<ExtractionPipeline>();
            var response = await pipeline.ExecuteAsync(validator.Id, request, ct);
            json = JsonSerializer.Serialize(response, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower });
        }

        return new
        {
            content = new[] { new { type = "text", text = json } },
            isError = false
        };
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    private static string? GetString(JsonElement el, string key) =>
        el.ValueKind == JsonValueKind.Object && el.TryGetProperty(key, out var v)
            ? v.GetString()
            : null;

    private static object? ReadId(JsonElement idProp) => idProp.ValueKind switch
    {
        JsonValueKind.Number => (object)idProp.GetInt64(),
        JsonValueKind.String => idProp.GetString(),
        _ => null
    };

    private static IResult JsonRpcError(object? id, int code, string message) =>
        Results.Json(new
        {
            jsonrpc = "2.0",
            error   = new { code, message },
            id
        });

    private sealed class McpException(int code, string message) : Exception(message)
    {
        public int Code { get; } = code;
    }
}
