using Microsoft.ML;
using Microsoft.ML.Data;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Data.SqlClient;

namespace HorseRacingPredictor.Horses.ML
{
    /// <summary>
    /// Servizio principale per l'implementazione del machine learning
    /// </summary>
    public class MachineLearningService
    {
        private readonly MLContext _mlContext;
        private ITransformer _trainedModel;
        private readonly string _modelPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Models", "HorseRaceModel.zip");

        public string ConnectionString { get; set; }

        public MachineLearningService(string connectionString = null)
        {
            _mlContext = new MLContext(seed: 42);
            Directory.CreateDirectory(Path.GetDirectoryName(_modelPath));
            ConnectionString = connectionString;
        }

        /// <summary>
        /// Carica un modello ML esistente da file
        /// </summary>
        public void LoadModel()
        {
            if (!File.Exists(_modelPath))
                throw new FileNotFoundException($"[ML] Modello non trovato: {_modelPath}");

            Console.WriteLine($"[ML] Caricamento modello da: {_modelPath}");
            _trainedModel = _mlContext.Model.Load(_modelPath, out _);
            Console.WriteLine("[ML] Modello ML caricato correttamente.");
        }

        /// <summary>
        /// Salva il modello addestrato su file
        /// </summary>
        public void SaveModel(ITransformer model)
        {
            Console.WriteLine($"[ML] Salvataggio modello in: {_modelPath}");
            _mlContext.Model.Save(model, null, _modelPath);
        }

        /// <summary>
        /// Ottiene i dati di training dal database e addestra un modello ML
        /// </summary>
        public void TrainModel(string connectionString)
        {
            // Imposta a 'true' per ricreare sempre il modello (default).
            // Per NON ricreare il modello ad ogni esecuzione, commenta la riga seguente:
            bool alwaysRetrain = true;
            // bool alwaysRetrain = false;

            if (!alwaysRetrain && File.Exists(_modelPath))
            {
                Console.WriteLine("[ML] Modello già esistente. Caricamento modello da file...");
                LoadModel();
                return;
            }

            try
            {
                Console.WriteLine("[ML] Inizio caricamento dati di training dal database...");
                var trainingData = LoadTrainingDataFromDatabase(connectionString);

                if (trainingData.Count == 0)
                {
                    Console.WriteLine("[ML][ERRORE] Nessun dato di training disponibile nel database.");
                    return;
                }

                Console.WriteLine($"[ML] Numero record caricati per il training: {trainingData.Count}");

                var dataView = _mlContext.Data.LoadFromEnumerable(trainingData);

                // Suddividi i dati in train e test (80% train, 20% test)
                var split = _mlContext.Data.TrainTestSplit(dataView, testFraction: 0.2, seed: 42);

                // Costruisci la pipeline di training
                var pipeline = _mlContext.Transforms.Concatenate("Features",
                        "Age", "HandicapRating", "Weight", "WeightCarried", "Barrier",
                        "CareerRuns", "CareerWins", "CareerStrikeRate", "CareerROI",
                        "ThisTrackRuns", "ThisTrackWins", "ThisTrackStrikeRate",
                        "ThisDistanceRuns", "ThisDistanceWins", "ThisDistanceStrikeRate",
                        "JockeyLast100Wins", "JockeyLast100StrikeRate",
                        "TrainerLast100Wins", "TrainerLast100StrikeRate", "BestFixedOdds",
                        "Rating1", "Rating2", "Rating3", "Rating4", "Rating5"
                        // Qui puoi aggiungere nuove feature create o derivate
                    )
                    .Append(_mlContext.Transforms.NormalizeMinMax("Features"))
                    .Append(_mlContext.Regression.Trainers.FastTree(
                        labelColumnName: "FinishPosition",
                        featureColumnName: "Features",
                        numberOfTrees: 200, // Aumentato per maggiore accuratezza
                        numberOfLeaves: 32, // Più foglie per maggiore complessità
                        minimumExampleCountPerLeaf: 5, // Più sensibile ai dettagli
                        learningRate: 0.15f // Puoi sperimentare anche questo parametro
                    ));

                Console.WriteLine("[ML] Inizio addestramento del modello...");
                _trainedModel = pipeline.Fit(split.TrainSet);
                Console.WriteLine("[ML] Addestramento completato.");

                // Valuta sul training set
                var trainMetrics = _mlContext.Regression.Evaluate(_trainedModel.Transform(split.TrainSet), labelColumnName: "FinishPosition");
                // Valuta sul test set
                var testMetrics = _mlContext.Regression.Evaluate(_trainedModel.Transform(split.TestSet), labelColumnName: "FinishPosition");

                // Cross-validation
                var cvResults = _mlContext.Regression.CrossValidate(data: dataView, estimator: pipeline, numberOfFolds: 5, labelColumnName: "FinishPosition");
                var avgRSquared = cvResults.Average(fold => fold.Metrics.RSquared);
                Console.WriteLine($"RSquared medio (cross-validation): {avgRSquared:F4}");

                SaveModel(_trainedModel);
                Console.WriteLine($"[ML] Modello salvato in: {_modelPath}");

                Console.WriteLine("[ML] Metriche di valutazione (TRAIN):");
                Console.WriteLine($"[ML] - RSquared: {trainMetrics.RSquared:F4}");
                Console.WriteLine($"[ML] - MAE: {trainMetrics.MeanAbsoluteError:F4}");
                Console.WriteLine($"[ML] - MSE: {trainMetrics.MeanSquaredError:F4}");
                Console.WriteLine($"[ML] - RMSE: {trainMetrics.RootMeanSquaredError:F4}");

                Console.WriteLine("[ML] Metriche di valutazione (TEST):");
                Console.WriteLine($"[ML] - RSquared: {testMetrics.RSquared:F4}");
                Console.WriteLine($"[ML] - MAE: {testMetrics.MeanAbsoluteError:F4}");
                Console.WriteLine($"[ML] - MSE: {testMetrics.MeanSquaredError:F4}");
                Console.WriteLine($"[ML] - RMSE: {testMetrics.RootMeanSquaredError:F4}");

                var predictions = _trainedModel.Transform(split.TestSet);
                var actuals = _mlContext.Data.CreateEnumerable<ModelInput>(split.TestSet, false).ToList();
                var preds = _mlContext.Data.CreateEnumerable<ModelOutput>(predictions, false).ToList();

                int top3Correct = actuals.Zip(preds, (a, p) => (a, p))
                    .Count(x => x.a.FinishPosition <= 3 && x.p.PredictedPosition <= 3);
                double accuracyTop3 = (double)top3Correct / actuals.Count;
                Console.WriteLine($"Percentuale cavalli previsti nei primi 3: {accuracyTop3:P2}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ML][ERRORE] Errore durante l'addestramento del modello: {ex.Message}");
                throw;
            }
        }

        /// <summary>
        /// Carica i dati di training dal database
        /// </summary>
        private List<ModelInput> LoadTrainingDataFromDatabase(string connectionString)
        {
            var trainingData = new List<ModelInput>();
            var calculator = new HorseRacingPredictor.Horses.Calculator();
            int skippedRows = 0;
            int rating1Errors = 0, rating2Errors = 0, rating3Errors = 0, rating4Errors = 0, rating5Errors = 0;

            try
            {
                using (var connection = new SqlConnection(connectionString))
                {
                    connection.Open();

                    string query = @"
                    SELECT 
                        CASE WHEN ISNUMERIC(FinishResult) = 1 THEN CAST(FinishResult AS FLOAT) ELSE 999 END AS FinishPosition,
                        COALESCE(Age, 0) AS Age,
                        COALESCE(HandicapRating, 0) AS HandicapRating,
                        COALESCE(Weight, 0) AS Weight,
                        COALESCE(WeightCarried, 0) AS WeightCarried,
                        COALESCE(Barrier, 0) AS Barrier,
                        COALESCE(CareerRuns, 0) AS CareerRuns,
                        COALESCE(CareerWins, 0) AS CareerWins,
                        COALESCE(CareerStrikeRate, 0) AS CareerStrikeRate,
                        COALESCE(CareerROI, 0) AS CareerROI,
                        COALESCE(ThisTrackRuns, 0) AS ThisTrackRuns,
                        COALESCE(ThisTrackWins, 0) AS ThisTrackWins,
                        COALESCE(ThisTrackStrikeRate, 0) AS ThisTrackStrikeRate,
                        COALESCE(ThisDistanceRuns, 0) AS ThisDistanceRuns,
                        COALESCE(ThisDistanceWins, 0) AS ThisDistanceWins,
                        COALESCE(ThisDistanceStrikeRate, 0) AS ThisDistanceStrikeRate,
                        COALESCE(JockeyLast100Wins, 0) AS JockeyLast100Wins,
                        COALESCE(JockeyLast100StrikeRate, 0) AS JockeyLast100StrikeRate,
                        COALESCE(TrainerLast100Wins, 0) AS TrainerLast100Wins,
                        COALESCE(TrainerLast100StrikeRate, 0) AS TrainerLast100StrikeRate,
                        COALESCE(BestFixedOdds, 0) AS BestFixedOdds,
                        COALESCE(Gender, '') AS Gender
                    FROM Races 
                    WHERE ISNUMERIC(FinishResult) = 1
                    AND FinishResult IS NOT NULL
                    AND FinishResult <> ''";

                    using (var command = new SqlCommand(query, connection))
                    {
                        using (var adapter = new SqlDataAdapter(command))
                        {
                            var table = new System.Data.DataTable();
                            adapter.Fill(table);

                            foreach (System.Data.DataRow row in table.Rows)
                            {
                                try {
                                    float finishPos = Convert.ToSingle(row["FinishPosition"]);
                                    if (finishPos >= 15 || finishPos <= 0) {
                                        skippedRows++;
                                        continue;
                                    }

                                    // Calcola i rating con gestione degli errori e sostituzione con valori predefiniti
                                    float rating1 = 0, rating2 = 0, rating3 = 0, rating4 = 0, rating5 = 0;
                                    
                                    try {
                                        rating1 = (float)calculator.CalculateRating1(row);
                                        if (float.IsNaN(rating1) || float.IsInfinity(rating1)) {
                                            rating1 = 0;
                                            rating1Errors++;
                                        }
                                    } catch (Exception) {
                                        rating1Errors++;
                                    }

                                    try {
                                        rating2 = (float)calculator.CalculateRating2(row);
                                        if (float.IsNaN(rating2) || float.IsInfinity(rating2)) {
                                            rating2 = 0;
                                            rating2Errors++;
                                        }
                                    } catch (Exception) {
                                        rating2Errors++;
                                    }

                                    try {
                                        rating3 = (float)calculator.CalculateRating3(row);
                                        if (float.IsNaN(rating3) || float.IsInfinity(rating3)) {
                                            rating3 = 0;
                                            rating3Errors++;
                                        }
                                    } catch (Exception) {
                                        rating3Errors++;
                                    }

                                    try {
                                        rating4 = (float)calculator.CalculateRating4(row);
                                        if (float.IsNaN(rating4) || float.IsInfinity(rating4)) {
                                            rating4 = 0;
                                            rating4Errors++;
                                        }
                                    } catch (Exception) {
                                        rating4Errors++;
                                    }

                                    try {
                                        rating5 = (float)calculator.CalculateRating5(row);
                                        if (float.IsNaN(rating5) || float.IsInfinity(rating5)) {
                                            rating5 = 0;
                                            rating5Errors++;
                                        }
                                    } catch (Exception) {
                                        rating5Errors++;
                                    }

                                    // Usa SafeConvert per tutte le conversioni
                                    trainingData.Add(new ModelInput
                                    {
                                        FinishPosition = finishPos,
                                        Age = SafeConvert.ToSingle(row["Age"]),
                                        HandicapRating = SafeConvert.ToSingle(row["HandicapRating"]),
                                        Weight = SafeConvert.ToSingle(row["Weight"]),
                                        WeightCarried = SafeConvert.ToSingle(row["WeightCarried"]),
                                        Barrier = SafeConvert.ToSingle(row["Barrier"]),
                                        CareerRuns = SafeConvert.ToSingle(row["CareerRuns"]),
                                        CareerWins = SafeConvert.ToSingle(row["CareerWins"]),
                                        CareerStrikeRate = SafeConvert.ToSingle(row["CareerStrikeRate"]),
                                        CareerROI = SafeConvert.ToSingle(row["CareerROI"]),
                                        ThisTrackRuns = SafeConvert.ToSingle(row["ThisTrackRuns"]),
                                        ThisTrackWins = SafeConvert.ToSingle(row["ThisTrackWins"]),
                                        ThisTrackStrikeRate = SafeConvert.ToSingle(row["ThisTrackStrikeRate"]),
                                        ThisDistanceRuns = SafeConvert.ToSingle(row["ThisDistanceRuns"]),
                                        ThisDistanceWins = SafeConvert.ToSingle(row["ThisDistanceWins"]),
                                        ThisDistanceStrikeRate = SafeConvert.ToSingle(row["ThisDistanceStrikeRate"]),
                                        JockeyLast100Wins = SafeConvert.ToSingle(row["JockeyLast100Wins"]),
                                        JockeyLast100StrikeRate = SafeConvert.ToSingle(row["JockeyLast100StrikeRate"]),
                                        TrainerLast100Wins = SafeConvert.ToSingle(row["TrainerLast100Wins"]),
                                        TrainerLast100StrikeRate = SafeConvert.ToSingle(row["TrainerLast100StrikeRate"]),
                                        BestFixedOdds = SafeConvert.ToSingle(row["BestFixedOdds"]),
                                        Rating1 = rating1,
                                        Rating2 = rating2,
                                        Rating3 = rating3,
                                        Rating4 = rating4,
                                        Rating5 = rating5
                                    });
                                } catch (Exception ex) {
                                    skippedRows++;
                                }
                            }
                            Console.WriteLine($"[ML] Record letti dal database: {table.Rows.Count}");
                            Console.WriteLine($"[ML] Record saltati: {skippedRows}");
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ML][ERRORE] Errore durante il caricamento dei dati dal database: {ex.Message}");
            }

            Console.WriteLine($"[ML] Caricati {trainingData.Count} record per l'addestramento.");
            return trainingData;
        }

        /// <summary>
        /// Costruisce la pipeline di addestramento del modello
        /// </summary>
        private IEstimator<ITransformer> BuildTrainingPipeline()
        {
            // Crea la pipeline per addestramento
            // 1. Concatena tutte le feature in un unico vettore
            // 2. Normalizza i dati di input
            // 3. Addestra un modello FastTree con FastTreeRegressionTrainer
            var pipeline = _mlContext.Transforms.Concatenate("Features",
                    "Age", "HandicapRating", "Weight", "WeightCarried", "Barrier",
                    "CareerRuns", "CareerWins", "CareerStrikeRate", "CareerROI",
                    "ThisTrackRuns", "ThisTrackWins", "ThisTrackStrikeRate",
                    "ThisDistanceRuns", "ThisDistanceWins", "ThisDistanceStrikeRate",
                    "JockeyLast100Wins", "JockeyLast100StrikeRate",
                    "TrainerLast100Wins", "TrainerLast100StrikeRate", "BestFixedOdds",
                    "Rating1", "Rating2", "Rating3", "Rating4", "Rating5")
                .Append(_mlContext.Transforms.NormalizeMinMax("Features"))
                .Append(_mlContext.Regression.Trainers.FastTree(
                    labelColumnName: "FinishPosition",
                    featureColumnName: "Features",
                    numberOfTrees: 100,
                    numberOfLeaves: 20,
                    minimumExampleCountPerLeaf: 10));

            return pipeline;
        }

        /// <summary>
        /// Valuta il modello addestrato
        /// </summary>
        private RegressionMetrics EvaluateModel(IDataView dataView, ITransformer model)
        {
            var predictions = model.Transform(dataView);
            return _mlContext.Regression.Evaluate(predictions, labelColumnName: "FinishPosition");
        }

        /// <summary>
        /// Predice la posizione finale per un cavallo
        /// </summary>
        public ModelOutput PredictFinishPosition(ModelInput input)
        {
            if (_trainedModel == null)
            {
                Console.WriteLine("Il modello non è stato addestrato o caricato.");

                // Restituisci un oggetto vuoto invece di lanciare un'eccezione
                return new ModelOutput
                {
                    PredictedPosition = 0,
                    WinProbability = 0,
                    PlaceProbability = 0
                };
            }

            // Crea il motore di previsione
            var predEngine = _mlContext.Model.CreatePredictionEngine<ModelInput, ModelOutput>(_trainedModel);

            // Effettua la previsione
            var prediction = predEngine.Predict(input);

            // Calcola le probabilità (implementazione semplificata)
            prediction.WinProbability = CalculateWinProbability(prediction.PredictedPosition);
            prediction.PlaceProbability = CalculatePlaceProbability(prediction.PredictedPosition);

            return prediction;
        }

        /// <summary>
        /// Calcola la probabilità di vittoria in base alla posizione prevista
        /// </summary>
        private float CalculateWinProbability(float predictedPosition)
        {
            // Implementazione semplice: più il valore è vicino a 1, maggiore è la probabilità di vittoria
            if (predictedPosition <= 1) return 0.9f;
            if (predictedPosition <= 2) return 0.5f;
            if (predictedPosition <= 3) return 0.3f;
            if (predictedPosition <= 5) return 0.1f;
            return 0.05f;
        }

        /// <summary>
        /// Calcola la probabilità di piazzamento (primi 3) in base alla posizione prevista
        /// </summary>
        private float CalculatePlaceProbability(float predictedPosition)
        {
            // Implementazione semplice: più il valore è vicino a 1, 2 o 3, maggiore è la probabilità di piazzamento
            if (predictedPosition <= 1) return 0.95f;
            if (predictedPosition <= 2) return 0.85f;
            if (predictedPosition <= 3) return 0.75f;
            if (predictedPosition <= 4) return 0.4f;
            if (predictedPosition <= 6) return 0.2f;
            return 0.1f;
        }

        /// <summary>
        /// Analizza un'intera corsa e prevede il risultato per tutti i cavalli
        /// </summary>
        public List<HorseRacePrediction> PredictRaceResult(string meeting, int raceNumber, DateTime raceDate, string connectionString)
        {
            var predictions = new List<HorseRacePrediction>();
            var calculator = new HorseRacingPredictor.Horses.Calculator(); // Inizializza il calculator qui

            try
            {
                using (var connection = new SqlConnection(connectionString))
                {
                    connection.Open();
                    string query = @"
                    SELECT 
                        Num, HorseName, 
                        COALESCE(Age, 0) AS Age,
                        COALESCE(HandicapRating, 0) AS HandicapRating,
                        COALESCE(Weight, 0) AS Weight,
                        COALESCE(WeightCarried, 0) AS WeightCarried,
                        COALESCE(Barrier, 0) AS Barrier,
                        COALESCE(CareerRuns, 0) AS CareerRuns,
                        COALESCE(CareerWins, 0) AS CareerWins,
                        COALESCE(CareerStrikeRate, 0) AS CareerStrikeRate,
                        COALESCE(CareerROI, 0) AS CareerROI,
                        COALESCE(ThisTrackRuns, 0) AS ThisTrackRuns,
                        COALESCE(ThisTrackWins, 0) AS ThisTrackWins,
                        COALESCE(ThisTrackStrikeRate, 0) AS ThisTrackStrikeRate,
                        COALESCE(ThisDistanceRuns, 0) AS ThisDistanceRuns,
                        COALESCE(ThisDistanceWins, 0) AS ThisDistanceWins,
                        COALESCE(ThisDistanceStrikeRate, 0) AS ThisDistanceStrikeRate,
                        COALESCE(JockeyLast100Wins, 0) AS JockeyLast100Wins,
                        COALESCE(JockeyLast100StrikeRate, 0) AS JockeyLast100StrikeRate,
                        COALESCE(TrainerLast100Wins, 0) AS TrainerLast100Wins,
                        COALESCE(TrainerLast100StrikeRate, 0) AS TrainerLast100StrikeRate,
                        COALESCE(BestFixedOdds, 0) AS BestFixedOdds,
                        COALESCE(Gender, '') AS Gender
                    FROM Races
                    WHERE Meeting = @Meeting AND Race = @Race AND Data = @Data";

                    using (var command = new SqlCommand(query, connection))
                    {
                        command.Parameters.AddWithValue("@Meeting", meeting);
                        command.Parameters.AddWithValue("@Race", raceNumber);
                        command.Parameters.AddWithValue("@Data", raceDate);

                        using (var reader = command.ExecuteReader())
                        {
                            while (reader.Read())
                            {
                                // Converti i dati del reader in una DataRow per usare il calculator
                                DataTable dt = new DataTable();
                                DataRow row = dt.NewRow();
                                for (int i = 0; i < reader.FieldCount; i++)
                                {
                                    if (!dt.Columns.Contains(reader.GetName(i)))
                                        dt.Columns.Add(reader.GetName(i));
                                    row[reader.GetName(i)] = reader.GetValue(i);
                                }
                                dt.Rows.Add(row);

                                // Prepara l'input per il modello
                                var input = new ModelInput
                                {
                                    Age = Convert.ToSingle(reader["Age"]),
                                    HandicapRating = Convert.ToSingle(reader["HandicapRating"]),
                                    Weight = Convert.ToSingle(reader["Weight"]),
                                    WeightCarried = Convert.ToSingle(reader["WeightCarried"]),
                                    Barrier = Convert.ToSingle(reader["Barrier"]),
                                    CareerRuns = Convert.ToSingle(reader["CareerRuns"]),
                                    CareerWins = Convert.ToSingle(reader["CareerWins"]),
                                    CareerStrikeRate = Convert.ToSingle(reader["CareerStrikeRate"]),
                                    CareerROI = Convert.ToSingle(reader["CareerROI"]),
                                    ThisTrackRuns = Convert.ToSingle(reader["ThisTrackRuns"]),
                                    ThisTrackWins = Convert.ToSingle(reader["ThisTrackWins"]),
                                    ThisTrackStrikeRate = Convert.ToSingle(reader["ThisTrackStrikeRate"]),
                                    ThisDistanceRuns = Convert.ToSingle(reader["ThisDistanceRuns"]),
                                    ThisDistanceWins = Convert.ToSingle(reader["ThisDistanceWins"]),
                                    ThisDistanceStrikeRate = Convert.ToSingle(reader["ThisDistanceStrikeRate"]),
                                    JockeyLast100Wins = Convert.ToSingle(reader["JockeyLast100Wins"]),
                                    JockeyLast100StrikeRate = Convert.ToSingle(reader["JockeyLast100StrikeRate"]),
                                    TrainerLast100Wins = Convert.ToSingle(reader["TrainerLast100Wins"]),
                                    TrainerLast100StrikeRate = Convert.ToSingle(reader["TrainerLast100StrikeRate"]),
                                    BestFixedOdds = Convert.ToSingle(reader["BestFixedOdds"]),
                                    // Aggiungi i rating calcolati
                                    Rating1 = (float)calculator.CalculateRating1(row),
                                    Rating2 = (float)calculator.CalculateRating2(row),
                                    Rating3 = (float)calculator.CalculateRating3(row),
                                    Rating4 = (float)calculator.CalculateRating4(row),
                                    Rating5 = (float)calculator.CalculateRating5(row)
                                };

                                // Effettua la previsione
                                var output = PredictFinishPosition(input);

                                // Calcola un rating ML basato sulla posizione prevista
                                int mlRating = CalculateMLRating(output);

                                // Aggiungi alla lista delle previsioni
                                predictions.Add(new HorseRacePrediction
                                {
                                    HorseId = Convert.ToString(reader["Num"]),
                                    HorseName = Convert.ToString(reader["HorseName"]),
                                    RaceMeeting = meeting,
                                    RaceNumber = raceNumber,
                                    RaceDate = raceDate,
                                    PredictedPosition = output.PredictedPosition,
                                    WinProbability = output.WinProbability,
                                    PlaceProbability = output.PlaceProbability,
                                    MLRating = mlRating,
                                    CurrentOdds = Convert.ToSingle(reader["BestFixedOdds"])
                                });
                            }
                        }
                    }
                }

                // Ordina per posizione prevista (dal migliore al peggiore)
                return predictions.OrderBy(p => p.PredictedPosition).ToList();
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ML][ERRORE] Errore durante la previsione della corsa: {ex.Message}");
                return predictions;
            }
        }

        /// <summary>
        /// Calcola un rating ML basato sulla posizione prevista e altre metriche
        /// </summary>
        private int CalculateMLRating(ModelOutput output)
        {
            // Formula per il rating ML (convertito in un numero intero da 0 a 100)
            double baseRating = (1 / Math.Max(0.1, output.PredictedPosition)) * 100;
            double winProbabilityFactor = output.WinProbability * 100;
            double placeProbabilityFactor = output.PlaceProbability * 50;

            // Combina i fattori con pesi diversi
            double combinedRating = (baseRating * 0.4) + (winProbabilityFactor * 0.4) + (placeProbabilityFactor * 0.2);

            // Limita il valore tra 0 e 100
            return (int)Math.Min(100, Math.Max(0, combinedRating));
        }

        // Metodo di utilità per convertire in modo sicuro i valori
        private static class SafeConvert
        {
            public static float ToSingle(object value, float defaultValue = 0)
            {
                if (value == null || value == DBNull.Value)
                    return defaultValue;
                
                try {
                    float result = Convert.ToSingle(value);
                    return float.IsNaN(result) || float.IsInfinity(result) ? defaultValue : result;
                }
                catch {
                    return defaultValue;
                }
            }
        }
    }
}
