using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.Json;
using AxCopilot.Models;

namespace AxCopilot.Services;

/// <summary>
/// MCP (Model Context Protocol) stdio 클라이언트.
/// 외부 MCP 서버 프로세스를 실행하고 JSON-RPC 2.0으로 통신합니다.
/// </summary>
public class McpClientService : IDisposable
{
    private readonly McpServerEntry _config;
    private Process? _process;
    private StreamWriter? _writer;
    private StreamReader? _reader;
    private int _requestId;
    internal bool _initialized;
    private readonly List<McpToolDefinition> _tools = new();

    public string ServerName => _config.Name;
    public bool IsConnected => _process is { HasExited: false };
    public IReadOnlyList<McpToolDefinition> Tools => _tools;

    private static readonly JsonSerializerOptions _jsonOpts = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
    };

    public McpClientService(McpServerEntry config)
    {
        _config = config;
    }

    /// <summary>MCP 서버 프로세스를 시작하고 초기화합니다.</summary>
    public async Task<bool> ConnectAsync(CancellationToken ct = default)
    {
        try
        {
            if (string.IsNullOrEmpty(_config.Command))
            {
                LogService.Warn($"MCP '{_config.Name}': command가 비어있습니다.");
                return false;
            }

            var psi = new ProcessStartInfo
            {
                FileName = _config.Command,
                UseShellExecute = false,
                RedirectStandardInput = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                CreateNoWindow = true,
                StandardOutputEncoding = Encoding.UTF8,
                StandardErrorEncoding = Encoding.UTF8,
            };

            foreach (var arg in _config.Args)
                psi.ArgumentList.Add(arg);

            foreach (var (key, value) in _config.Env)
                psi.Environment[key] = value;

            _process = Process.Start(psi);
            if (_process == null)
            {
                LogService.Warn($"MCP '{_config.Name}': 프로세스 시작 실패.");
                return false;
            }

            _writer = _process.StandardInput;
            _reader = _process.StandardOutput;

            // JSON-RPC: initialize
            var initResult = await SendRequestAsync("initialize", new
            {
                protocolVersion = "2024-11-05",
                capabilities = new { },
                clientInfo = new { name = "AX Copilot", version = "2.0.0" },
            }, ct);

            if (initResult == null) return false;

            // initialized notification
            await SendNotificationAsync("notifications/initialized", ct);

            _initialized = true;

            // 도구 목록 가져오기
            await RefreshToolsAsync(ct);

            LogService.Info($"MCP '{_config.Name}': 연결 성공 ({_tools.Count}개 도구).");
            return true;
        }
        catch (Exception ex)
        {
            LogService.Warn($"MCP '{_config.Name}' 연결 실패: {ex.Message}");
            return false;
        }
    }

    /// <summary>서버에서 사용 가능한 도구 목록을 갱신합니다.</summary>
    public async Task RefreshToolsAsync(CancellationToken ct = default)
    {
        _tools.Clear();
        var result = await SendRequestAsync("tools/list", new { }, ct);
        if (result == null) return;

        try
        {
            if (result.Value.TryGetProperty("tools", out var toolsArr))
            {
                foreach (var tool in toolsArr.EnumerateArray())
                {
                    var def = new McpToolDefinition
                    {
                        Name = tool.GetProperty("name").GetString() ?? "",
                        Description = tool.TryGetProperty("description", out var desc) ? desc.GetString() ?? "" : "",
                        ServerName = _config.Name,
                    };

                    if (tool.TryGetProperty("inputSchema", out var schema) &&
                        schema.TryGetProperty("properties", out var props))
                    {
                        foreach (var prop in props.EnumerateObject())
                        {
                            def.Parameters[prop.Name] = new McpParameterDef
                            {
                                Type = prop.Value.TryGetProperty("type", out var t) ? t.GetString() ?? "string" : "string",
                                Description = prop.Value.TryGetProperty("description", out var d) ? d.GetString() ?? "" : "",
                            };
                        }

                        if (schema.TryGetProperty("required", out var reqArr))
                        {
                            foreach (var req in reqArr.EnumerateArray())
                            {
                                var reqName = req.GetString();
                                if (reqName != null && def.Parameters.TryGetValue(reqName, out var p))
                                    p.Required = true;
                            }
                        }
                    }

                    _tools.Add(def);
                }
            }
        }
        catch (Exception ex)
        {
            LogService.Warn($"MCP '{_config.Name}' 도구 파싱 실패: {ex.Message}");
        }
    }

    /// <summary>MCP 도구를 호출합니다.</summary>
    public async Task<string> CallToolAsync(string toolName, Dictionary<string, object> arguments, CancellationToken ct = default)
    {
        var result = await SendRequestAsync("tools/call", new
        {
            name = toolName,
            arguments,
        }, ct);

        if (result == null) return "[MCP 호출 실패]";

        try
        {
            if (result.Value.TryGetProperty("content", out var contentArr))
            {
                var sb = new StringBuilder();
                foreach (var item in contentArr.EnumerateArray())
                {
                    if (item.TryGetProperty("text", out var text))
                        sb.AppendLine(text.GetString());
                }
                return sb.ToString().TrimEnd();
            }

            if (result.Value.TryGetProperty("isError", out var isErr) && isErr.GetBoolean())
            {
                return $"[MCP 오류] {result}";
            }
        }
        catch (Exception) { }

        return result?.ToString() ?? "[MCP 빈 응답]";
    }

    private async Task<JsonElement?> SendRequestAsync(string method, object parameters, CancellationToken ct)
    {
        if (_writer == null || _reader == null) return null;

        var id = Interlocked.Increment(ref _requestId);
        var request = new
        {
            jsonrpc = "2.0",
            id,
            method,
            @params = parameters,
        };

        var json = JsonSerializer.Serialize(request, _jsonOpts);
        await _writer.WriteLineAsync(json);
        await _writer.FlushAsync();

        // 응답 읽기 (동기식 대기, 타임아웃 10초)
        using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
        cts.CancelAfter(TimeSpan.FromSeconds(10));

        while (!cts.Token.IsCancellationRequested)
        {
            var line = await _reader.ReadLineAsync(cts.Token);
            if (string.IsNullOrWhiteSpace(line)) continue;

            try
            {
                var doc = JsonDocument.Parse(line);
                var root = doc.RootElement;

                // notification은 건너뛰기
                if (!root.TryGetProperty("id", out _)) continue;

                if (root.TryGetProperty("result", out var result))
                    return result;

                if (root.TryGetProperty("error", out var error))
                {
                    var msg = error.TryGetProperty("message", out var m) ? m.GetString() : "Unknown error";
                    LogService.Warn($"MCP '{_config.Name}' RPC 오류: {msg}");
                    return null;
                }
            }
            catch (Exception) { continue; }
        }

        return null;
    }

    private async Task SendNotificationAsync(string method, CancellationToken ct)
    {
        if (_writer == null) return;

        var notification = new { jsonrpc = "2.0", method };
        var json = JsonSerializer.Serialize(notification, _jsonOpts);
        await _writer.WriteLineAsync(json);
        await _writer.FlushAsync();
    }

    public void Dispose()
    {
        try
        {
            _writer?.Dispose();
            _reader?.Dispose();
            if (_process is { HasExited: false })
            {
                _process.Kill(entireProcessTree: true);
                _process.Dispose();
            }
        }
        catch (Exception) { }
    }
}
