using Services.Models;
using System.Diagnostics;
using System.Net.Http.Json;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using ToolsServices;
using OllamaService.Models;

namespace Services.Ollama
{
    public static class OllamaService
    {
        #region Variables
        private static HttpClient? _HttpClient;
        private static readonly string NomFichierData = FichiersInternesService.ParamsOllama;// "paramsOllama.txt";
        private static List<string> ImagesFullFilename = new();
        #endregion

        #region Méthodes publiques

        #region InterActions
        /// <summary>
        /// Vérifie si Ollama est actif
        /// </summary>
        /// <param name="isOnDemand"></param>
        /// <returns></returns>
        public static async Task<bool> IsOllamaActif(bool isOnDemand)
        {
            var precisionContext = isOnDemand ? "OnDemand" : "Batch";
            LoggerService.LogInfo($"OllamaService.IsOllamaActif {precisionContext}");
            
            try
            {
                ParametresOllamaService? ollama = LoadParametres();
                if (ollama == null)
                {
                    var msg = "Erreur de chargement des paramètres Ollama.";
                    LoggerService.LogWarning(msg);
                    return false;
                }
                var url = "";
                if(isOnDemand)
                    url = $"{ollama.Ollama_URL}/api/version";
                else
                    url = $"{ollama.Ollama_Batch_URL}/api/version";
                _HttpClient = new HttpClient();

                _HttpClient.Timeout = TimeSpan.FromSeconds(30);
                var response = await _HttpClient.GetAsync(url);
                LoggerService.LogDebug($"OllamaService.IsOllamaActif {precisionContext} : {response.IsSuccessStatusCode}");
                return response.IsSuccessStatusCode;
            }
            catch(Exception ex)
            {
                LoggerService.LogError($"OllamaService.IsOllamaActif {precisionContext} : False --> {ex.Message}");
                return false;
            }
        }

        /// <summary>
        /// Appelle l'api Generate de Ollama
        /// </summary>
        /// <param name="prompt"></param>
        /// <param name="precision"></param>
        /// <param name="model"></param>
        /// <param name="isOnDemand"></param>
        /// <returns></returns>
        public static async Task<(bool, string)> GenererAsync(string prompt, string precision, string model, bool isOnDemand)
        {
            var precisionContext = isOnDemand ? "OnDemand" : "Batch";
            LoggerService.LogInfo($"OllamaService.GenererAsync {precisionContext} : {model}");
            string? sReturn = "";
            Stopwatch chrono = new Stopwatch();
            chrono.Start();
            bool isSuccess = true;
            ParametresOllamaService? ollama = LoadParametres();
            if (ollama == null)
            {
                var msg = "Erreur de chargement des paramètres Ollama.";
                LoggerService.LogWarning(msg);
                return (false, msg);
            }

            try
            {
                //prompt = "Donne tes réponses dans la langue Française." + "\n" + prompt;
                prompt = PromptService.GetPrompt(PromptService.ePrompt.OllamaService_PromptSystem) + "\n" + prompt;
                var requestBody = new
                {
                    model = model,
                    prompt = TruncatePromptBySentenceAsync(prompt),
                    temperature = isOnDemand ? ollama.Ollama_Temperature : ollama.Ollama_Batch_Temperature,  // → 0 : réponses plus précises et conservatrices ; 1 : plus créatives
                    top_p = isOnDemand ? ollama.Ollama_Top_p : ollama.Ollama_Batch_Top_p,         // → on considère uniquement les mots parmi les plus probables (limite l’invention)
                    max_tokens = isOnDemand ? ollama.Ollama_Max_tokens : ollama.Ollama_Batch_Max_tokens,    // → pour ne pas générer des réponses trop longues
                    stream = false
                };
                var url = isOnDemand ? ollama.Ollama_URL : ollama.Ollama_Batch_URL;
                int timeOut = isOnDemand ? ollama.Ollama_TimeOut : ollama.Ollama_Batch_TimeOut;

                url = $"{url}/api/generate";

                LoggerService.LogDebug($"Génération Ollama : {precision} - Model : {model} - URL : {url} - Time-out : {timeOut}");
                LoggerService.LogDebug($"Prompt : {requestBody.prompt}");

                _HttpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(timeOut) };

                var response = await _HttpClient.PostAsJsonAsync($"{url}", requestBody);
                
                if (!response.IsSuccessStatusCode)
                {
                    var err = await response.Content.ReadAsStringAsync();
                    var msg = $"Erreur Ollama({response.StatusCode}) : {err}";
                    LoggerService.LogWarning(msg);
                    return (false, $"{msg}");
                }

                var json = await response.Content.ReadFromJsonAsync<JsonElement>();
                sReturn = json!.GetProperty("response")!.GetString();
                if (sReturn != null)
                {
                    sReturn = EpureReponse(sReturn);
                }

                LoggerService.LogDebug($"OllamaService.GenerateAsync : {sReturn}");
               
            }
            catch (HttpRequestException ex)
            {
                isSuccess = false;
                LoggerService.LogError("Erreur HTTP : " + ex.Message);
            }
            catch (OperationCanceledException)
            {
                isSuccess = false;
                LoggerService.LogError($"La requête a été annulée après {ollama.Ollama_TimeOut} secondes.");
            }
            catch (Exception ex)
            {
                isSuccess = false;
                sReturn = $"Erreur lors de la génération : {ex.Message}";
                LoggerService.LogError(sReturn);
                //return $"{msg}";
            }
            finally
            {
                chrono.Stop();
                LoggerService.LogInfo($"Temps de la génération : {chrono.ElapsedMilliseconds / 1000} s ({precision})");

            }
            return (isSuccess,$"{sReturn}");
        }

        /// <summary>
        /// Appelle l'api Chat de Ollama
        /// </summary>
        /// <param name="model"></param>
        /// <param name="messages"></param>
        /// <returns></returns>
        public static async Task<(bool, string)> ChatAsync(string model, List<ChatMessage> messages)
        {
            LoggerService.LogInfo($"OllamaService.SendMessageAsync : {model}");
            Stopwatch chrono = new Stopwatch();
            chrono.Start();
            string? sReturn = "";
            bool isSuccess = true;
            ParametresOllamaService? ollama = LoadParametres();
            if (ollama == null)
            {
                var msg = "Erreur de chargement des paramètres Ollama.";
                LoggerService.LogWarning(msg);
                return (false, msg);
            }

            try
            {
                var promptSystem = PromptService.GetPrompt(PromptService.ePrompt.OllamaService_PromptSystem);
                var systemPrompt = new ChatMessage { Role = "system", Content = promptSystem, Model = model };
                var allMessages = new List<ChatMessage> { systemPrompt };

                messages[0].Content = promptSystem + "\n" + messages[0].Content;
                allMessages.AddRange(messages);

                var request = new
                {
                    model = model,
                    messages = allMessages.Select(m => new { role = m.Role, content = m.Content }).ToList(),
                    temperature = ollama.Ollama_Temperature,  // → 0 : réponses plus précises et conservatrices ; 1 : plus créatives
                    top_p = ollama.Ollama_Top_p,         // → on considère uniquement les mots parmi les plus probables (limite l’invention)
                    max_tokens = ollama.Ollama_Max_tokens,    // → pour ne pas générer des réponses trop longues
                    stream = false
                };

                var url = $"{ollama.Ollama_URL}/api/chat";
                LoggerService.LogDebug($"Génération Ollama : CHAT - Model : {model} - URL : {url} - Time-out : {ollama.Ollama_TimeOut}");
                LoggerService.LogDebug($"Prompt : {request.messages[1].content}");

                _HttpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(ollama.Ollama_TimeOut) };

                var response = await _HttpClient.PostAsync(url, new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, "application/json"));

                if (!response.IsSuccessStatusCode)
                {
                    var err = await response.Content.ReadAsStringAsync();
                    var msg = $"Erreur Ollama({response.StatusCode}) : {err}";
                    LoggerService.LogWarning(msg);
                    return (false, $"{msg}");
                }

                var result = await response.Content.ReadAsStringAsync();
                using var doc = JsonDocument.Parse(result);
                sReturn = doc.RootElement.GetProperty("message").GetProperty("content").GetString();
                if (sReturn != null)
                {
                    sReturn = EpureReponse(sReturn);
                }
                LoggerService.LogDebug($"OllamaService.SendMessageAsync : {sReturn}");

            }

            catch (Exception ex) 
            {
                isSuccess = false;
                sReturn = $"Erreur lors de la génération : {ex.Message}";
                LoggerService.LogError(sReturn);
            }

            finally
            {
                chrono.Stop();
                LoggerService.LogInfo($"Temps de la génération : {chrono.ElapsedMilliseconds / 1000} s (LLM)");

                if (sReturn == null)
                    sReturn = "";
            }
            return (isSuccess, $"{sReturn}");
        }

        /// <summary>
        /// Appelle l'api Generate de Ollama avec envoi d'images dans le prompt
        /// </summary>
        /// <param name="model"></param>
        /// <param name="prompt"></param>
        /// <param name="imagesFullFilename"></param>
        /// <returns></returns>
        public static async Task<(bool, string)> GenererAsync(string model, string prompt, List<string> imagesFullFilename)
        {
            LoggerService.LogInfo($"OllamaService.SendMessageAsync : {model}");
            Stopwatch chrono = new Stopwatch();
            chrono.Start();
            bool isSuccess = true;
            var imagesBase64 = new List<string>();

            ParametresOllamaService? ollama = LoadParametres();
            if (ollama == null)
            {
                var msg = "Erreur de chargement des paramètres Ollama.";
                LoggerService.LogWarning(msg);
                return (false, msg);
            }

            foreach (var uneImage in imagesFullFilename)
            {
                byte[] imageBytes = File.ReadAllBytes(uneImage);
                imagesBase64.Add(Convert.ToBase64String(imageBytes));
            }
            var request = new OllamaRequest
            {
                Model = model,
                Prompt = PromptService.GetPrompt(PromptService.ePrompt.OllamaService_PromptSystem) + "\n" + prompt,
                Temperature = ollama.Ollama_Temperature,  // → 0 : réponses plus précises et conservatrices ; 1 : plus créatives
                Top_p = ollama.Ollama_Top_p,         // → on considère uniquement les mots parmi les plus probables (limite l’invention)
                Max_tokens = ollama.Ollama_Max_tokens,    // → pour ne pas générer des réponses trop longues
                Images = imagesBase64,
                Stream = false
            };

            string? sReturn = "";


            try
            {

                var url = $"{ollama.Ollama_URL}/api/generate";
                LoggerService.LogDebug($"Prompt envoyé à {url}");
                _HttpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(ollama.Ollama_TimeOut) };

                var response = await _HttpClient.PostAsJsonAsync(url, request);

                if (!response.IsSuccessStatusCode)
                {
                    var err = await response.Content.ReadAsStringAsync();
                    var msg = $"Erreur Ollama({response.StatusCode}) : {err}";
                    LoggerService.LogWarning(msg);
                    return (false, $"{msg}");
                }

                var result = await response.Content.ReadFromJsonAsync<OllamaResponse>();
                if (result != null)
                {
                    sReturn = result.Completion.Trim();
                    sReturn = EpureReponse(sReturn);
                }
                LoggerService.LogDebug($"OllamaService.SendMessageAsync : {sReturn}");

            }
            catch (Exception ex)
            {
                isSuccess=false;
                sReturn = $"Erreur lors de la génération : {ex.Message}";
                LoggerService.LogError(sReturn);
                //return sReturn;
            }
            finally
            {
                chrono.Stop();
                LoggerService.LogInfo($"Temps de la génération : {chrono.ElapsedMilliseconds / 1000} s (Interprétation images)");

                if (sReturn == null)
                    sReturn = "";
            }
            return (isSuccess, $"{sReturn}");
        }

        #endregion

        #region Gestion des paramètres
        public static ParametresOllamaService? LoadParametres()
        {
            LoggerService.LogInfo("OllamaService.LoadParametres");
            ParametresOllamaService SelectedItem = new();

            try
            {              
                if (File.Exists(NomFichierData))
                {

                    string[] lignes = File.ReadAllLines(NomFichierData);

                    if (lignes.Length > 0)
                    {
                        SelectedItem.Ollama_URL = lignes[0];
                    }
                    if (lignes.Length > 1)
                    {
                        if(int.TryParse(lignes[1], out int timeout))
                            SelectedItem.Ollama_TimeOut = timeout;
                    }
                    if (lignes.Length > 2)
                    {
                        if (double.TryParse(lignes[2], out double temperature))
                            SelectedItem.Ollama_Temperature = temperature;
                    }
                    if (lignes.Length > 3)
                    {
                        if (double.TryParse(lignes[3], out double top_p))
                            SelectedItem.Ollama_Top_p = top_p;
                    }
                    if (lignes.Length > 4)
                    {
                        if (int.TryParse(lignes[4], out int max_tokens))
                            SelectedItem.Ollama_Max_tokens = max_tokens;
                    }


                    if (lignes.Length > 5)
                    {
                        SelectedItem.Ollama_Batch_URL = lignes[5];
                    }
                    if (lignes.Length > 6)
                    {
                        if (int.TryParse(lignes[6], out int timeout))
                            SelectedItem.Ollama_Batch_TimeOut = timeout;
                    }
                    if (lignes.Length > 7)
                    {
                        if (double.TryParse(lignes[7], out double temperature))
                            SelectedItem.Ollama_Batch_Temperature = temperature;
                    }
                    if (lignes.Length > 8)
                    {
                        if (double.TryParse(lignes[8], out double top_p))
                            SelectedItem.Ollama_Batch_Top_p = top_p;
                    }
                    if (lignes.Length > 9)
                    {
                        if (int.TryParse(lignes[9], out int max_tokens))
                            SelectedItem.Ollama_Batch_Max_tokens = max_tokens;
                    }

                }
                return SelectedItem;

            }
            catch
            {
                return null;
            }
        }

        public static bool SaveParametres(ParametresOllamaService selectedItem)
        {
            LoggerService.LogInfo("OllamaService.SaveParametres");
            try
            {
                StringBuilder sb = new();
                sb.AppendLine(selectedItem.Ollama_URL);
                sb.AppendLine(selectedItem.Ollama_TimeOut.ToString());
                sb.AppendLine(selectedItem.Ollama_Temperature.ToString());
                sb.AppendLine(selectedItem.Ollama_Top_p.ToString());
                sb.AppendLine(selectedItem.Ollama_Max_tokens.ToString());

                sb.AppendLine(selectedItem.Ollama_Batch_URL);
                sb.AppendLine(selectedItem.Ollama_Batch_TimeOut.ToString());
                sb.AppendLine(selectedItem.Ollama_Batch_Temperature.ToString());
                sb.AppendLine(selectedItem.Ollama_Batch_Top_p.ToString());
                sb.AppendLine(selectedItem.Ollama_Batch_Max_tokens.ToString());

                File.WriteAllText(NomFichierData, sb.ToString());

                return true;
            }
            catch
            {
                return false;
            }

        }

        public static async Task<List<OllamaModel>> GetInstalledModelsAsync(bool isOnDemand)
        {
            LoggerService.LogInfo("OllamaService.GetInstalledModelsAsync");
            try
            {
                ParametresOllamaService? ollama = LoadParametres();
                if (ollama == null)
                {
                    return new List<OllamaModel> { new OllamaModel { Name = "Erreur de chargement des paramètres Ollama." } };
                }

                var url = "";
                int timeOut = 360;
                if (isOnDemand)
                {
                    url = $"{ollama.Ollama_URL}/api/tags";
                    timeOut = ollama.Ollama_TimeOut;
                }
                else
                {
                    url = $"{ollama.Ollama_Batch_URL}/api/tags";
                    timeOut = ollama.Ollama_Batch_TimeOut;
                }
                _HttpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(timeOut) };

                var response = await _HttpClient.GetAsync($"{url}");
                LoggerService.LogDebug($"Requête envoyée à {url}");
                response.EnsureSuccessStatusCode();

                var json = await response.Content.ReadAsStringAsync();
                var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = false };
                var result = JsonSerializer.Deserialize<OllamaTagsResponse>(json, options);

                if (result?.Models == null)
                {
                    return new List<OllamaModel> { new OllamaModel { Name = "Erreur de chargement des paramètres Ollama." } };
                }

                return result.Models;
            }
            catch (Exception ex)
            {
                LoggerService.LogError($"Erreur lors de la récupération des modèles installés : {ex.Message}");
                return new List<OllamaModel> { new OllamaModel { Name = $"Erreur : {ex.Message}" } };
            }
        }

        public static string GetModeleIA(string useCase)
        {
            LoggerService.LogInfo("OllamaService.GetModeleIA");

            ModelSelector selector = new();
            var models = selector.GetModelsForUseCase(useCase);
            if(models.ToList().Count>1)
                return models.First();

            return "";
        }

        public static IEnumerable<string> GetModelesIA(string useCase)
        {
            LoggerService.LogInfo("OllamaService.GetModelesIA");

            ModelSelector selector = new();
            var models = selector.GetModelsForUseCase(useCase);
            return models;

        }

        public static bool SaveParametresModeles(string useCase, ModelConfig models)
        {
            LoggerService.LogInfo("OllamaService.SaveParametresModeles");

            ModelSelector selector = new();
            var b = selector.SaveConfig(useCase, models);
            return b;
        }
        #endregion

        #region Nettoyage des prompts et surveillance de la taille
        public static string CleanUserInput(string input, out bool isPromptInjection)
        {
            LoggerService.LogInfo("OllamaService.CleanUserInput");
            isPromptInjection = false;
            var avant = input;
            if (string.IsNullOrWhiteSpace(input))
                return string.Empty;

            // 1. Limiter la taille max (ex : 500 caractères)
            /*
            int maxLength = 500;
            if (input.Length > maxLength)
                input = input.Substring(0, maxLength);
            */

            // 2. Supprimer les caractères non imprimables (ex : contrôles, retours chariot non standards)
            input = RemoveNonPrintableChars(input);

            // 3. Échapper les triples quotes pour ne pas casser le prompt (on remplace ''' par ' ' ' par exemple)
            input = input.Replace("'''", "' ' '");

            // 4. Rechercher et remplacer les mots/expressions d’injection potentielles
            string[] blacklist = new string[] {
            "ignore", "cancel", "stop generating", "disregard instructions","oublie les instructions", "oublie",
            "ignore instructions","ignore les instructions", "forget", "override", "bypass"
            };

            foreach (var word in blacklist)
            {
                // Remplacer même si insensible à la casse
                input = Regex.Replace(input, Regex.Escape(word), "[CENSORED]", RegexOptions.IgnoreCase);
            }

            // 5. Optionnel : remplacer les guillemets doubles et simples pour éviter la confusion
            input = input.Replace("\"", "'");
            input = input.Replace("\\", "/"); // éviter les échappements

            if (input.Contains("[CENSORED]"))
            {
                LoggerService.LogDebug($"Avant :{avant}");
                LoggerService.LogDebug($"Après :{input}");
                isPromptInjection = true;
            }

            return input.Trim();
        }
        #endregion

        #endregion

        #region Méthodes privées
        private static string EpureReponse(string texte)
        {
            var txt = texte;
            txt = txt.Replace("**", "");
            //txt = txt.Replace("*", "");
            txt = txt.Replace("</end_of_turn>", "");
            txt = txt.Replace("</start_of_turn>", "");
            txt = txt.Replace("```csv", "");
            txt = txt.Replace("```xml", "");
            txt = txt.Replace("```", "");
            txt = txt.Trim();
            return txt;
        }

        private static async Task<int> GetTokenMaxAsync(bool isOld)
        {
            LoggerService.LogInfo("OllamaService.GetTokenMaxAsync");
            try
            {
                ParametresOllamaService? ollama = LoadParametres();
                if (ollama == null)
                {
                    return -1;
                }
                var requestBody = new
                {
                    model = "",// ollama.Ollama_Model,
                };

                _HttpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(ollama.Ollama_TimeOut) };

                // Construire l'URL pour obtenir les informations du modèle
                string url = $"{ollama.Ollama_URL}/api/show";
                LoggerService.LogDebug($"Prompt envoyé à {url}");

                var response = await _HttpClient.PostAsJsonAsync($"{url}", requestBody);


                // Envoyer une requête GET à l'API Ollama
                //HttpResponseMessage response = await _HttpClient.GetAsync(url);

                // Vérifier le statut de la réponse
                if (response.IsSuccessStatusCode)
                {
                    // Lire le contenu de la réponse
                    string responseBody = await response.Content.ReadAsStringAsync();

                    var json = await response.Content.ReadFromJsonAsync<JsonElement>();
                    var contextLength = json
                        .GetProperty("model_info")
                        .GetProperty("llama.context_length")
                        .GetInt32();

                    return contextLength;

                    //return maxContextSize.HasValue ? (int)maxContextSize : 0;
                }
                else
                {
                    var model = "";//ollama.Ollama_Model
                    LoggerService.LogError($"Erreur lors de la récupération des informations du modèle {model}: {response.StatusCode}");
                    return -1;
                }
            }
            catch (Exception ex)
            {
                LoggerService.LogError($"Erreur lors de la récupération du modèle : {ex.Message}");
                return -3; // Erreur lors de la récupération du modèle
            }
        }

        // Supprime les caractères non imprimables
        private static string RemoveNonPrintableChars(string text)
        {
            LoggerService.LogInfo("OllamaService.RemoveNonPrintableChars");
            var sb = new StringBuilder();
            foreach (char c in text)
            {
                // Exclure uniquement les caractères de contrôle ASCII (< 32), sauf les classiques utiles
                if (!char.IsControl(c) || c == '\n' || c == '\r' || c == '\t')
                {
                    sb.Append(c);
                }
            }
            return sb.ToString();
        }

        private static int GetTokenMaxAsync()
        {
            LoggerService.LogInfo("OllamaService.GetTokenMaxAsync");
            try
            {
                //await Task.Delay(1);
                return 4096;
            }
            catch (Exception ex)
            {
                LoggerService.LogError($"Erreur lors de la récupération du modèle : {ex.Message}");
                return -3; // Erreur lors de la récupération du modèle
            }
        }

        private static int CountTokensAsync(string prompt)
        {
            LoggerService.LogInfo("OllamaService.CountTokensAsync");
            try
            {
                // 1 token, en langue francaise, c'est 1.25 mots (environ)
                int estimatedTokens = prompt.Split(' ').Length * 5 / 4;
                return estimatedTokens;
            }
            catch (Exception ex)
            {
                LoggerService.LogError($"Erreur lors de la récupération du modèle : {ex.Message}");
                return -1; // Erreur lors de la récupération du modèle
            }
        }

        private static string TruncatePromptBySentenceAsync(string prompt)
        {
            LoggerService.LogInfo("OllamaService.TruncatePromptBySentenceAsync");
            
            prompt = prompt.Trim();
            LoggerService.LogDebug($"Taille avant : {prompt.Length}");

            int reserveReponse = 512 * 3; // Réserve pour la réponse
            var maxTokens = GetTokenMaxAsync();
            var nbrTokens = CountTokensAsync(prompt);
            // Cas simple : le prompt tient dans la limite
            // On garde 512 * 3 de réserve pour la réponse
            if ((nbrTokens + reserveReponse) <= maxTokens)
            {
                LoggerService.LogDebug($"Taille après : {prompt.Length}");
                return prompt;
            }

            // Découpe le texte en phrases (naïvement en utilisant les points)
            var sentences = prompt.Split(new[] { ". ", "? ", "! " }, StringSplitOptions.RemoveEmptyEntries);

            StringBuilder builder = new();
            foreach (var sentence in sentences)
            {
                var next = builder.Length > 0 ? builder + ". " + sentence.Trim() : sentence.Trim();

                var tokenCount = CountTokensAsync(next.ToString());
                if ((tokenCount + reserveReponse) > maxTokens)
                {
                    break; // Trop long, on s'arrête
                }

                builder.Clear();
                builder.Append(next);
            }

            var rep = builder.ToString().Trim();
            LoggerService.LogDebug($"Taille après : {rep.Length}");
            return rep;// + "\n\n[Tronqué pour respecter la limite de contexte]";
        }
        #endregion
    }
}
