using CsvHelper;
using CsvHelper.Configuration;
using CsvHelper.TypeConversion;
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace HorseRacingPredictor.Manager
{
    /// <summary>
    /// Gestore generico per la lettura di file CSV
    /// </summary>
    public class FileReader
    {
        public class NullableDecimalConverter : DefaultTypeConverter
        {
            public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
            {
                return ConvertToDecimal(text);
            }
        }

        public class NullableIntConverter : DefaultTypeConverter
        {
            public override object ConvertFromString(string text, IReaderRow row, MemberMapData memberMapData)
            {
                return ConvertToInt(text);
            }
        }

        /// <summary>
        /// Legge un file CSV e lo converte in un DataTable
        /// </summary>
        /// <param name="filePath">Percorso del file CSV</param>
        /// <returns>DataTable contenente i dati del file CSV</returns>
        public DataTable ReadCsvToDataTable(string filePath)
        {
            if (string.IsNullOrEmpty(filePath))
            {
                throw new ArgumentException("Il percorso del file non può essere vuoto.", nameof(filePath));
            }

            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException($"File non trovato: {filePath}");
            }

            var dataTable = new DataTable();

            // Configurazione per CsvHelper
            var config = new CsvConfiguration(CultureInfo.InvariantCulture)
            {
                HasHeaderRecord = true,
                MissingFieldFound = null, // Ignora campi mancanti
                TrimOptions = TrimOptions.Trim, // Rimuove spazi iniziali e finali
                PrepareHeaderForMatch = args => args.Header.Replace("#", "").Trim() // Rimuove # e spazi dai nomi delle colonne
            };

            using (var reader = new StreamReader(filePath))
            using (var csv = new CsvReader(reader, config))
            {
                // Legge l'intestazione
                csv.Read();
                csv.ReadHeader();

                // Crea le colonne della tabella dai nomi delle colonne del CSV
                foreach (var header in csv.HeaderRecord)
                {
                    string columnName = header.Replace("#", "").Trim();
                    dataTable.Columns.Add(columnName);
                }

                // Legge i dati riga per riga
                while (csv.Read())
                {
                    var row = dataTable.NewRow();
                    for (int i = 0; i < csv.HeaderRecord.Length; i++)
                    {
                        string columnName = csv.HeaderRecord[i].Replace("#", "").Trim();
                        string value = csv.GetField(i) ?? string.Empty;
                        row[columnName] = value;
                    }
                    dataTable.Rows.Add(row);
                }
            }

            return dataTable;
        }

        /// <summary>
        /// Ottiene una lista di tutti i file CSV in una cartella e nelle sue sottocartelle
        /// </summary>
        /// <param name="folderPath">Percorso della cartella da esplorare</param>
        /// <returns>Lista di percorsi dei file CSV trovati</returns>
        public List<string> GetCsvFiles(string folderPath)
        {
            if (string.IsNullOrEmpty(folderPath))
            {
                throw new ArgumentException("Il percorso della cartella non può essere vuoto.", nameof(folderPath));
            }

            if (!Directory.Exists(folderPath))
            {
                throw new DirectoryNotFoundException($"Directory non trovata: {folderPath}");
            }

            var csvFiles = Directory.GetFiles(folderPath, "*.csv", SearchOption.AllDirectories);
            return new List<string>(csvFiles);
        }

        /// <summary>
        /// Metodo helper per estrarre un valore stringa da una riga di DataTable
        /// </summary>
        public string GetStringValue(DataRow row, string columnName)
        {
            if (!row.Table.Columns.Contains(columnName) || row[columnName] == DBNull.Value)
            {
                return string.Empty;
            }
            return row[columnName].ToString();
        }

        /// <summary>
        /// Metodo helper per estrarre un valore intero da una riga di DataTable
        /// Gestisce valori nulli o non validi restituendo 0
        /// </summary>
        public int GetIntValue(DataRow row, string columnName)
        {
            if (!row.Table.Columns.Contains(columnName) || row[columnName] == DBNull.Value)
            {
                return 0;
            }

            if (int.TryParse(row[columnName].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out int result))
            {
                return result;
            }
            return 0;
        }

        /// <summary>
        /// Metodo helper per estrarre un valore intero nullable da una riga di DataTable
        /// Restituisce null per valori mancanti o non validi
        /// </summary>
        public int? GetNullableIntValue(DataRow row, string columnName)
        {
            if (!row.Table.Columns.Contains(columnName) || row[columnName] == DBNull.Value)
            {
                return null;
            }

            string stringValue = row[columnName].ToString();
            if (string.IsNullOrWhiteSpace(stringValue))
            {
                return null;
            }

            if (int.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out int result))
            {
                return result;
            }
            return null;
        }

        /// <summary>
        /// Metodo helper per estrarre un valore decimale da una riga di DataTable con controllo di precisione
        /// Gestisce valori nulli o non validi restituendo 0
        /// </summary>
        public decimal GetDecimalValue(DataRow row, string columnName, int precision, int scale)
        {
            if (!row.Table.Columns.Contains(columnName) || row[columnName] == DBNull.Value)
            {
                return 0.0M;
            }

            if (decimal.TryParse(row[columnName].ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, out decimal result))
            {
                // Arrotonda il valore per rispettare il limite di precisione e scala
                decimal factor = (decimal)Math.Pow(10, scale);
                result = Math.Round(result, scale);

                // Verifica se il valore è entro i limiti di precisione
                decimal maxValue = (decimal)Math.Pow(10, precision - scale) - (1 / factor);
                if (Math.Abs(result) > maxValue)
                {
                    Console.WriteLine($"Valore fuori range per {columnName}: {result}, limitato a {maxValue}");
                    result = result < 0 ? -maxValue : maxValue;
                }

                return result;
            }
            return 0.0M;
        }

        /// <summary>
        /// Metodo helper per estrarre un valore decimale nullable da una riga di DataTable
        /// Restituisce null per valori mancanti o non validi
        /// </summary>
        public decimal? GetNullableDecimalValue(DataRow row, string columnName, int precision, int scale)
        {
            if (!row.Table.Columns.Contains(columnName) || row[columnName] == DBNull.Value)
            {
                return null;
            }

            string stringValue = row[columnName].ToString();
            if (string.IsNullOrWhiteSpace(stringValue))
            {
                return null;
            }

            if (decimal.TryParse(stringValue, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal result))
            {
                // Arrotonda il valore per rispettare il limite di precisione e scala
                decimal factor = (decimal)Math.Pow(10, scale);
                result = Math.Round(result, scale);

                // Verifica se il valore è entro i limiti di precisione
                decimal maxValue = (decimal)Math.Pow(10, precision - scale) - (1 / factor);
                if (Math.Abs(result) > maxValue)
                {
                    Console.WriteLine($"Valore fuori range per {columnName}: {result}, limitato a {maxValue}");
                    result = result < 0 ? -maxValue : maxValue;
                }

                return result;
            }
            return null;
        }

        /// <summary>
        /// Converte un valore stringa in int con gestione dei valori nulli o non validi
        /// </summary>
        public static int ConvertToInt(string value)
        {
            if (string.IsNullOrWhiteSpace(value))
                return 0;

            if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out int result))
                return result;

            return 0;
        }

        /// <summary>
        /// Converte un valore stringa in decimal con gestione dei valori nulli o non validi
        /// </summary>
        public static decimal ConvertToDecimal(string value)
        {
            if (string.IsNullOrWhiteSpace(value))
                return 0m;

            if (decimal.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal result))
                return result;

            return 0m;
        }

        /// <summary>
        /// Converte un valore stringa in int? con gestione dei valori nulli o non validi
        /// </summary>
        public static int? ConvertToNullableInt(string value)
        {
            if (string.IsNullOrWhiteSpace(value))
                return null;

            if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out int result))
                return result;

            return null;
        }

        /// <summary>
        /// Converte un valore stringa in decimal? con gestione dei valori nulli o non validi
        /// </summary>
        public static decimal? ConvertToNullableDecimal(string value)
        {
            if (string.IsNullOrWhiteSpace(value))
                return null;

            if (decimal.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal result))
                return result;

            return null;
        }
    }
}
