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

namespace AxCopilot.Services.Agent.Sdk;

/// <summary>
/// Phase 31-B: SDK 제어 프로토콜 서버.
/// stdin/stdout을 통한 양방향 JSON 스트리밍으로 외부 호스트(IDE/스크립트)에서
/// AX Agent를 프로그래밍 방식으로 제어합니다.
///
/// CC의 AgentSdkServer와 동등한 기능:
///   - Initialize → 세션 설정 + 커스텀 에이전트 등록
///   - UserMessage → 에이전트 루프 실행 + 스트리밍 응답
///   - SetModel / SetPermissionMode → 런타임 변경
///   - Interrupt / Terminate → 세션 제어
///   - CanUseTool → 외부 권한 핸들링
/// </summary>
public class AgentSdkServer : IDisposable
{
    private readonly AgentLoopService _agent;
    private readonly LlmService _llm;
    private readonly ToolRegistry _tools;
    private readonly SettingsService _settings;
    private readonly AgentTypeRegistry _agentRegistry = new();
    private readonly JsonSerializerOptions _jsonOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        WriteIndented = false,
    };

    private CancellationTokenSource? _currentTurnCts;
    private TextReader _input;
    private TextWriter _output;
    private bool _initialized;
    private string? _sessionId;
    private readonly List<string> _hookCallbacks = new();

    /// <summary>SDK 서버 생성.</summary>
    public AgentSdkServer(
        AgentLoopService agent,
        LlmService llm,
        ToolRegistry tools,
        SettingsService settings,
        TextReader? input = null,
        TextWriter? output = null)
    {
        _agent = agent;
        _llm = llm;
        _tools = tools;
        _settings = settings;
        _input = input ?? Console.In;
        _output = output ?? Console.Out;
    }

    /// <summary>SDK 서버 메인 루프. 종료까지 블로킹.</summary>
    public async Task RunAsync(CancellationToken ct = default)
    {
        // 에이전트 이벤트 → SDK 메시지 변환 구독
        _agent.Events.CollectionChanged += OnAgentEvent;

        try
        {
            while (!ct.IsCancellationRequested)
            {
                var line = await _input.ReadLineAsync();
                if (line == null) break; // EOF

                SdkControlRequest? request;
                try
                {
                    request = JsonSerializer.Deserialize<SdkControlRequest>(line, _jsonOptions);
                    if (request == null) continue;
                }
                catch (JsonException ex)
                {
                    await SendErrorAsync("PARSE_ERROR", $"JSON 파싱 실패: {ex.Message}");
                    continue;
                }

                try
                {
                    await HandleRequestAsync(request, ct);
                }
                catch (OperationCanceledException) when (ct.IsCancellationRequested)
                {
                    break;
                }
                catch (Exception ex)
                {
                    await SendErrorAsync("HANDLER_ERROR", ex.Message, request.Id);
                }
            }
        }
        finally
        {
            _agent.Events.CollectionChanged -= OnAgentEvent;
            _currentTurnCts?.Cancel();
        }
    }

    private async Task HandleRequestAsync(SdkControlRequest request, CancellationToken ct)
    {
        switch (request.Type)
        {
            case SdkControlRequestType.Initialize:
                await HandleInitializeAsync(request, ct);
                break;

            case SdkControlRequestType.UserMessage:
                await HandleUserMessageAsync(request, ct);
                break;

            case SdkControlRequestType.SetModel:
                HandleSetModel(request);
                break;

            case SdkControlRequestType.SetPermissionMode:
                HandleSetPermissionMode(request);
                break;

            case SdkControlRequestType.Interrupt:
                HandleInterrupt(request);
                break;

            case SdkControlRequestType.GetContextUsage:
                await HandleGetContextUsageAsync(request);
                break;

            case SdkControlRequestType.Terminate:
                _currentTurnCts?.Cancel();
                await SendMessageAsync(new SdkMessage
                {
                    Type = SdkMessageType.Notification,
                    RequestId = request.Id,
                    Data = new { message = "세션 종료됨" },
                });
                break;

            case SdkControlRequestType.CanUseToolResponse:
                // 권한 응답 처리는 별도 콜백 채널로 구현
                break;

            default:
                await SendErrorAsync("UNKNOWN_REQUEST", $"알 수 없는 요청 타입: {request.Type}", request.Id);
                break;
        }
    }

    #region ── 요청 핸들러 ──

    private async Task HandleInitializeAsync(SdkControlRequest request, CancellationToken ct)
    {
        if (_initialized)
        {
            await SendErrorAsync("ALREADY_INITIALIZED", "세션이 이미 초기화되었습니다.", request.Id);
            return;
        }

        SdkInitializePayload? payload = null;
        if (request.Payload.HasValue)
        {
            payload = JsonSerializer.Deserialize<SdkInitializePayload>(
                request.Payload.Value.GetRawText(), _jsonOptions);
        }

        _sessionId = Guid.NewGuid().ToString("N")[..12];

        // 설정 적용
        var llm = _settings.Settings.Llm;
        if (!string.IsNullOrEmpty(payload?.WorkFolder))
            llm.WorkFolder = payload.WorkFolder;
        if (!string.IsNullOrEmpty(payload?.Model))
            llm.Model = payload.Model;
        if (payload?.MaxTurns > 0)
            llm.MaxAgentIterations = payload.MaxTurns.Value;
        if (payload?.DisabledTools != null)
            llm.DisabledTools = payload.DisabledTools;

        // 커스텀 에이전트 등록
        if (payload?.CustomAgents != null)
        {
            foreach (var agentDef in payload.CustomAgents)
                _agentRegistry.Register(agentDef);
        }

        // 훅 콜백 등록
        if (payload?.HookCallbacks != null)
            _hookCallbacks.AddRange(payload.HookCallbacks);

        _initialized = true;

        await SendMessageAsync(new SdkMessage
        {
            Type = SdkMessageType.InitializeResult,
            RequestId = request.Id,
            Data = new
            {
                sessionId = _sessionId,
                model = llm.Model ?? "unknown",
                availableTools = _tools.All.Select(t => t.Name).ToList(),
                customAgents = _agentRegistry.ListAll().Select(a => a.Name).ToList(),
            },
        });
    }

    private async Task HandleUserMessageAsync(SdkControlRequest request, CancellationToken ct)
    {
        if (!_initialized)
        {
            await SendErrorAsync("NOT_INITIALIZED", "먼저 Initialize 요청을 보내세요.", request.Id);
            return;
        }

        SdkUserMessagePayload? payload = null;
        if (request.Payload.HasValue)
        {
            payload = JsonSerializer.Deserialize<SdkUserMessagePayload>(
                request.Payload.Value.GetRawText(), _jsonOptions);
        }

        var message = payload?.Message ?? "";
        if (string.IsNullOrWhiteSpace(message))
        {
            await SendErrorAsync("EMPTY_MESSAGE", "메시지가 비어있습니다.", request.Id);
            return;
        }

        // 에이전트 루프 실행
        _currentTurnCts = CancellationTokenSource.CreateLinkedTokenSource(ct);
        var messages = new List<ChatMessage>
        {
            new() { Role = "user", Content = message }
        };

        var sw = System.Diagnostics.Stopwatch.StartNew();
        try
        {
            var response = await _agent.RunAsync(messages, _currentTurnCts.Token);
            sw.Stop();

            await SendMessageAsync(new SdkMessage
            {
                Type = SdkMessageType.SessionResult,
                RequestId = request.Id,
                Data = new SdkSessionResultData
                {
                    Response = response,
                    DurationMs = sw.ElapsedMilliseconds,
                },
            });
        }
        catch (OperationCanceledException)
        {
            await SendMessageAsync(new SdkMessage
            {
                Type = SdkMessageType.Notification,
                RequestId = request.Id,
                Data = new { message = "턴이 중단되었습니다." },
            });
        }
        catch (Exception ex)
        {
            await SendErrorAsync("AGENT_ERROR", ex.Message, request.Id);
        }
        finally
        {
            _currentTurnCts?.Dispose();
            _currentTurnCts = null;
        }
    }

    private void HandleSetModel(SdkControlRequest request)
    {
        if (request.Payload.HasValue && request.Payload.Value.TryGetProperty("model", out var m))
        {
            _settings.Settings.Llm.Model = m.GetString() ?? _settings.Settings.Llm.Model;
        }
    }

    private void HandleSetPermissionMode(SdkControlRequest request)
    {
        if (request.Payload.HasValue && request.Payload.Value.TryGetProperty("mode", out var m))
        {
            _settings.Settings.Llm.FilePermission = m.GetString() ?? "Ask";
        }
    }

    private void HandleInterrupt(SdkControlRequest request)
    {
        _currentTurnCts?.Cancel();
    }

    private async Task HandleGetContextUsageAsync(SdkControlRequest request)
    {
        var llm = _settings.Settings.Llm;
        var usage = _llm.LastTokenUsage;

        await SendMessageAsync(new SdkMessage
        {
            Type = SdkMessageType.Notification,
            RequestId = request.Id,
            Data = new
            {
                maxContextTokens = llm.MaxContextTokens,
                promptTokens = usage?.PromptTokens ?? 0,
                completionTokens = usage?.CompletionTokens ?? 0,
                usagePercent = llm.MaxContextTokens > 0 && usage != null
                    ? (int)(100.0 * usage.PromptTokens / llm.MaxContextTokens)
                    : 0,
            },
        });
    }

    #endregion

    #region ── 이벤트 → SDK 메시지 변환 ──

    private void OnAgentEvent(object? sender,
        System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems == null) return;

        foreach (AgentEvent evt in e.NewItems)
        {
            var msg = ConvertAgentEvent(evt);
            if (msg != null)
            {
                // fire-and-forget (이벤트 핸들러에서 async 불가)
                _ = SendMessageAsync(msg);
            }
        }
    }

    private SdkMessage? ConvertAgentEvent(AgentEvent evt)
    {
        return evt.Type switch
        {
            AgentEventType.ToolCall => new SdkMessage
            {
                Type = SdkMessageType.ToolUseStart,
                Data = new SdkToolUseStartData
                {
                    ToolName = evt.ToolName,
                    Input = evt.ToolInput,
                },
            },
            AgentEventType.ToolResult => new SdkMessage
            {
                Type = SdkMessageType.ToolResult,
                Data = new SdkToolResultData
                {
                    ToolName = evt.ToolName,
                    Success = evt.Success,
                    Output = evt.Summary,
                    FilePath = evt.FilePath,
                    ElapsedMs = evt.ElapsedMs,
                },
            },
            AgentEventType.Thinking => new SdkMessage
            {
                Type = SdkMessageType.Notification,
                Data = new { message = evt.Summary },
            },
            AgentEventType.Error => new SdkMessage
            {
                Type = SdkMessageType.Error,
                Data = new SdkErrorData
                {
                    Code = "AGENT_ERROR",
                    Message = evt.Summary,
                },
            },
            _ => null, // 기타 이벤트는 SDK로 전달하지 않음
        };
    }

    #endregion

    #region ── 메시지 전송 ──

    private readonly SemaphoreSlim _writeLock = new(1, 1);

    private async Task SendMessageAsync(SdkMessage message)
    {
        var json = JsonSerializer.Serialize(message, _jsonOptions);
        await _writeLock.WaitAsync();
        try
        {
            await _output.WriteLineAsync(json);
            await _output.FlushAsync();
        }
        finally
        {
            _writeLock.Release();
        }
    }

    private Task SendErrorAsync(string code, string message, string? requestId = null)
        => SendMessageAsync(new SdkMessage
        {
            Type = SdkMessageType.Error,
            RequestId = requestId,
            Data = new SdkErrorData { Code = code, Message = message },
        });

    #endregion

    /// <summary>등록된 커스텀 에이전트 레지스트리.</summary>
    public AgentTypeRegistry AgentRegistry => _agentRegistry;

    public void Dispose()
    {
        _currentTurnCts?.Cancel();
        _currentTurnCts?.Dispose();
        _writeLock.Dispose();
    }
}
