using ClosedXML;
using Google.Protobuf.WellKnownTypes;
using MailKit;
using MailKit.Net.Imap;
using MailKit.Search;
using MailKit.Security;
using MimeKit;
using Org.BouncyCastle.Security;
using Services.ReActAgent;
using System.Text.Json;
using ToolsServices;

namespace Services
{
    #region Classe static annexe JsonDb
    public static class JsonDb
    {
        static string FullFileName = FichiersInternesService.ListeMailsSend;

        public static ThreadStore Load()
        {
            if (!File.Exists(FullFileName)) return new ThreadStore();
            var json = File.ReadAllText(FullFileName);
            return JsonSerializer.Deserialize<ThreadStore>(json) ?? new ThreadStore();
        }

        public static bool Save(ThreadItem thread)
        {
            ThreadStore data;

            // Charger fichier existant
            if (File.Exists(FullFileName))
            {
                string json = File.ReadAllText(FullFileName);
                data = JsonSerializer.Deserialize<ThreadStore>(json, new JsonSerializerOptions { WriteIndented = true }) ?? new ThreadStore();
            }
            else
            {
                data = new ThreadStore();
            }

            // Chercher si le thread existe déjà
            var existing = data.Threads.FirstOrDefault(t => t.Id == thread.Id);
            if (existing != null)
            {
                int index = data.Threads.IndexOf(existing);
                data.Threads[index] = thread; // remplacement
            }
            else
            {
                data.Threads.Add(thread); // ajout
            }

            // Sauvegarder
            string updatedJson = JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true });
            File.WriteAllText(FullFileName, updatedJson);
            return true;
        }

        public static void Save(ThreadStore store)
        {
            Directory.CreateDirectory(Path.GetDirectoryName(FullFileName)!);
            var json = JsonSerializer.Serialize(store, new JsonSerializerOptions { WriteIndented = true });
            var tmp = FullFileName + ".tmp";
            File.WriteAllText(tmp, json);
            File.Move(tmp, FullFileName, true);
        }
    }
    #endregion

    #region Classe static EmailSendService
    public static class EmailSendService
    {
        #region Variables
        private static ReActAgent.ReActAgent _ReActAgent = new();
        private static List<IMailFolder> ListInboxFolders = new List<IMailFolder>();
        private static CompteMailUser ParametreUser = new();
        #endregion

        #region Méthodes publiques
        public static int NbRelancesEnAttente()
        {
            var lstReponsesEnAttente = LstRelancesEnAttente();
            return lstReponsesEnAttente.Count;
        }

        public static ThreadStore ChargerDonnees()
        {
            var fileMailsSend = FichiersInternesService.ListeMailsSend;
            ThreadStore store = new ThreadStore();
            if (File.Exists(fileMailsSend))
            {
                var json = System.IO.File.ReadAllText(fileMailsSend);
                store = JsonSerializer.Deserialize<ThreadStore>(json) ?? new ThreadStore();
            }
            return store;
        }

        public static List<ThreadItem> LstRelancesEnAttente()
        {
            var store = JsonDb.Load();
            var lstReponsesEnAttente = store.Threads
                .Where(t => !t.SubjectClosed)      // Sujet non fermé
                .Where(t => t.IsOverdue)           // En retard
                .ToList();
            return lstReponsesEnAttente;
        }

        private class MessageDossier
        {
            public UniqueId uid { get; set; }
            public MimeMessage mimeMessage  { get; set; } =new();
            public IMailFolder? dossier { get; set; }
            public string dossierFullname { get; set; } = "";
            public string direction { get; set; } = "";
        }
        
        public static async Task<(bool, string, int)> RunOnce(CompteMailUser parametreUser)
        {
            var nbMsgSentSaved = 0;
            var nbMsgInboxSaved = 0;
            var nbRelancesCreated = 0;
            var nbConvReOuvertes = 0;

            ParametreUser = parametreUser;
            try
            {
                LoggerService.LogInfo("EmailSendService.RunOnce");
                var dateFilter = DateTime.UtcNow.AddDays(-1 * ParametreUser.DelaySentRecup);

                var parametresMail = EmailService.LoadParametres();
                if(parametresMail == null)
                {
                    return (false, "Paramètres email introuvable",0);
                }
                
                var store = JsonDb.Load();

                using var client = new ImapClient();
                await client.ConnectAsync(parametresMail.ServeurImap, parametresMail.ServeurImapPort, SecureSocketOptions.SslOnConnect);
                await client.AuthenticateAsync(parametresMail.UserAdresse, parametresMail.UserMotPasse);

                ListInboxFolders.Clear();
                var personal = client.GetFolder(client.PersonalNamespaces[0]);
                await ProcessFolder(personal);

                // Récupérer le dossier "Sent"
                var sentFolder = await personal.GetSubfolderAsync("Sent");

                // Si ça ne marche pas (par exemple dossier en français "Envoyés") :
                if (sentFolder == null || !sentFolder.Exists)
                {
                    var allFolders = await personal.GetSubfoldersAsync(false);
                    sentFolder = allFolders.FirstOrDefault(f => f.Name.Equals("Sent", StringComparison.OrdinalIgnoreCase)
                                                              || f.Name.Equals("Envoyés", StringComparison.OrdinalIgnoreCase));
                }

                // Si on a bien trouvé un dossier "Sent" ou équivalent
                if (sentFolder == null)
                {
                    return (false, "Dossier mails envoyés introuvables",0);
                }
                var sent = sentFolder;



                await sent.OpenAsync(MailKit.FolderAccess.ReadOnly);
                //var uidsSent = await sent.SearchAsync(SearchQuery.All); 
                


                

                var uidsSent = await sent.SearchAsync(SearchQuery.DeliveredAfter(dateFilter));

                List<MessageDossier> lst = new();
                foreach (var uid in uidsSent)
                {
                    var msg = await sent.GetMessageAsync(uid);
                    MessageDossier nouveau = new();
                    nouveau.uid = uid;
                    nouveau.mimeMessage = msg;
                    nouveau.dossier = sent;
                    nouveau.dossierFullname = sentFolder.FullName;
                    nouveau.direction = "out";
                    lst.Add(nouveau);
                }

                foreach (var oneFolder in ListInboxFolders)
                {
                    var inbox = oneFolder!;// client.Inbox;
                    await inbox.OpenAsync(MailKit.FolderAccess.ReadWrite);
                    //var uidsInbox = await inbox.SearchAsync(SearchQuery.All);
                    var uidsInbox = await inbox.SearchAsync(SearchQuery.DeliveredAfter(dateFilter));
                    foreach (var uid in uidsInbox)
                    {
                        var msg = await inbox.GetMessageAsync(uid);
                        MessageDossier nouveau = new();
                        nouveau.uid = uid;
                        nouveau.mimeMessage = msg;
                        nouveau.dossier = inbox;
                        nouveau.dossierFullname = inbox.FullName;
                        nouveau.direction = "in";

                        lst.Add(nouveau);
                    }
                }
                lst = lst.OrderBy(e => e.mimeMessage.Date).ToList();
                foreach (var message in lst)
                {
                    var bCreateMsg = await AddMessage(message.uid, store, message.mimeMessage, message.direction,message.dossierFullname, parametreUser.OverdueDaysSent);
                    if (bCreateMsg)
                        nbMsgSentSaved++;
                    JsonDb.Save(store);
                }



                /*
                foreach (var uid in uidsSent)
                {
                    var msg = await sent.GetMessageAsync(uid);

                    var bCreateMsg = await AddMessage(uid, store, msg, "out", sentFolder.FullName, parametreUser.OverdueDaysSent);
                    if (bCreateMsg)
                        nbMsgSentSaved++;
                    JsonDb.Save(store);
                }

                // Tous les dossiers Inbox

                foreach (var oneFolder in ListInboxFolders)
                {
                    var inbox = oneFolder!;// client.Inbox;
                    await inbox.OpenAsync(MailKit.FolderAccess.ReadWrite);
                    //var uidsInbox = await inbox.SearchAsync(SearchQuery.All);
                    var uidsInbox = await inbox.SearchAsync(SearchQuery.DeliveredAfter(dateFilter));
                    foreach (var uid in uidsInbox)
                    {
                        var msg = await inbox.GetMessageAsync(uid);

                        var bCreateMsg = await AddMessage(uid, store, msg, "in", oneFolder.FullName, parametreUser.OverdueDaysSent);
                        if (bCreateMsg)
                            nbMsgInboxSaved++;
                        JsonDb.Save(store);
                    }
                }
                */

                await client.DisconnectAsync(true);

                // Analyse IA pour déterminer les relances

                foreach (var thread in store.Threads)
                {
                    // On prend le dernier mail sortant
                    var lastOut = thread.Messages
                        .FindLast(m => m.Direction == "out");

                    var theLast = thread.Messages.FindLast(m => m.Direction == "out" || m.Direction == "in");
                    thread.DateLastMessage = theLast!.Date;

                    if (lastOut != null && lastOut.RequiresResponse)
                    {
                        // On ne régénère pas une relance si on l'a déjà faite pour ce mail
                        bool alreadyFollowedUp = (thread.LastFollowUpForMessageId == lastOut.Id);
                        if (!alreadyFollowedUp)
                        {
                            // Dernier mail entrant
                            var lastIn = thread.Messages
                                .Where(m => m.Direction == "in")
                                .OrderBy(m => m.Date.ToUniversalTime())
                                .LastOrDefault();

                            // relance si
                            bool needsFollowUp = (
                                lastIn == null // pas de réponse
                                || (lastIn.Date.ToUniversalTime() < lastOut.Date.ToUniversalTime()) // pas de réponse APRES le dernier message
                                || (lastIn.Date.ToUniversalTime() >= lastOut.Date.ToUniversalTime() && !lastIn.CoversAllPoints) // réponse APRES le dernier message MAIS tous les points ne sont pas couverts
                                );

                            if (needsFollowUp)
                            {
                                //if(thread.FollowUpDraft == null || thread.FollowUpDraft == "")
                                thread.FollowUpDraft = await GenerateFollowUp(thread);
                                // Même si cloturée, on ré-ouvre la conversation
                                if(thread.SubjectClosed == true)
                                {
                                    nbConvReOuvertes++;
                                    thread.SubjectClosed = false;
                                }

                                thread.LastFollowUpForMessageId = lastOut.Id;
                                nbRelancesCreated++;
                            }
                        }
                    }
                    JsonDb.Save(store);
                }

                JsonDb.Save(store);
                LoggerService.LogDebug($"EmailSendService.RunOnce : Nbre messages envoyés sauvegardés : {nbMsgSentSaved}");
                LoggerService.LogDebug($"EmailSendService.RunOnce : Nbre messages reçus sauvegardés   : {nbMsgInboxSaved}");
                LoggerService.LogDebug($"EmailSendService.RunOnce : Nbre relances générées            : {nbRelancesCreated}");
                LoggerService.LogDebug($"EmailSendService.RunOnce : Nbre coversation réouvertes       : {nbConvReOuvertes}");
                LoggerService.LogDebug("EmailSendService.RunOnce : terminé");
                return (true, "", nbConvReOuvertes);
            }
            catch(Exception ex)
            {
                LoggerService.LogError($"EmailSendService.RunOnce : {ex.Message}"); 
                LoggerService.LogDebug($"EmailSendService.RunOnce : Nbre messages envoyés sauvegardés : {nbMsgSentSaved}");
                LoggerService.LogDebug($"EmailSendService.RunOnce : Nbre messages reçus sauvegardés   : {nbMsgInboxSaved}");
                LoggerService.LogDebug($"EmailSendService.RunOnce : Nbre relances générées            : {nbRelancesCreated}");
                LoggerService.LogDebug($"EmailSendService.RunOnce : Nbre coversation réouvertes       : {nbConvReOuvertes}");

                return (false, $"EmailSendService.RunOnce : {ex.Message}", nbConvReOuvertes);
            }
        }

        public static bool Save(ThreadItem thread)
        {
            LoggerService.LogInfo("EmailSendService.Save");
            return JsonDb.Save(thread);
        }

        public async static Task<bool> ArchiverRestaurerItem(ThreadItemMini thread)
        {
            await Task.Delay(1);
            var store = ChargerDonnees();
            var tts = store.Threads;
            ThreadItem? t = tts!.Where(e => e.Id == thread.Id)!.FirstOrDefault();
            if (t == null)
            {
                return false;
            }
            t.SubjectClosed = !t.SubjectClosed;
            Save(t);
            return true;
        }

        public async static Task<bool> SaveSendRelanceItem(ThreadItemMini thread)
        {
            await Task.Delay(1);
            var store = ChargerDonnees();
            var tts = store.Threads;
            ThreadItem? t = tts!.Where(e=>e.Id == thread.Id)!.FirstOrDefault();
            if (t == null)
            {
                return false;
            }
            t.FollowUpDraft = thread.FollowUpDraft;
            Save(t);
            return true;
        }
        public async static Task<bool> SaveAsync(ThreadItem thread)
        {
            await Task.Delay(1);
            LoggerService.LogInfo("EmailSendService.Save");
            return JsonDb.Save(thread);
        }

        public static void AllConversationsChangeStatus(bool isClosed)
        {
            var store = JsonDb.Load();
            foreach(var conversation in store.Threads)
            {
                conversation.SubjectClosed = isClosed;
            }
            JsonDb.Save(store);

        }
        #endregion

        #region Méthodes privées
        private static async Task ProcessFolder(IMailFolder folder)
        {
            var excludedFolders = new[] { "Junk", "spam", "Sent", "Trash", "Drafts" };
            if (Array.Exists(excludedFolders, f => string.Equals(f, folder.Name, StringComparison.OrdinalIgnoreCase)))
                return;


            if (folder.Attributes.HasFlag(FolderAttributes.NoSelect))
            {
                LoggerService.LogDebug($"📂 {folder.FullName} (conteneur uniquement, pas de mails)");
            }
            else
            {
                ListInboxFolders.Add(folder);
                LoggerService.LogDebug($"📂 {folder.FullName}");
            }

            // Parcours récursif des sous-dossiers
            foreach (var sub in folder.GetSubfolders())
            {
                await ProcessFolder(sub);
            }
        }

        private static async Task<bool> AddMessage(UniqueId uid, ThreadStore store, MimeMessage msg, string direction, string folder, int overdueDays)
        {
            //var folder = (direction == "out" ? "Sent" : "Inbox");
            var subject = msg.Subject ?? "(sans objet)";
            var body = msg.TextBody ?? msg.HtmlBody ?? "";
            var messageId = msg.MessageId ?? Guid.NewGuid().ToString();
            var references = msg.References?.ToArray() ?? Array.Empty<string>();
            var inReplyTo = msg.InReplyTo;

            // Trouve un thread existant basé sur Reply-To
            var thread = (ThreadItem?)null;

            if (messageId != null)
            {
                // On cherche le message par son ID (pour ne pas créer un doublon)
                thread = store.Threads.Find(t =>
                        t.Messages.Exists(m => m.Id == messageId)
                    );
            }
            if (thread == null && inReplyTo != null)
            {
                // On cherche si une reponse existe déjà pour mettre ce message dans le même thread
                thread = store.Threads.Find(t =>
                        t.Messages.Exists(m => m.Id == inReplyTo)
                    );
            }
            if (thread == null)
            {
                thread = store.Threads.Find(t =>
                t.Messages.Exists(m => m.InReplyTo == messageId)
                );
            }
            // Vérifie si déjà importé via UID
            /*
            if (thread != null && thread.Messages.Exists(m => m.Folder == folder && m.Uid == uid))
                return false;
            */
            if (thread != null)
            {
                // Si le message existe déjà dans le thread, il a peut être été déplacé de dossier
                if (thread.Messages.Exists(m => m.Id == msg.MessageId))
                {
                    if (thread.Messages.Exists(m => m.Folder == folder && m.Id == msg.MessageId))
                    {
                        // existe et n'a pas été déplacé : on arrête
                        return false;
                    }
                    else
                    {
                        // a été déplacé --> mettre à jour le folder et uid
                        var msgExist = thread.Messages.Find(m => m.Id == msg.MessageId);
                        if (msgExist != null)
                        {
                            msgExist.Folder = folder;
                            //msgExist.Uid = uid;
                            return true;
                        }
                        else
                        {
                            // cas impossible ou msgExist est null
                            return false;
                        }
                    }
                }
            }

            if (thread == null)
            {
                thread = new ThreadItem(overdueDays) { Subject = subject, DateMessage = msg.Date };
                store.Threads.Add(thread);
            }

            var analysis = await AnalyzeMessage(body, direction);

            thread.Messages.Add(new MailMessageItem
            {
                Id = msg.MessageId!,
                InReplyTo = msg.InReplyTo,
                //UidString = uid.ToString(),
                Folder = folder,
                Subject = subject,
                From = msg.From.Mailboxes.FirstOrDefault()?.Address ?? "",
                To = string.Join("; ", msg.To.Mailboxes.Select(m => m.Address)),
                Direction = direction,
                Date = msg.Date,
                Body = body,
                RequiresResponse = analysis.requiresResponse,
                CoversAllPoints = analysis.coversAllPoints
            });
            return true;
        }

        private static async Task<(bool requiresResponse, bool coversAllPoints)> AnalyzeMessage(string body, string direction)
        {
            try
            {
                var prompt = "";

                if (direction == "out")
                {
                    //prompt = $"Réponds uniquement par oui ou non. Une réponse est-elle nécessaire à ce mail\nDébut du mail:\n{body} \n- fin du mail";
                    prompt = PromptService.GetPrompt(PromptService.ePrompt.EmailSendService_AnalyserMail_BesoinReponse, body);
                }
                else
                {
                    //prompt = $"Réponds uniquement par oui ou non. Tous les points de ce mail ont-ils été traités\nDébut du mail:\n{body} \n- fin du mail";
                    prompt = PromptService.GetPrompt(PromptService.ePrompt.EmailSendService_AnalyserMail_AllPointsChecked, body);
                }

                var (reponseOllama, m) = await _ReActAgent.AppelerLLMAsync(ModelsUseCases.TypeUseCase.AnalyseMails, false, prompt, "Analyse mails sortants");

                if(reponseOllama == null || reponseOllama.ToString().Trim().Length ==0)
                {
                    return (true, true);
                }
                if (direction == "out")
                {
                    var rep = (reponseOllama.ToString().ToLower().Contains("oui"));
                    return (rep, false);
                }
                else
                {
                    var rep = (reponseOllama.ToString().ToLower().Contains("oui"));
                    return (false, rep);
                }
            }
            catch
            {
                return (true, true);
            }
        }

        private static async Task<string> GenerateFollowUp(ThreadItem thread)
        {
            var conversation = string.Join("\n\n", thread.Messages.ConvertAll(m => $"[{m.Direction}] {m.Body}"));
            //var prompt = $"Génère un brouillon de mail en langue française de relance poli basé sur cette conversation :\n{conversation}";
            var prompt = PromptService.GetPrompt(PromptService.ePrompt.EmailSendService_AnalyserMail_Relance, thread.Subject, conversation, ParametreUser.UserNomPrenom, ParametreUser.UserRole, ParametreUser.UserEntreprise);

            var (reponseOllama, _) = await _ReActAgent.AppelerLLMAsync(ModelsUseCases.TypeUseCase.AnalyseMails, false, prompt, "Génération mail de relance");
            return reponseOllama;
        }
        #endregion
    }
    #endregion
}
