using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace WebClientTpos.Server.AiChat;

/// <summary>
/// EN: AI chat provider for OpenAI and OpenRouter APIs (same request/response format).
///     Supports function calling (tool_calls) for MCP tool integration.
/// VI: AI chat provider cho OpenAI va OpenRouter API (cung dinh dang request/response).
///     Ho tro function calling (tool_calls) de tich hop MCP tool.
/// </summary>
public class OpenAiChatProvider : IAiChatProvider
{
    private readonly HttpClient _httpClient;
    private readonly ILogger<OpenAiChatProvider> _logger;

    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
        PropertyNameCaseInsensitive = true
    };

    public OpenAiChatProvider(HttpClient httpClient, ILogger<OpenAiChatProvider> logger)
    {
        _httpClient = httpClient;
        _logger = logger;
    }

    /// <summary>
    /// EN: Send a chat completion request to OpenAI / OpenRouter.
    /// VI: Gui chat completion request den OpenAI / OpenRouter.
    /// </summary>
    public async Task<AiChatResponse> SendAsync(AiChatRequest request, CancellationToken cancellationToken = default)
    {
        var messages = BuildMessages(request);
        var tools = BuildTools(request.Tools);

        var body = new Dictionary<string, object>
        {
            ["model"] = request.Model,
            ["messages"] = messages
        };

        if (tools is not null)
            body["tools"] = tools;

        var json = JsonSerializer.Serialize(body, JsonOptions);
        var url = $"{request.BaseUrl.TrimEnd('/')}/chat/completions";

        using var httpRequest = new HttpRequestMessage(HttpMethod.Post, url);
        httpRequest.Content = new StringContent(json, Encoding.UTF8, "application/json");
        httpRequest.Headers.TryAddWithoutValidation("Authorization", $"Bearer {request.ApiKey}");

        // EN: OpenRouter-specific headers for attribution and ranking
        // VI: Headers rieng cho OpenRouter de attribution va ranking
        if (request.BaseUrl.Contains("openrouter", StringComparison.OrdinalIgnoreCase))
        {
            httpRequest.Headers.TryAddWithoutValidation("HTTP-Referer", "https://goodgo.vn");
            httpRequest.Headers.TryAddWithoutValidation("X-Title", "aPOS POS");
        }

        _logger.LogDebug("EN: Sending OpenAI chat request to {Url} with model {Model}.",
                          url, request.Model);

        using var response = await _httpClient.SendAsync(httpRequest, cancellationToken);
        var responseBody = await response.Content.ReadAsStringAsync(cancellationToken);

        if (!response.IsSuccessStatusCode)
        {
            _logger.LogError("EN: OpenAI API error {StatusCode}: {Body}.",
                             (int)response.StatusCode, responseBody);
            throw new HttpRequestException(
                $"OpenAI API returned {(int)response.StatusCode}: {responseBody}");
        }

        return ParseResponse(responseBody);
    }

    /// <summary>
    /// EN: Build the messages array for the OpenAI API request.
    /// VI: Xay dung mang messages cho OpenAI API request.
    /// </summary>
    private static List<object> BuildMessages(AiChatRequest request)
    {
        var messages = new List<object>();

        // EN: System prompt as first message
        // VI: System prompt la tin nhan dau tien
        if (!string.IsNullOrWhiteSpace(request.SystemPrompt))
        {
            messages.Add(new { role = "system", content = request.SystemPrompt });
        }

        foreach (var msg in request.Messages)
        {
            if (msg.ToolCalls is { Count: > 0 })
            {
                // EN: Assistant message with tool_calls
                // VI: Tin nhan assistant voi tool_calls
                messages.Add(new
                {
                    role = msg.Role,
                    content = msg.Content ?? "",
                    tool_calls = msg.ToolCalls.Select(tc => new
                    {
                        id = tc.Id,
                        type = "function",
                        function = new
                        {
                            name = tc.Name,
                            arguments = tc.ArgumentsJson
                        }
                    }).ToList()
                });
            }
            else if (msg.ToolCallId is not null)
            {
                // EN: Tool result message
                // VI: Tin nhan ket qua tool
                messages.Add(new
                {
                    role = "tool",
                    tool_call_id = msg.ToolCallId,
                    content = msg.Content
                });
            }
            else
            {
                messages.Add(new { role = msg.Role, content = msg.Content });
            }
        }

        return messages;
    }

    /// <summary>
    /// EN: Build the tools array in OpenAI function calling format.
    /// VI: Xay dung mang tools theo dinh dang function calling cua OpenAI.
    /// </summary>
    private static List<object>? BuildTools(List<AiToolDefinition>? tools)
    {
        if (tools is null or { Count: 0 })
            return null;

        return tools.Select(t => (object)new
        {
            type = "function",
            function = new
            {
                name = t.Name,
                description = t.Description,
                parameters = t.Parameters
            }
        }).ToList();
    }

    /// <summary>
    /// EN: Parse the OpenAI API response into AiChatResponse.
    /// VI: Phan tich OpenAI API response thanh AiChatResponse.
    /// </summary>
    private AiChatResponse ParseResponse(string responseBody)
    {
        using var doc = JsonDocument.Parse(responseBody);
        var root = doc.RootElement;

        var choice = root.GetProperty("choices")[0];
        var message = choice.GetProperty("message");

        var content = message.TryGetProperty("content", out var contentEl) && contentEl.ValueKind != JsonValueKind.Null
            ? contentEl.GetString() ?? ""
            : "";

        List<AiToolCall>? toolCalls = null;

        if (message.TryGetProperty("tool_calls", out var toolCallsEl) && toolCallsEl.ValueKind == JsonValueKind.Array)
        {
            toolCalls = new List<AiToolCall>();
            foreach (var tc in toolCallsEl.EnumerateArray())
            {
                var id = tc.GetProperty("id").GetString()!;
                var fn = tc.GetProperty("function");
                var name = fn.GetProperty("name").GetString()!;
                var args = fn.GetProperty("arguments").GetString() ?? "{}";
                toolCalls.Add(new AiToolCall(id, name, args));
            }
        }

        var requiresExecution = toolCalls is { Count: > 0 };

        _logger.LogDebug("EN: OpenAI response parsed — content length: {ContentLen}, tool calls: {ToolCallCount}.",
                         content.Length, toolCalls?.Count ?? 0);

        return new AiChatResponse(content, toolCalls, requiresExecution);
    }
}
