// Questo file fa parte di MyTown.
// MyTown è software libero: puoi ridistribuirlo e/o modificarlo secondo
// i termini della GNU Affero General Public License come pubblicata dalla
// Free Software Foundation, versione 3.
// Distribuito senza ALCUNA GARANZIA; vedi la GNU AGPL v3 per i dettagli.
// Copyright (C) Kondor Consulting & Services S.r.l.
// SPDX-License-Identifier: AGPL-3.0-or-later

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using iTextSharp.text.pdf;
using iTextSharp.text.pdf.parser;
using Newtonsoft.Json.Linq;

namespace KondorCS.ScheduleLibrary.AIFileProcessor
{
    /// <summary>
    /// Handler per l'estrazione automatica di dati da PDF generici (non UNILAV) con testo.
    /// Tenta di matchare il testo estratto con i campi del form definiti in ft_form.
    /// Se il match è completo, restituisce i dati senza chiamare l'AI.
    /// Se il match è parziale, restituisce i dati estratti e indica quali campi mancanti servono all'AI.
    /// Se non c'è match, delega completamente all'AI.
    /// </summary>
    public class GenericPdfHandler
    {
        private bool _debug;

        public GenericPdfHandler(bool debug = false)
        {
            _debug = debug;
        }

        /// <summary>
        /// Determina se un file è un PDF generico con testo (escludendo UNILAV).
        /// </summary>
        public bool IsProcessablePdf(FileInfo file)
        {
            if (file == null || !file.Exists)
                return false;

            // Verifica estensione PDF
            if (!file.Extension.Equals(".pdf", StringComparison.OrdinalIgnoreCase))
                return false;

            // Esclude se è un PDF UNILAV (nome file)
            string fileName = file.Name.ToLower();
            if (fileName.Contains("unilav") || fileName.Contains("comunicazione"))
                return false;

            // Verifica che contenga testo estraibile
            try
            {
                string text = ExtractTextFromPdf(file.FullName);

                // Deve contenere almeno 50 caratteri di testo per essere considerato processabile
                return !string.IsNullOrWhiteSpace(text) && text.Length >= 50;
            }
            catch
            {
                // Se l'estrazione fallisce, non è processabile
                return false;
            }
        }

        /// <summary>
        /// Estrae il testo completo da un PDF.
        /// </summary>
        private string ExtractTextFromPdf(string pdfPath)
        {
            StringBuilder text = new StringBuilder();

            using (PdfReader reader = new PdfReader(pdfPath))
            {
                for (int page = 1; page <= reader.NumberOfPages; page++)
                {
                    ITextExtractionStrategy strategy = new SimpleTextExtractionStrategy();
                    string pageText = PdfTextExtractor.GetTextFromPage(reader, page, strategy);
                    text.AppendLine(pageText);
                }
            }

            return text.ToString();
        }

        /// <summary>
        /// Tenta di estrarre dati dal PDF e matchare con i campi del form.
        /// Restituisce un oggetto che indica se il match è completo, parziale o assente.
        /// </summary>
        /// <param name="file">File PDF da elaborare</param>
        /// <param name="jsonFields">JSON con la struttura dei campi (ft_form)</param>
        /// <returns>Risultato dell'estrazione con percentuale di match e dati estratti</returns>
        public PdfExtractionResult TryExtractAndMatch(FileInfo file, string jsonFields)
        {
            try
            {
                if (_debug)
                    Console.WriteLine($"[GENERIC PDF] Inizio estrazione PDF: {file.Name}");

                // Estrae il testo dal PDF
                string fullText = ExtractTextFromPdf(file.FullName);

                if (_debug)
                {
                    Console.WriteLine($"[GENERIC PDF] Testo estratto: {fullText.Length} caratteri");
                    Console.WriteLine($"[GENERIC PDF] Prime 500 caratteri: {fullText.Substring(0, Math.Min(500, fullText.Length))}");
                }

                // Parse del JSON dei campi
                JObject fieldsObj = JObject.Parse(jsonFields);
                JArray fieldsArray = fieldsObj["fields"] as JArray;

                if (fieldsArray == null || fieldsArray.Count == 0)
                {
                    if (_debug)
                        Console.WriteLine($"[GENERIC PDF] Nessun campo da matchare trovato nel JSON");
                    return new PdfExtractionResult
                    {
                        MatchPercentage = 0,
                        ExtractedData = new JObject(),
                        MissingFields = new List<string>()
                    };
                }

                // Tenta di matchare i campi
                JObject extractedData = new JObject();
                List<string> missingFields = new List<string>();
                List<string> missingRequiredFields = new List<string>();
                int totalFields = 0;
                int matchedFields = 0;
                int totalRequiredFields = 0;
                int matchedRequiredFields = 0;

                foreach (JObject field in fieldsArray)
                {
                    string fieldName = field["name"]?.ToString();
                    string fieldLabel = field["label"]?.ToString();
                    int fieldType = field["type"]?.ToObject<int>() ?? 1;
                    bool isRequired = field["required"]?.ToObject<bool>() ?? false;

                    if (string.IsNullOrWhiteSpace(fieldName))
                        continue;

                    totalFields++;
                    if (isRequired)
                        totalRequiredFields++;

                    // Tenta di estrarre il valore per questo campo
                    string extractedValue = TryExtractFieldValue(fullText, fieldLabel, fieldName, fieldType);

                    if (!string.IsNullOrWhiteSpace(extractedValue))
                    {
                        // Campo trovato
                        field["value"] = extractedValue;
                        matchedFields++;

                        if (isRequired)
                            matchedRequiredFields++;

                        if (_debug)
                            Console.WriteLine($"[GENERIC PDF] ✓ Campo '{fieldName}'{(isRequired ? " (obbligatorio)" : "")} trovato: '{extractedValue}'");
                    }
                    else
                    {
                        // Campo non trovato
                        missingFields.Add(fieldName);

                        if (isRequired)
                            missingRequiredFields.Add(fieldName);

                        if (_debug)
                            Console.WriteLine($"[GENERIC PDF] ✗ Campo '{fieldName}'{(isRequired ? " (obbligatorio)" : "")} NON trovato");
                    }

                    extractedData[fieldName] = field;
                }

                // Calcola percentuale di match
                double matchPercentage = totalFields > 0 ? (double)matchedFields / totalFields * 100 : 0;
                bool allRequiredFieldsFound = totalRequiredFields > 0 && missingRequiredFields.Count == 0;

                if (_debug)
                {
                    Console.WriteLine($"[GENERIC PDF] Match: {matchedFields}/{totalFields} campi ({matchPercentage:F1}%)");
                    Console.WriteLine($"[GENERIC PDF] Campi obbligatori: {matchedRequiredFields}/{totalRequiredFields}");
                    if (missingRequiredFields.Count > 0)
                        Console.WriteLine($"[GENERIC PDF] Campi obbligatori mancanti: {string.Join(", ", missingRequiredFields)}");
                    Console.WriteLine($"[GENERIC PDF] Campi mancanti totali: {string.Join(", ", missingFields)}");
                }

                // Ricostruisce il JSON nel formato corretto
                JObject resultJson = new JObject();
                resultJson["fields"] = new JArray(extractedData.Properties().Select(p => p.Value));

                return new PdfExtractionResult
                {
                    MatchPercentage = matchPercentage,
                    ExtractedData = resultJson,
                    MissingFields = missingFields,
                    MissingRequiredFields = missingRequiredFields,
                    AllRequiredFieldsFound = allRequiredFieldsFound,
                    MatchedFieldsCount = matchedFields,
                    TotalFieldsCount = totalFields
                };
            }
            catch (Exception ex)
            {
                if (_debug)
                    Console.WriteLine($"[GENERIC PDF ERROR] Errore durante l'estrazione: {ex.Message}");

                return new PdfExtractionResult
                {
                    MatchPercentage = 0,
                    ExtractedData = new JObject(),
                    MissingFields = new List<string>(),
                    ErrorMessage = ex.Message
                };
            }
        }

        /// <summary>
        /// Tenta di estrarre il valore di un campo specifico dal testo del PDF.
        /// Utilizza pattern euristici per trovare il valore basandosi su label e name del campo.
        /// </summary>
        private string TryExtractFieldValue(string fullText, string fieldLabel, string fieldName, int fieldType)
        {
            // Normalizza il testo per la ricerca
            string normalizedText = fullText;

            // Crea pattern di ricerca basati su label e name
            List<string> searchPatterns = new List<string>();

            if (!string.IsNullOrWhiteSpace(fieldLabel))
            {
                // Normalizza la label: rimuove caratteri speciali
                string normalizedLabel = Regex.Replace(fieldLabel, @"[^\w\sàèéìòù]", "", RegexOptions.IgnoreCase);
                searchPatterns.Add(normalizedLabel);

                // Varianti della label
                searchPatterns.Add(fieldLabel); // Label originale
            }

            if (!string.IsNullOrWhiteSpace(fieldName))
            {
                // Converte camelCase in parole separate
                string nameWithSpaces = Regex.Replace(fieldName, "([a-z])([A-Z])", "$1 $2");
                searchPatterns.Add(nameWithSpaces);
            }

            // PRIMA: Pattern specifici per documenti narrativi (più precisi)
            string narrativeValue = TryExtractNarrativePattern(fullText, fieldLabel, fieldName, fieldType);
            if (!string.IsNullOrWhiteSpace(narrativeValue))
                return narrativeValue;

            // SECONDA: Pattern strutturati (tabelle, form)
            foreach (string pattern in searchPatterns.Where(p => !string.IsNullOrWhiteSpace(p)))
            {
                // Pattern 1: "Label: Valore" o "Label Valore"
                var match1 = Regex.Match(normalizedText,
                    $@"{Regex.Escape(pattern)}\s*:?\s+([A-Z0-9][^\r\n]{{1,100}})",
                    RegexOptions.IgnoreCase);

                if (match1.Success)
                {
                    string value = match1.Groups[1].Value.Trim();
                    return ProcessExtractedValue(value, fieldType);
                }

                // Pattern 2: tabellare "Label    Valore" (multiple spaces)
                var match2 = Regex.Match(normalizedText,
                    $@"{Regex.Escape(pattern)}\s{{2,}}([A-Z0-9][^\r\n]{{1,100}})",
                    RegexOptions.IgnoreCase);

                if (match2.Success)
                {
                    string value = match2.Groups[1].Value.Trim();
                    return ProcessExtractedValue(value, fieldType);
                }

                // Pattern 3: "Label" seguito da valore sulla riga successiva
                var match3 = Regex.Match(normalizedText,
                    $@"{Regex.Escape(pattern)}\s*[\r\n]+\s*([A-Z0-9][^\r\n]{{1,100}})",
                    RegexOptions.IgnoreCase);

                if (match3.Success)
                {
                    string value = match3.Groups[1].Value.Trim();
                    return ProcessExtractedValue(value, fieldType);
                }
            }

            // Nessun match trovato
            return null;
        }

        /// <summary>
        /// Tenta di estrarre valori da testo narrativo usando pattern specifici.
        /// Es: "al dipendente Mario Rossi nato il 01/01/1980"
        /// </summary>
        private string TryExtractNarrativePattern(string fullText, string fieldLabel, string fieldName, int fieldType)
        {
            string normalizedFieldName = fieldName?.ToLower() ?? "";
            string normalizedLabel = fieldLabel?.ToLower() ?? "";

            // CODICE FISCALE
            if (normalizedFieldName.Contains("codicefiscale") || normalizedFieldName.Contains("fiscale") ||
                normalizedLabel.Contains("codice fiscale") || normalizedLabel.Contains("c.f"))
            {
                // Pattern: "c.f." o "C.F." o "codice fiscale" seguito da codice
                var match = Regex.Match(fullText, @"(?:c\.f\.|codice fiscale|CF|fiscale)\s*[:.]?\s*([A-Z]{6}\d{2}[A-Z]\d{2}[A-Z]\d{3}[A-Z])", RegexOptions.IgnoreCase);
                if (match.Success)
                    return match.Groups[1].Value.ToUpper();
            }

            // NOME E COGNOME (insieme)
            if ((normalizedFieldName.Contains("nome") && normalizedFieldName.Contains("cognome")) ||
                normalizedLabel.Contains("nome completo") || normalizedLabel.Contains("nome e cognome"))
            {
                // Pattern: "al dipendente" o "del dipendente" seguito da nome cognome
                var match = Regex.Match(fullText, @"(?:al|del)\s+dipendente\s+([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+)", RegexOptions.IgnoreCase);
                if (match.Success)
                    return match.Groups[1].Value;
            }

            // COGNOME
            if (normalizedFieldName == "cognome" || normalizedLabel == "cognome")
            {
                // Pattern: "al dipendente Cognome Nome" → prende solo il cognome (prima parola)
                var match = Regex.Match(fullText, @"(?:al|del)\s+dipendente\s+([A-Z][a-z]+)", RegexOptions.IgnoreCase);
                if (match.Success)
                    return match.Groups[1].Value;
            }

            // NOME
            if (normalizedFieldName == "nome" || normalizedLabel == "nome")
            {
                // Pattern: "al dipendente Cognome Nome" → prende solo il nome (seconda parola)
                var match = Regex.Match(fullText, @"(?:al|del)\s+dipendente\s+[A-Z][a-z]+\s+([A-Z][a-z]+)", RegexOptions.IgnoreCase);
                if (match.Success)
                    return match.Groups[1].Value;
            }

            // DATA DI NASCITA
            if (normalizedFieldName.Contains("datanascita") || normalizedFieldName.Contains("data") && normalizedFieldName.Contains("nascita") ||
                normalizedLabel.Contains("data di nascita") || normalizedLabel.Contains("nato il"))
            {
                // Pattern: "nato il" o "nata il" o "nato/a il" seguito da data
                var match = Regex.Match(fullText, @"nat[oa](?:/a)?\s+(?:il|a\s+.+?\s+il)\s+(\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4})", RegexOptions.IgnoreCase);
                if (match.Success)
                    return match.Groups[1].Value;
            }

            // LUOGO/COMUNE DI NASCITA
            if (normalizedFieldName.Contains("luogonascita") || normalizedFieldName.Contains("comunenascita") ||
                normalizedLabel.Contains("luogo di nascita") || normalizedLabel.Contains("comune di nascita") || normalizedLabel.Contains("nato a"))
            {
                // Pattern: "nato a Città" prima di "(EE)" o "il"
                var match = Regex.Match(fullText, @"nat[oa]\s+a\s+([A-Z][a-zA-Z\s]+?)(?:\s+\(|il)", RegexOptions.IgnoreCase);
                if (match.Success)
                    return match.Groups[1].Value.Trim();
            }

            // INDIRIZZO/RESIDENZA
            if (normalizedFieldName.Contains("indirizzo") || normalizedFieldName.Contains("residenza") ||
                normalizedLabel.Contains("indirizzo") || normalizedLabel.Contains("residente"))
            {
                // Pattern: "residente in Via/Piazza..." fino a CAP o comune
                var match = Regex.Match(fullText, @"residente\s+in\s+([A-Z][^,\d]{3,}?,\s*\d+)", RegexOptions.IgnoreCase);
                if (match.Success)
                    return match.Groups[1].Value.Trim();
            }

            // LIVELLO
            if (normalizedFieldName.Contains("livello") || normalizedLabel.Contains("livello"))
            {
                // Pattern: "N° livello" o "N livello"
                var match = Regex.Match(fullText, @"(\d+)°?\s*livello", RegexOptions.IgnoreCase);
                if (match.Success)
                    return match.Groups[1].Value;
            }

            // MANSIONE/QUALIFICA
            if (normalizedFieldName.Contains("mansione") || normalizedFieldName.Contains("qualifica") ||
                normalizedLabel.Contains("mansione") || normalizedLabel.Contains("qualifica"))
            {
                // Pattern: "mansione di" seguito da parola
                var match = Regex.Match(fullText, @"mansione\s+di\s+([A-Z][a-z]+)", RegexOptions.IgnoreCase);
                if (match.Success)
                    return match.Groups[1].Value;
            }

            // DATA DECORRENZA/INIZIO
            if (normalizedFieldName.Contains("datadecorrenza") || normalizedFieldName.Contains("datainizio") ||
                normalizedFieldName.Contains("dal") ||
                normalizedLabel.Contains("decorrenza") || normalizedLabel.Contains("dal") || normalizedLabel.Contains("data inizio"))
            {
                // Pattern: "dal" seguito da data
                var match = Regex.Match(fullText, @"(?:dal|a far data dal)\s+(\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4})", RegexOptions.IgnoreCase);
                if (match.Success)
                    return match.Groups[1].Value;
            }

            return null;
        }

        /// <summary>
        /// Processa il valore estratto in base al tipo di campo.
        /// </summary>
        private string ProcessExtractedValue(string value, int fieldType)
        {
            if (string.IsNullOrWhiteSpace(value))
                return null;

            // Pulisce il valore da spazi multipli
            value = Regex.Replace(value, @"\s+", " ").Trim();

            // Rimuove eventuali etichette successive che potrebbero essere state catturate
            // (es: "ROSSI Cognome MARIO" -> "ROSSI")
            var nextLabelMatch = Regex.Match(value, @"^([^\s]+(?:\s+[^\s]+)?)\s+[A-Z][a-zA-Z\s]+$");
            if (nextLabelMatch.Success)
            {
                value = nextLabelMatch.Groups[1].Value.Trim();
            }

            // Type 2: Date - tenta di formattare
            if (fieldType == 2)
            {
                return FormatDate(value);
            }

            return value;
        }

        /// <summary>
        /// Formatta una data da formato italiano (dd/MM/yyyy) a ISO (yyyy-MM-dd).
        /// </summary>
        private string FormatDate(string value)
        {
            if (string.IsNullOrWhiteSpace(value))
                return value;

            // Estrae solo la parte della data se c'è anche l'orario
            string datePart = value.Split(' ')[0];

            // Verifica se è in formato dd/MM/yyyy
            var parts = datePart.Split('/');
            if (parts.Length == 3)
            {
                string day = parts[0].Trim();
                string month = parts[1].Trim();
                string year = parts[2].Trim();

                if (int.TryParse(day, out int d) &&
                    int.TryParse(month, out int m) &&
                    int.TryParse(year, out int y))
                {
                    if (d >= 1 && d <= 31 && m >= 1 && m <= 12 && y >= 1900 && y <= 2100)
                    {
                        return $"{year}-{month.PadLeft(2, '0')}-{day.PadLeft(2, '0')}";
                    }
                }
            }

            return value;
        }
    }

    /// <summary>
    /// Risultato dell'estrazione e matching di un PDF generico.
    /// </summary>
    public class PdfExtractionResult
    {
        /// <summary>
        /// Percentuale di campi matchati con successo (0-100).
        /// </summary>
        public double MatchPercentage { get; set; }

        /// <summary>
        /// Dati estratti in formato JSON (con i campi matchati popolati).
        /// </summary>
        public JObject ExtractedData { get; set; }

        /// <summary>
        /// Lista dei nomi dei campi che non sono stati matchati.
        /// </summary>
        public List<string> MissingFields { get; set; }

        /// <summary>
        /// Lista dei nomi dei campi OBBLIGATORI che non sono stati matchati.
        /// </summary>
        public List<string> MissingRequiredFields { get; set; }

        /// <summary>
        /// Indica se tutti i campi obbligatori sono stati trovati.
        /// </summary>
        public bool AllRequiredFieldsFound { get; set; }

        /// <summary>
        /// Numero di campi matchati.
        /// </summary>
        public int MatchedFieldsCount { get; set; }

        /// <summary>
        /// Numero totale di campi.
        /// </summary>
        public int TotalFieldsCount { get; set; }

        /// <summary>
        /// Messaggio di errore se l'estrazione è fallita.
        /// </summary>
        public string ErrorMessage { get; set; }

        /// <summary>
        /// Indica se il match è completo (100%).
        /// </summary>
        public bool IsFullMatch => MatchPercentage >= 100;

        /// <summary>
        /// Indica se il match è parziale (>0% e <100%).
        /// </summary>
        public bool IsPartialMatch => MatchPercentage > 0 && MatchPercentage < 100;

        /// <summary>
        /// Indica se non c'è match (0%).
        /// </summary>
        public bool IsNoMatch => MatchPercentage == 0;
    }
}
