using System;
using System.ClientModel;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Azure.AI.OpenAI;
using CommunityToolkit.Mvvm.ComponentModel;
using OpenAI;
using OpenAI.Chat;

namespace SourceGit.Models
{
    public partial class OpenAIResponse
    {
        public OpenAIResponse(Action<string> onUpdate)
        {
            _onUpdate = onUpdate;
        }

        public void Append(string text)
        {
            var buffer = text;

            if (_thinkTail.Length > 0)
            {
                _thinkTail.Append(buffer);
                buffer = _thinkTail.ToString();
                _thinkTail.Clear();
            }

            buffer = REG_COT().Replace(buffer, "");

            var startIdx = buffer.IndexOf('<', StringComparison.Ordinal);
            if (startIdx >= 0)
            {
                if (startIdx > 0)
                    OnReceive(buffer.Substring(0, startIdx));

                var endIdx = buffer.IndexOf(">", startIdx + 1, StringComparison.Ordinal);
                if (endIdx <= startIdx)
                {
                    if (buffer.Length - startIdx <= 15)
                        _thinkTail.Append(buffer.Substring(startIdx));
                    else
                        OnReceive(buffer.Substring(startIdx));
                }
                else if (endIdx < startIdx + 15)
                {
                    var tag = buffer.Substring(startIdx + 1, endIdx - startIdx - 1);
                    if (_thinkTags.Contains(tag))
                        _thinkTail.Append(buffer.Substring(startIdx));
                    else
                        OnReceive(buffer.Substring(startIdx));
                }
                else
                {
                    OnReceive(buffer.Substring(startIdx));
                }
            }
            else
            {
                OnReceive(buffer);
            }
        }

        public void End()
        {
            if (_thinkTail.Length > 0)
            {
                OnReceive(_thinkTail.ToString());
                _thinkTail.Clear();
            }
        }

        private void OnReceive(string text)
        {
            if (!_hasTrimmedStart)
            {
                text = text.TrimStart();
                if (string.IsNullOrEmpty(text))
                    return;

                _hasTrimmedStart = true;
            }

            _onUpdate.Invoke(text);
        }

        [GeneratedRegex(@"<(think|thought|thinking|thought_chain)>.*?</\1>", RegexOptions.Singleline)]
        private static partial Regex REG_COT();

        private Action<string> _onUpdate = null;
        private StringBuilder _thinkTail = new StringBuilder();
        private HashSet<string> _thinkTags = ["think", "thought", "thinking", "thought_chain"];
        private bool _hasTrimmedStart = false;
    }

    public class OpenAIService : ObservableObject
    {
        public string Name
        {
            get => _name;
            set => SetProperty(ref _name, value);
        }

        public string Server
        {
            get => _server;
            set
            {
                // migrate old server value
                if (!string.IsNullOrEmpty(value) && value.EndsWith("/chat/completions", StringComparison.Ordinal))
                {
                    value = value.Substring(0, value.Length - "/chat/completions".Length);
                }
                SetProperty(ref _server, value);
            }
        }

        public string ApiKey
        {
            get => _apiKey;
            set => SetProperty(ref _apiKey, value);
        }

        public string Model
        {
            get => _model;
            set => SetProperty(ref _model, value);
        }

        public bool Streaming
        {
            get => _streaming;
            set => SetProperty(ref _streaming, value);
        }

        public string AnalyzeDiffPrompt
        {
            get => _analyzeDiffPrompt;
            set => SetProperty(ref _analyzeDiffPrompt, value);
        }

        public string GenerateSubjectPrompt
        {
            get => _generateSubjectPrompt;
            set => SetProperty(ref _generateSubjectPrompt, value);
        }

        public OpenAIService()
        {
            AnalyzeDiffPrompt = """
                You are an expert developer specialist in creating commits.
                Provide a super concise one sentence overall changes summary of the user `git diff` output following strictly the next rules:
                - Do not use any code snippets, imports, file routes or bullets points.
                - Do not mention the route of file that has been change.
                - Write clear, concise, and descriptive messages that explain the MAIN GOAL made of the changes.
                - Use the present tense and active voice in the message, for example, "Fix bug" instead of "Fixed bug.".
                - Use the imperative mood, which gives the message a sense of command, e.g. "Add feature" instead of "Added feature".
                - Avoid using general terms like "update" or "change", be specific about what was updated or changed.
                - Avoid using terms like "The main goal of", just output directly the summary in plain text
                """;

            GenerateSubjectPrompt = """
                You are an expert developer specialist in creating commits messages.
                Your only goal is to retrieve a single commit message.
                Based on the provided user changes, combine them in ONE SINGLE commit message retrieving the global idea, following strictly the next rules:
                - Assign the commit {type} according to the next conditions:
                    feat: Only when adding a new feature.
                    fix: When fixing a bug.
                    docs: When updating documentation.
                    style: When changing elements styles or design and/or making changes to the code style (formatting, missing semicolons, etc.) without changing the code logic.
                    test: When adding or updating tests.
                    chore: When making changes to the build process or auxiliary tools and libraries.
                    revert: When undoing a previous commit.
                    refactor: When restructuring code without changing its external behavior, or is any of the other refactor types.
                - Do not add any issues numeration, explain your output nor introduce your answer.
                - Output directly only one commit message in plain text with the next format: {type}: {commit_message}.
                - Be as concise as possible, keep the message under 50 characters.
                """;
        }

        public void Chat(string prompt, string question, CancellationToken cancellation, Action<string> onUpdate)
        {
            var server = new Uri(_server);
            var key = new ApiKeyCredential(_apiKey);
            var client = null as ChatClient;
            if (_server.Contains("openai.azure.com/", StringComparison.Ordinal))
            {
                var azure = new AzureOpenAIClient(server, key);
                client = azure.GetChatClient(_model);
            }
            else
            {
                var openai = new OpenAIClient(key, new() { Endpoint = server });
                client = openai.GetChatClient(_model);
            }

            var messages = new List<ChatMessage>();
            messages.Add(_model.Equals("o1-mini", StringComparison.Ordinal) ? new UserChatMessage(prompt) : new SystemChatMessage(prompt));
            messages.Add(new UserChatMessage(question));

            try
            {
                var rsp = new OpenAIResponse(onUpdate);

                if (_streaming)
                {
                    var updates = client.CompleteChatStreaming(messages, null, cancellation);

                    foreach (var update in updates)
                    {
                        if (update.ContentUpdate.Count > 0)
                            rsp.Append(update.ContentUpdate[0].Text);
                    }
                }
                else
                {
                    var completion = client.CompleteChat(messages, null, cancellation);

                    if (completion.Value.Content.Count > 0)
                        rsp.Append(completion.Value.Content[0].Text);
                }

                rsp.End();
            }
            catch
            {
                if (!cancellation.IsCancellationRequested)
                    throw;
            }
        }

        private string _name;
        private string _server;
        private string _apiKey;
        private string _model;
        private bool _streaming = true;
        private string _analyzeDiffPrompt;
        private string _generateSubjectPrompt;
    }
}
