using Models;
using Qdrant.Client.Grpc;
using System.Collections;
using System.ComponentModel.DataAnnotations;
using System.Numerics;
using System.Text;
using ToolsServices;


namespace Services;

#region enum des type de documents
public enum Domain
{
    RH,
    Juridique,
    Global,
    Emails,
    CV,
    Technique
}
#endregion

#region Classe statique d'extension pour Domain
public static class DomainExtensions
{
    private static readonly string _CollectionRH = "RH";
    private static readonly string _CollectionJuridique = "Juridique";
    private static readonly string _CollectionGlobal = "Global";
    private static readonly string _CollectionEmails = "Emails";
    private static readonly string _CollectionCV = "CV";
    private static readonly string _CollectionTechnique = "Technique";

    public static List<string> CollectionsName = GetCollectionsName();
    public static List<string> CollectionsNameRestricted = GetCollectionsNameRestricted(false);

    private static List<string> GetCollectionsName()
    {
        List<string> strings = new()
        {
            _CollectionRH,
            _CollectionJuridique,
            _CollectionGlobal,
            _CollectionEmails,
            _CollectionCV,
            _CollectionTechnique
        };
        return strings;
    }

    private static List<string> GetCollectionsNameRestricted(bool isVeryRestricted)
    {
        List<string> strings = new();
        strings.Add(_CollectionGlobal);
        strings.Add(_CollectionTechnique);
        
        if (!isVeryRestricted)
        {
            strings.Add(_CollectionJuridique);
            strings.Add(_CollectionRH);
        }
        return strings;
    }

    /// <summary>
    /// Retourne le nom de collection Qdrant associé à un domaine.
    /// </summary>
    public static string ToCollectionName(this Domain domain) => domain switch
    {
        Domain.RH => _CollectionRH,
        Domain.Juridique => _CollectionJuridique,
        Domain.Global => _CollectionGlobal,
        Domain.Emails => _CollectionEmails,
        Domain.CV => _CollectionCV,
        Domain.Technique => _CollectionTechnique,
        _ => throw new ArgumentOutOfRangeException(nameof(domain), domain, null)
    };

    /// <summary>
    /// Retourne le nom de collection Qdrant associé à un domaine.
    /// </summary>
    public static Domain GetDomainByCollectionName(string CollectionName)
    {
        Domain domain = Domain.Global;

        switch (CollectionName)
        {
            case "RH":
                domain = Domain.RH;
                break;
            case "Juridique":
                domain = Domain.Juridique;
                break;
            case "Global":
                domain = Domain.Global;
                break;
            case "Emails":
                domain = Domain.Emails;
                break;
            case "CV":
                domain = Domain.CV;
                break;
            case "Technique":
                domain = Domain.Technique;
                break;
            
        }
        return domain;
    }

    public static Domain GetDomainByPath(string filePath)
    {
        var dirs = Path.GetDirectoryName(filePath)?
            .Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar,
                   StringSplitOptions.RemoveEmptyEntries);

        if (dirs == null)
            return Domain.Global;

        var collections = GetCollectionsName();

        // On cherche la première correspondance
        var collectionName = dirs.FirstOrDefault(d =>
            collections.Any(c => string.Equals(c, d, StringComparison.OrdinalIgnoreCase)));

        return GetDomainByCollectionName(collectionName ?? "Global");
    }


}
#endregion

#region Classe QdrantService
/// <summary>
/// http://localhost:6333/dashboard
/// </summary>
public class QdrantService
{

    #region Variables
    private static readonly string NomFichierData = FichiersInternesService.ParamsQdrant;// "paramsOllama.txt";

    private readonly Qdrant.Client.QdrantClient _client;
    private const int EmbeddingDim = 768;
    private const Distance DefaultDistance = Distance.Cosine;
    #endregion

    #region Constructeur
    public QdrantService()
    {
        var parametres = LoadParametres();
        //_client = new Qdrant.Client.QdrantClient(parametres!.Qdrant_URL, parametres!.Qdrant_Port, parametres!.Qdrant_IsHttps);
        var httpString = parametres!.Qdrant_IsHttps ? "https://" : "http://";
        _client = new Qdrant.Client.QdrantClient(new Uri($"{httpString}{parametres!.Qdrant_URL}"));
    }
    #endregion

    #region Méthodes publiques

    #region Gestion des Paramètres
    public static ParametresQdrantService? LoadParametres()
    {
        ParametresQdrantService? item = new();
        LoggerService.LogInfo("QdrantService.LoadParametres");
        try
        {
            if (File.Exists(NomFichierData))
            {

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

                if (lignes.Length > 0)
                    item.Qdrant_URL = lignes[0];

                if (lignes.Length > 1)
                {
                    if (bool.TryParse(lignes[1], out bool isHttps))
                        item.Qdrant_IsHttps = isHttps;
                }
                /*
                if (lignes.Length > 2)
                {
                    if (int.TryParse(lignes[1], out int port))
                        item.Qdrant_Port = port;
                }
                */
            }
            return item;

        }
        catch
        {
            return null;
        }

    }

    public static bool SaveParametres(ParametresQdrantService item)
    {
        LoggerService.LogInfo("QdrantService.SaveParametres");
        try
        {
            StringBuilder sb = new();
            sb.AppendLine(item.Qdrant_URL);
            sb.AppendLine(item.Qdrant_IsHttps.ToString());
            //sb.AppendLine(item.Qdrant_Port.ToString());

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

            return true;
        }
        catch
        {
            return false;
        }
    }
    #endregion

    public async Task<bool> IsAlive()
    {
        try
        {
            var health = await _client.HealthAsync();
            return (health != null);
        }
        catch
        {
            LoggerService.LogError("QdrantService.IsAlive - Qdrant n'est pas accessible.");
            return false;
        }
    }

    public async Task InitializeCollectionsAsync(bool isRAZCollections)
    {
        if(isRAZCollections)
            await DeleteCollectionAsync();
        
        foreach (Domain domain in Enum.GetValues(typeof(Domain)))
        {
            await EnsureCollectionAsync(domain);
        }
    }

    public async Task DeleteDocumentInQdrant(Domain domain, string nomFichier)
    {
        var pointId = new PointId { Uuid = Guid.NewGuid().ToString() };
        var collectionName = domain.ToCollectionName();

        // 1. Définir le filtre pour trouver les points avec le même `nom_fichier`
        var filter = new Qdrant.Client.Grpc.Filter
        {
            Must =
            {
                new Condition
                {
                    Field = new FieldCondition
                    {
                        Key = "nom_fichier",
                        Match = new Match { Text = nomFichier }
                    }
                }
            }
        };
        // 2. Chercher les points existants avec ce filtre
        var searchResult = await _client.ScrollAsync(
            collectionName,
            filter: filter,
            limit: 1 // On n'a besoin de vérifier qu'un seul résultat
        );
        // 3. Vérifier si des points ont été trouvés
        if (searchResult.Result.Count > 0)
        {
            LoggerService.LogDebug($"Un ou plusieurs documents avec le nom de fichier '{nomFichier}' ont été trouvés. Suppression...");

            // Si des points existent, on utilise le même filtre pour les supprimer
            await _client.DeleteAsync(
                collectionName,
                filter
            );

            LoggerService.LogDebug("Documents existants supprimés avec succès.");
        }
    }

    public async Task IngestDocument(Domain domain, float[] vector, string nomFichier, string nomChunck, string content, string emailObject = "", string proprietaire = "", string niveauAcces="")
    {
        var pointId = new PointId { Uuid = Guid.NewGuid().ToString() };
        var collectionName = domain.ToCollectionName();
        var vectorObj = new Qdrant.Client.Grpc.Vector();
        vectorObj.Data.Add(vector);

        /*
        // 1. Définir le filtre pour trouver les points avec le même `nom_fichier`
        var filter = new Qdrant.Client.Grpc.Filter
        {
            Must =
            {
                new Condition
                {
                    Field = new FieldCondition
                    {
                        Key = "nom_fichier",
                        Match = new Match { Text = nomFichier }
                    }
                }
            }
        };
        // 2. Chercher les points existants avec ce filtre
        var searchResult = await _client.ScrollAsync(
            collectionName,
            filter: filter,
            limit: 1 // On n'a besoin de vérifier qu'un seul résultat
        );
        // 3. Vérifier si des points ont été trouvés
        if (searchResult.Result.Count > 0)
        {
            LoggerService.LogDebug($"Un ou plusieurs documents avec le nom de fichier '{nomFichier}' ont été trouvés. Suppression...");

            // Si des points existent, on utilise le même filtre pour les supprimer
            await _client.DeleteAsync(
                collectionName,
                filter
            );

            LoggerService.LogDebug("Documents existants supprimés avec succès.");
        }
        */

        await _client.UpsertAsync(collectionName, new[]
        {
            new PointStruct
            {
                Id = pointId,
                Vectors = new Vectors { Vector = vectorObj },
                Payload =
                {
                    ["categorie"] = collectionName,
                    ["nom_fichier"] = nomFichier,
                    ["email_object"] = emailObject,
                    ["nom_chunck"] = nomChunck,
                    ["niveau_acces"] = niveauAcces,
                    ["content"] = content,
                    ["proprietaire"] = proprietaire
                }
            }
        });
    }

    //public async Task<IReadOnlyList<ScoredPoint>> SearchAsync(Domain domain, float[] queryVector, float seuilScore, int topK = 5, string adresseMail = "")
    public async Task<List<SearchResult>> SearchAsync(Domain domain, float[] queryVector, float seuilScore, int topK=5, string adresseMail="")
    {
        var collectionName = domain.ToCollectionName();
        Filter filter = new();
        if (domain == Domain.Emails)
        {
            filter = new Qdrant.Client.Grpc.Filter
            {
                Must = { 
                    new Condition
                    {
                        Field = new FieldCondition
                        {
                            Key = "categorie",
                            Match = new Match { Text = collectionName }
                        }
                    },
                    new Condition
                    {
                        Field = new FieldCondition
                        {
                            Key = "proprietaire",
                            Match = new Match { Text = adresseMail }
                        }
                    }
                }

            };
        }
        else
        {
            filter = new Qdrant.Client.Grpc.Filter
            {
                Must = { new Condition
                {
                    Field = new FieldCondition
                    {
                        Key = "categorie",
                        Match = new Match { Text = collectionName }
                    }
                }}
            };
        }


        var rep = await _client.SearchAsync(collectionName, queryVector, filter, null, (ulong)topK);
        var filteredChunks = rep
            .Where(r => r.Score >= seuilScore)
            .ToArray();

        //
        var resultList = filteredChunks.Select(r => new SearchResult
            {
                Text = r.Payload["content"].StringValue,
                Nom_Fichier = r.Payload["nom_fichier"].StringValue
            }).ToList();
        return resultList;
        //return filteredChunks;

    }
    #endregion

    #region Méthodes privées
    private async Task EnsureCollectionAsync(Domain domain)
    {
        var collectionName = domain.ToCollectionName();
        var existing = await _client.ListCollectionsAsync();

        if (!existing.Contains(collectionName))
        {
            await _client.CreateCollectionAsync(collectionName, new VectorParams
            {
                Size = (ulong)EmbeddingDim,
                Distance = DefaultDistance
            });
        }
    }

    private async Task DeleteCollectionAsync()
    {
        var collections = await _client.ListCollectionsAsync();
        foreach (var col in collections)
        {
            await DeleteCollectionAsync(col);
        }
    }

    private async Task DeleteCollectionAsync(Domain domain)
    {
        var collectionName = domain.ToCollectionName();
        await DeleteCollectionAsync(collectionName);
    }

    private async Task DeleteCollectionAsync(string collectionName)
    {
        await _client.DeleteCollectionAsync(collectionName);
    }
    #endregion
}
#endregion