using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Polly;
using Polly.CircuitBreaker;

namespace MktXService.Infrastructure.ExternalServices.OpenAI;

/// <summary>
/// EN: OpenAI API client implementation with Polly resilience.
/// VI: Triển khai OpenAI API client với Polly resilience.
/// </summary>
public class OpenAIServiceClient : IAIServiceClient
{
    private readonly HttpClient _httpClient;
    private readonly OpenAIOptions _options;
    private readonly ILogger<OpenAIServiceClient> _logger;
    private readonly AsyncCircuitBreakerPolicy<HttpResponseMessage> _circuitBreakerPolicy;

    private const string BaseUrl = "https://api.openai.com/v1";

    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
        WriteIndented = false
    };

    public OpenAIServiceClient(
        HttpClient httpClient,
        IOptions<OpenAIOptions> options,
        ILogger<OpenAIServiceClient> logger)
    {
        _httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
        _options = options?.Value ?? throw new ArgumentNullException(nameof(options));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));

        _httpClient.DefaultRequestHeaders.Authorization = 
            new AuthenticationHeaderValue("Bearer", _options.ApiKey);

        _circuitBreakerPolicy = Policy<HttpResponseMessage>
            .Handle<HttpRequestException>()
            .OrResult(r => (int)r.StatusCode >= 500)
            .CircuitBreakerAsync(
                5,
                TimeSpan.FromMinutes(1),
                (outcome, breakDuration) =>
                {
                    _logger.LogError("OpenAI circuit breaker opened for {Duration}s", breakDuration.TotalSeconds);
                },
                () => _logger.LogInformation("OpenAI circuit breaker reset"));
    }

    #region Chat Completions

    public async Task<ChatCompletionResult> GenerateChatResponseAsync(
        IEnumerable<ChatMessage> messages,
        ChatCompletionOptions? options = null,
        CancellationToken cancellationToken = default)
    {
        options ??= new ChatCompletionOptions();

        var payload = new
        {
            model = options.Model,
            messages = messages.Select(m => new { role = m.Role, content = m.Content, name = m.Name }).ToList(),
            temperature = options.Temperature,
            max_tokens = options.MaxTokens,
            top_p = options.TopP,
            frequency_penalty = options.FrequencyPenalty,
            presence_penalty = options.PresencePenalty
        };

        try
        {
            var response = await SendRequestAsync($"{BaseUrl}/chat/completions", payload, cancellationToken);

            if (!response.IsSuccessStatusCode)
            {
                var error = await response.Content.ReadAsStringAsync(cancellationToken);
                _logger.LogError("OpenAI API error: {Error}", error);
                return new ChatCompletionResult(false, null, null, null, error, 0, null);
            }

            var content = await response.Content.ReadAsStringAsync(cancellationToken);
            var result = JsonSerializer.Deserialize<JsonElement>(content);

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

            return new ChatCompletionResult(
                true,
                message.TryGetProperty("content", out var c) ? c.GetString() : null,
                null,
                null,
                null,
                usage.GetProperty("total_tokens").GetInt32(),
                choice.GetProperty("finish_reason").GetString());
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to generate chat response");
            return new ChatCompletionResult(false, null, null, null, ex.Message, 0, null);
        }
    }

    public async Task<ChatCompletionResult> GenerateChatWithFunctionsAsync(
        IEnumerable<ChatMessage> messages,
        IEnumerable<FunctionDefinition> functions,
        ChatCompletionOptions? options = null,
        CancellationToken cancellationToken = default)
    {
        options ??= new ChatCompletionOptions();

        var payload = new
        {
            model = options.Model,
            messages = messages.Select(m => new { role = m.Role, content = m.Content }).ToList(),
            functions = functions.Select(f => new
            {
                name = f.Name,
                description = f.Description,
                parameters = f.Parameters
            }).ToList(),
            function_call = "auto",
            temperature = options.Temperature,
            max_tokens = options.MaxTokens
        };

        try
        {
            var response = await SendRequestAsync($"{BaseUrl}/chat/completions", payload, cancellationToken);

            if (!response.IsSuccessStatusCode)
            {
                var error = await response.Content.ReadAsStringAsync(cancellationToken);
                return new ChatCompletionResult(false, null, null, null, error, 0, null);
            }

            var content = await response.Content.ReadAsStringAsync(cancellationToken);
            var result = JsonSerializer.Deserialize<JsonElement>(content);

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

            string? functionName = null;
            string? functionArgs = null;

            if (message.TryGetProperty("function_call", out var fc))
            {
                functionName = fc.GetProperty("name").GetString();
                functionArgs = fc.GetProperty("arguments").GetString();
            }

            return new ChatCompletionResult(
                true,
                message.TryGetProperty("content", out var c) ? c.GetString() : null,
                functionName,
                functionArgs,
                null,
                usage.GetProperty("total_tokens").GetInt32(),
                choice.GetProperty("finish_reason").GetString());
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to generate chat with functions");
            return new ChatCompletionResult(false, null, null, null, ex.Message, 0, null);
        }
    }

    public async IAsyncEnumerable<string> StreamChatResponseAsync(
        IEnumerable<ChatMessage> messages,
        ChatCompletionOptions? options = null,
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        options ??= new ChatCompletionOptions();

        var payload = new
        {
            model = options.Model,
            messages = messages.Select(m => new { role = m.Role, content = m.Content }).ToList(),
            temperature = options.Temperature,
            max_tokens = options.MaxTokens,
            stream = true
        };

        var request = new HttpRequestMessage(HttpMethod.Post, $"{BaseUrl}/chat/completions")
        {
            Content = new StringContent(JsonSerializer.Serialize(payload, JsonOptions), Encoding.UTF8, "application/json")
        };

        var response = await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
        response.EnsureSuccessStatusCode();

        await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
        using var reader = new StreamReader(stream);

        string? line;
        while ((line = await reader.ReadLineAsync(cancellationToken)) != null && !cancellationToken.IsCancellationRequested)
        {
            if (string.IsNullOrEmpty(line) || !line.StartsWith("data: "))
                continue;

            var data = line[6..];
            if (data == "[DONE]")
                break;

            var json = JsonSerializer.Deserialize<JsonElement>(data);
            if (json.TryGetProperty("choices", out var choices) && 
                choices.GetArrayLength() > 0 &&
                choices[0].TryGetProperty("delta", out var delta) &&
                delta.TryGetProperty("content", out var content))
            {
                var text = content.GetString();
                if (!string.IsNullOrEmpty(text))
                    yield return text;
            }
        }
    }

    #endregion

    #region Intent Detection

    public async Task<IntentDetectionResult> DetectIntentAsync(
        string message,
        IEnumerable<string> possibleIntents,
        CancellationToken cancellationToken = default)
    {
        var systemPrompt = $@"You are an intent classifier. Classify the user message into one of these intents: {string.Join(", ", possibleIntents)}.
Respond ONLY with a JSON object: {{""intent"": ""<intent>"", ""confidence"": <0.0-1.0>}}";

        var messages = new[]
        {
            new ChatMessage("system", systemPrompt),
            new ChatMessage("user", message)
        };

        var result = await GenerateChatResponseAsync(messages, new ChatCompletionOptions(Temperature: 0.1), cancellationToken);

        if (!result.Success || string.IsNullOrEmpty(result.Content))
            return new IntentDetectionResult("unknown", 0);

        try
        {
            var json = JsonSerializer.Deserialize<JsonElement>(result.Content);
            return new IntentDetectionResult(
                json.GetProperty("intent").GetString() ?? "unknown",
                json.GetProperty("confidence").GetDouble());
        }
        catch
        {
            return new IntentDetectionResult("unknown", 0);
        }
    }

    public async Task<Dictionary<string, object>> ExtractEntitiesAsync(
        string message,
        IEnumerable<EntityDefinition> entityDefinitions,
        CancellationToken cancellationToken = default)
    {
        var entityDesc = string.Join("\n", entityDefinitions.Select(e => $"- {e.Name} ({e.Type}): {e.Description}"));
        var systemPrompt = $@"Extract the following entities from the user message:
{entityDesc}

Respond ONLY with a JSON object containing the extracted values.";

        var messages = new[]
        {
            new ChatMessage("system", systemPrompt),
            new ChatMessage("user", message)
        };

        var result = await GenerateChatResponseAsync(messages, new ChatCompletionOptions(Temperature: 0.1), cancellationToken);

        if (!result.Success || string.IsNullOrEmpty(result.Content))
            return new Dictionary<string, object>();

        try
        {
            return JsonSerializer.Deserialize<Dictionary<string, object>>(result.Content) 
                   ?? new Dictionary<string, object>();
        }
        catch
        {
            return new Dictionary<string, object>();
        }
    }

    #endregion

    #region Embeddings

    public async Task<float[]> GenerateEmbeddingAsync(string text, CancellationToken cancellationToken = default)
    {
        var payload = new
        {
            model = _options.EmbeddingModel,
            input = text
        };

        var response = await SendRequestAsync($"{BaseUrl}/embeddings", payload, cancellationToken);
        
        if (!response.IsSuccessStatusCode)
            return Array.Empty<float>();

        var content = await response.Content.ReadAsStringAsync(cancellationToken);
        var result = JsonSerializer.Deserialize<JsonElement>(content);
        var embedding = result.GetProperty("data")[0].GetProperty("embedding");

        return embedding.EnumerateArray().Select(e => e.GetSingle()).ToArray();
    }

    public async Task<IEnumerable<float[]>> GenerateEmbeddingsAsync(
        IEnumerable<string> texts,
        CancellationToken cancellationToken = default)
    {
        var payload = new
        {
            model = _options.EmbeddingModel,
            input = texts.ToList()
        };

        var response = await SendRequestAsync($"{BaseUrl}/embeddings", payload, cancellationToken);
        
        if (!response.IsSuccessStatusCode)
            return Enumerable.Empty<float[]>();

        var content = await response.Content.ReadAsStringAsync(cancellationToken);
        var result = JsonSerializer.Deserialize<JsonElement>(content);

        return result.GetProperty("data")
            .EnumerateArray()
            .Select(d => d.GetProperty("embedding").EnumerateArray().Select(e => e.GetSingle()).ToArray())
            .ToList();
    }

    #endregion

    #region Moderation

    public async Task<ModerationResult> ModerateContentAsync(string content, CancellationToken cancellationToken = default)
    {
        var payload = new { input = content };

        var response = await SendRequestAsync($"{BaseUrl}/moderations", payload, cancellationToken);
        
        if (!response.IsSuccessStatusCode)
            return new ModerationResult(false, new Dictionary<string, bool>(), new Dictionary<string, double>());

        var responseContent = await response.Content.ReadAsStringAsync(cancellationToken);
        var result = JsonSerializer.Deserialize<JsonElement>(responseContent);
        var moderationResult = result.GetProperty("results")[0];

        var categories = moderationResult.GetProperty("categories");
        var categoryScores = moderationResult.GetProperty("category_scores");

        return new ModerationResult(
            moderationResult.GetProperty("flagged").GetBoolean(),
            categories.EnumerateObject().ToDictionary(p => p.Name, p => p.Value.GetBoolean()),
            categoryScores.EnumerateObject().ToDictionary(p => p.Name, p => p.Value.GetDouble()));
    }

    #endregion

    #region Private Helpers

    private async Task<HttpResponseMessage> SendRequestAsync(string url, object payload, CancellationToken cancellationToken)
    {
        return await _circuitBreakerPolicy.ExecuteAsync(async () =>
        {
            var request = new HttpRequestMessage(HttpMethod.Post, url)
            {
                Content = new StringContent(JsonSerializer.Serialize(payload, JsonOptions), Encoding.UTF8, "application/json")
            };

            return await _httpClient.SendAsync(request, cancellationToken);
        });
    }

    #endregion
}

/// <summary>
/// EN: OpenAI API configuration options.
/// VI: Các tùy chọn cấu hình OpenAI API.
/// </summary>
public class OpenAIOptions
{
    public string ApiKey { get; set; } = string.Empty;
    public string DefaultModel { get; set; } = "gpt-4o-mini";
    public string EmbeddingModel { get; set; } = "text-embedding-3-small";
    public int MaxTokens { get; set; } = 1000;
    public double DefaultTemperature { get; set; } = 0.7;
}
