#nullable disable
using Newtonsoft.Json;
using OneOf.Types;
using SharpToken;
using System.Net.Http.Json;
using System.Text.RegularExpressions;

namespace EllieBot.Modules.Games.Common.ChatterBot;

public partial class OpenAiApiSession : IChatterBotSession
{
    private readonly string _baseUrl;
    private readonly string _apiKey;
    private readonly string _model;
    private readonly int _maxHistory;
    private readonly int _maxTokens;
    private readonly int _minTokens;
    private readonly string _ellieUsername;
    private readonly GptEncoding _encoding;
    private List<OpenAiApiMessage> messages = new();
    private readonly IHttpClientFactory _httpFactory;


    public OpenAiApiSession(
        string url,
        string apiKey,
        string model,
        int chatHistory,
        int maxTokens,
        int minTokens,
        string personality,
        string ellieUsername,
        IHttpClientFactory factory)
    {
        if (string.IsNullOrWhiteSpace(url) || !Uri.TryCreate(url, UriKind.Absolute, out _))
        {
            throw new ArgumentException("Invalid OpenAi api url provided", nameof(url));
        }

        _baseUrl = url.TrimEnd('/');

        _apiKey = apiKey;
        _model = model;
        _httpFactory = factory;
        _maxHistory = chatHistory;
        _maxTokens = maxTokens;
        _minTokens = minTokens;
        _ellieUsername = UsernameCleaner().Replace(ellieUsername, "");
        _encoding = GptEncoding.GetEncodingForModel("gpt-4o");
        if (!string.IsNullOrWhiteSpace(personality))
        {
            messages.Add(new()
            {
                Role = "system",
                Content = personality,
                Name = _ellieUsername
            });
        }
    }


    [GeneratedRegex("[^a-zA-Z0-9_-]")]
    private static partial Regex UsernameCleaner();

    public async Task<OneOf.OneOf<ThinkResult, Error<string>>> Think(string input, string username)
    {
        username = UsernameCleaner().Replace(username, "");

        messages.Add(new()
        {
            Role = "user",
            Content = input,
            Name = username
        });

        while (messages.Count > _maxHistory + 2)
        {
            messages.RemoveAt(1);
        }

        var tokensUsed = messages.Sum(message => _encoding.Encode(message.Content).Count);

        tokensUsed *= 2;

        //check if we have the minimum number of tokens available to use. Remove messages until we have enough, otherwise exit out and inform the user why.
        while (_maxTokens - tokensUsed <= _minTokens)
        {
            if (messages.Count > 2)
            {
                var tokens = _encoding.Encode(messages[1].Content).Count * 2;
                tokensUsed -= tokens;
                messages.RemoveAt(1);
            }
            else
            {
                return new Error<string>(
                    "Token count exceeded, please increase the number of tokens in the bot config and restart.");
            }
        }

        using var http = _httpFactory.CreateClient();
        http.DefaultRequestHeaders.Authorization = new("Bearer", _apiKey);

        var data = await http.PostAsJsonAsync($"{_baseUrl}/v1/chat/completions",
            new OpenAiApiRequest()
            {
                Model = _model,
                Messages = messages,
                MaxTokens = _maxTokens - tokensUsed,
                Temperature = 1,
            });

        var dataString = await data.Content.ReadAsStringAsync();

        try
        {
            data.EnsureSuccessStatusCode();
        }
        catch (Exception ex)
        {
            Log.Error(ex, "Failed to get response from OpenAI: {Message}", ex.Message);
            return new Error<string>("Failed to get response from OpenAI");
        }

        try
        {

            var response = JsonConvert.DeserializeObject<OpenAiCompletionResponse>(dataString);

            // Log.Information("Received response: {Response} ", dataString);
            var res = response?.Choices?[0];
            var message = res?.Message?.Content;

            if (message is null)
            {
                return new Error<string>("ChatGpt: Received no response.");
            }

            messages.Add(new()
            {
                Role = "assistant",
                Content = message,
                Name = _ellieUsername
            });

            return new ThinkResult()
            {
                Text = message,
                TokensIn = response.Usage.PromptTokens,
                TokensOut = response.Usage.CompletionTokens
            };
        }
        catch
        {
            Log.Warning("Unexpected response received from OpenAI: {ResponseString}", dataString);
            return new Error<string>("Unexpected response received");
        }
    }
}