using System.Text.Json;
using AxCopilot.Models;
using AxCopilot.Services.Agent;

namespace AxCopilot.Services;

/// <summary>
/// LlmService의 Function Calling (tool_use) 확장.
/// Claude tool_use, Gemini function_calling 프로토콜을 지원합니다.
/// 기존 SendAsync/StreamAsync는 변경하지 않고, 에이전트 전용 메서드를 추가합니다.
/// </summary>
public partial class LlmService
{
    /// <summary>LLM 응답에서 파싱된 컨텐츠 블록.</summary>
    public class ContentBlock
    {
        public string Type { get; init; } = "text"; // "text" | "tool_use"
        public string Text { get; init; } = "";      // text 타입일 때
        public string ToolName { get; init; } = "";   // tool_use 타입일 때
        public string ToolId { get; init; } = "";     // tool_use ID
        public JsonElement? ToolInput { get; init; }   // tool_use 파라미터
    }

    /// <summary>도구 정의를 포함하여 LLM에 요청하고, 텍스트 + tool_use 블록을 파싱하여 반환합니다.</summary>
    public async Task<List<ContentBlock>> SendWithToolsAsync(
        List<ChatMessage> messages,
        IReadOnlyCollection<IAgentTool> tools,
        CancellationToken ct = default)
    {
        var activeService = ResolveService();
        return activeService.ToLowerInvariant() switch
        {
            "claude" => await SendClaudeWithToolsAsync(messages, tools, ct),
            "gemini" => await SendGeminiWithToolsAsync(messages, tools, ct),
            "ollama" or "vllm" => await SendOpenAiWithToolsAsync(messages, tools, ct),
            _ => throw new NotSupportedException($"서비스 '{activeService}'는 아직 Function Calling을 지원하지 않습니다.")
        };
    }

    /// <summary>도구 실행 결과를 LLM에 피드백하기 위한 메시지를 생성합니다.</summary>
    public static ChatMessage CreateToolResultMessage(string toolId, string toolName, string result)
    {
        // Claude: role=user, content=[{type:"tool_result", tool_use_id, content}]
        // 내부적으로는 JSON으로 인코딩하여 Content에 저장
        var payload = JsonSerializer.Serialize(new
        {
            type = "tool_result",
            tool_use_id = toolId,
            tool_name = toolName,
            content = result
        });
        return new ChatMessage
        {
            Role = "user",
            Content = payload,
            Timestamp = DateTime.Now,
        };
    }

    // ─── 공통 헬퍼 ─────────────────────────────────────────────────────

    /// <summary>ToolProperty를 LLM API용 스키마 객체로 변환. array/enum/items 포함.</summary>
    private static object BuildPropertySchema(Agent.ToolProperty prop, bool upperCaseType)
    {
        var typeName = upperCaseType ? prop.Type.ToUpperInvariant() : prop.Type;

        if (prop.Type.Equals("array", StringComparison.OrdinalIgnoreCase) && prop.Items != null)
        {
            return new
            {
                type = typeName,
                description = prop.Description,
                items = BuildPropertySchema(prop.Items, upperCaseType)
            };
        }

        // enum 값이 있으면 포함
        if (prop.Enum is { Count: > 0 })
            return new { type = typeName, description = prop.Description, @enum = prop.Enum };

        return new { type = typeName, description = prop.Description };
    }

    /// <summary>에러 응답 본문에서 핵심 메시지를 추출합니다.</summary>
    private static string ExtractErrorDetail(string errBody)
    {
        if (string.IsNullOrWhiteSpace(errBody)) return "응답 없음";
        try
        {
            using var doc = JsonDocument.Parse(errBody);
            // Ollama: {"error":"..."}
            if (doc.RootElement.TryGetProperty("error", out var err))
            {
                if (err.ValueKind == JsonValueKind.String)
                    return err.GetString() ?? errBody;
                if (err.ValueKind == JsonValueKind.Object && err.TryGetProperty("message", out var m))
                    return m.GetString() ?? errBody;
            }
        }
        catch (Exception) { }
        // JSON 아니면 원본 (최대 500자)
        return errBody.Length > 500 ? errBody[..500] + "…" : errBody;
    }
}

/// <summary>도구 호출 자체가 서버에서 거부된 경우 (400). 일반 텍스트 응답으로 폴백 시도 가능.</summary>
public class ToolCallNotSupportedException : Exception
{
    public ToolCallNotSupportedException(string message) : base(message) { }
}
