using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Data.Analysis;
using MathNet.Numerics.Statistics;
using System.Data;
using LibraryPricer.Models;
using Accord.Statistics.Distributions.Multivariate;
using MathNet.Numerics.RootFinding;
using Accord.Math;

namespace LibraryPricer
{
    public class CalcFunctions
    {
        public static List<PrezziSottostanti> GetCachedEodUnderlyings()
        {
            return _cachedEodUnderlyings;
        }

        public static List<UnderlyingStats> ComputeUnderlyingStats(List<PrezziSottostanti> prezziUL)
        {
            var results = new List<UnderlyingStats>();

            foreach (var ul in prezziUL)
            {
                // Step 1: recupero la serie dei prezzi
                double[] prezzi = ul.PrezziClose;

                // Step 2: calcolo i rendimenti logaritmici
                int n = prezzi.Length;
                double[] logReturns = new double[n - 1];
                for (int i = 0; i < n - 1; i++)
                {
                    if (prezzi[i] > 0 && prezzi[i + 1] > 0)
                        logReturns[i] = Math.Log(prezzi[i] / prezzi[i + 1]);
                    else
                        logReturns[i] = 0.0; // fallback prudente se c’è un errore nei dati
                }

                // Step 3: calcolo della deviazione standard
                double avg = logReturns.Average();
                double sumSquared = logReturns.Select(r => Math.Pow(r - avg, 2)).Sum();
                double variance = sumSquared / (logReturns.Length - 1);
                double stdDev = Math.Sqrt(variance);

                // Step 4: annualizzazione (assume 252 giorni di mercato)
                double volatilityAnnualized = stdDev * Math.Sqrt(252);

                // Step 5: salva tutto nell’oggetto
                results.Add(new UnderlyingStats
                {
                    Nome = ul.Sottostante,
                    Prezzi = prezzi,
                    LogReturns = logReturns,
                    Volatility = volatilityAnnualized
                });
            }

            return results;
        }

        //Cache del risultato della query con i prezzi EOD per evitare di farla due volte
        private static List<PrezziSottostanti> _cachedEodUnderlyings = new List<PrezziSottostanti>();

        //Cache dell'array dei Fair Value per evitare di calcolarlo due volte
        private static FairValueResultArray _cachedFairValueArray;

        public static FairValueResult FairValue(double[] PricesUL, 
                                       double[,] cor_mat, 
                                       int numSottostanti, 
                                       int num_sims, 
                                       double TassoInteresse, 
                                       int DaysToMaturity, 
                                       double[] Dividends, 
                                       double[] Volatility, 
                                       int[] DaysToObservation,
                                       double[] DaysToObservationYFract,
                                       double[] CouponValues,
                                       double[] CouponTriggers,
                                       double[] AutocallValues,
                                       double[] AutocallTriggers,                                      
                                       int[] MemoryFlags,
                                       double CouponInMemory,
                                       string? PDI_Style, 
                                       double PDI_Strike, 
                                       double PDI_Barrier,
                                       double CapitalValue,
                                       double ProtMinVal,
                                       int Airbag,
                                       int Sigma,
                                       int Twinwin,
                                       int Relief,
                                       double FattoreAirbag,
                                       int OneStar,
                                       double TriggerOneStar,
                                       double CAP,
                                       double Leva)
        {

            double[] PV_Digits = new double[num_sims];
            double FairValue = 0;
            string CasoFairValue = string.Empty;

            FairValueResult fvr = new FairValueResult(); // Istanzio l'oggetto FairValue per ritornare più valori (FairValue,CasoFairValue)

            double[][] S_t_mat = new double[DaysToMaturity + 1][];
            double[] MinPricesUL = new double[DaysToMaturity + 1];
            if (_cachedFairValueArray != null)
            {
                PV_Digits = _cachedFairValueArray.FairValueArray;
                fvr.CasoFairValue = _cachedFairValueArray.CasoFairValue;
            }
            else
            {
                for (int i = 0; i < num_sims; i++)
                {
                    S_t_mat = CalcFunctions.GBMMultiEquity(PricesUL, cor_mat, numSottostanti, DaysToMaturity, TassoInteresse, Dividends, Volatility);

                    // Minimum stock price in % from all observed stock prices
                    double MinPrice = S_t_mat.SelectMany(x => x).Min();

                    // Stock price of the Worst performer at maturity
                    double MinPriceAtMaturity = MinArrayAtRow(S_t_mat, numSottostanti, DaysToMaturity);


                    double PV_digit = 0;
                    double memory_coupon = CouponInMemory;
                    double coupon_paid = 0;
                    for (int k = 0; k < DaysToObservation.Length; k++)
                    {
                        double minByRow = MinArrayAtRow(S_t_mat, numSottostanti, DaysToObservation[k]);
                        if (minByRow >= AutocallTriggers[k]) // caso rimborso autocall
                        {
                            PV_digit = (AutocallValues[k] + memory_coupon) * Math.Exp(-TassoInteresse * DaysToObservationYFract[k]) + coupon_paid;
                            break;
                        }
                        else
                        if (minByRow >= CouponTriggers[k]) // caso coupon pagato
                        {
                            coupon_paid += (CouponValues[k] + memory_coupon) * Math.Exp(-TassoInteresse * DaysToObservationYFract[k]);
                            memory_coupon = 0;
                        }
                        else
                        {
                            memory_coupon += (MemoryFlags[k] * CouponValues[k]);
                        }

                        // Blocco aggiornato per la gestione dei casi Fair Value
                        if (k == DaysToObservation.Length - 1)
                        {
                            // === CASI GESTITI ===
                            if (Airbag == 0 && Sigma == 0 && Relief == 0 && Twinwin == 0 && OneStar == 0)
                            {
                                PV_digit = CasoStandard(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                                       CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity,
                                                       PV_digit, memory_coupon, coupon_paid, k);
                                if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "Standard";
                            }
                            if (Airbag == 1 && Sigma == 0 && Relief == 0 && Twinwin == 0 && OneStar == 0)
                            {
                                PV_digit = CasoAirbag(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                                  CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity,
                                                  PV_digit, memory_coupon, coupon_paid, k, FattoreAirbag);
                                if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "Airbag";
                            }
                            if (Airbag == 0 && Sigma == 1 && Relief == 0 && Twinwin == 0 && OneStar == 0)
                            {
                                PV_digit = CasoSigma(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                                 CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity,
                                                 PV_digit, memory_coupon, coupon_paid, k, PDI_Strike);
                                if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "Sigma";
                            }
                            if (Airbag == 0 && Sigma == 0 && Relief == 1 && Twinwin == 0 && OneStar == 0)
                            {
                                double SecondMinPriceAtMaturity = SecondMinArrayAtRow(S_t_mat, numSottostanti, DaysToMaturity);
                                PV_digit = CasoRelief(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                                  CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity, SecondMinPriceAtMaturity,
                                                  PV_digit, memory_coupon, coupon_paid, k);
                                if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "Relief";
                            }
                            if (Airbag == 0 && Sigma == 0 && Relief == 0 && Twinwin == 1 && OneStar == 0)
                            {
                                PV_digit = CasoTwinWin(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                                     CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity,
                                                     PV_digit, memory_coupon, coupon_paid, k, PDI_Strike,
                                                     AutocallTriggers[k], CAP);
                                if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "TwinWin";
                            }
                            if (Airbag == 0 && Sigma == 0 && Relief == 0 && Twinwin == 0 && OneStar == 1)
                            {
                                double MaxPriceAtMaturity = MaxArrayAtRow(S_t_mat, numSottostanti, DaysToObservation[k]);
                                PV_digit = CasoOneStar(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                                    CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity, MaxPriceAtMaturity,
                                                    PV_digit, memory_coupon, coupon_paid, k, TriggerOneStar, CouponTriggers, CouponValues);

                                if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "OneStar";
                            }

                            // === NUOVI CASI COMBINATI ===
                            if (Airbag == 1 && Sigma == 0 && Relief == 0 && Twinwin == 0 && OneStar == 1)
                            {
                                double MaxPriceAtMaturity = MaxArrayAtRow(S_t_mat, numSottostanti, DaysToObservation[k]);
                                PV_digit = CasoAirbagOneStar(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                                          CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity, MaxPriceAtMaturity,
                                                          PV_digit, memory_coupon, coupon_paid, k, FattoreAirbag, TriggerOneStar);
                                if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "Airbag + OneStar";
                            }
                            if (Airbag == 0 && Sigma == 1 && Relief == 0 && Twinwin == 0 && OneStar == 1)
                            {
                                double MaxPriceAtMaturity = MaxArrayAtRow(S_t_mat, numSottostanti, DaysToObservation[k]);
                                PV_digit = CasoSigmaOneStar(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                                         CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity, MaxPriceAtMaturity,
                                                         PV_digit, memory_coupon, coupon_paid, k, PDI_Strike, TriggerOneStar);
                                if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "Sigma + OneStar";
                            }
                            if (Airbag == 0 && Sigma == 0 && Relief == 1 && Twinwin == 0 && OneStar == 1)
                            {
                                double SecondMinPriceAtMaturity = SecondMinArrayAtRow(S_t_mat, numSottostanti, DaysToMaturity);
                                double MaxPriceAtMaturity = MaxArrayAtRow(S_t_mat, numSottostanti, DaysToObservation[k]);
                                PV_digit = CasoReliefOneStar(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                                          CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity, SecondMinPriceAtMaturity, MaxPriceAtMaturity,
                                                          PV_digit, memory_coupon, coupon_paid, k, TriggerOneStar);
                                if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "Relief + OneStar";
                            }
                            if (Airbag == 0 && Sigma == 0 && Relief == 0 && Twinwin == 1 && OneStar == 1)
                            {
                                double MaxPriceAtMaturity = MaxArrayAtRow(S_t_mat, numSottostanti, DaysToObservation[k]);
                                PV_digit = CasoTwinWinOneStar(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                                          CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity, MaxPriceAtMaturity,
                                                          PV_digit, memory_coupon, coupon_paid, k, PDI_Strike, AutocallTriggers[k], CAP, TriggerOneStar);
                                if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "TwinWin + OneStar";
                            }
                            if (Airbag == 1 && Sigma == 0 && Relief == 0 && Twinwin == 1 && OneStar == 0)
                            {
                                PV_digit = CasoAirbagTwinWin(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                                          CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity,
                                                          PV_digit, memory_coupon, coupon_paid, k,
                                                          PDI_Strike, AutocallTriggers[k], CAP, FattoreAirbag);
                                if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "Airbag + TwinWin";
                            }
                        }
                    }

                    PV_Digits[i] = PV_digit;
                }

            }
            // The Autocallable price is the average of the present value of cash flows from all simulations
            fvr.FairValue = PV_Digits.Mean() * 100;
            fvr.Stdev = MathNet.Numerics.Statistics.Statistics.StandardDeviation(PV_Digits.Select(x => x * 100));
            fvr.Simulations = num_sims;
            fvr.FairValueArray = PV_Digits.Select(x => x * 100).ToArray();

            return fvr;

        }

        public static FairValueResultArray FairValueArray(double[] PricesUL,
                                       double[,] cor_mat,
                                       int numSottostanti,
                                       int num_sims,
                                       double TassoInteresse,
                                       int DaysToMaturity,
                                       double[] Dividends,
                                       double[] Volatility,
                                       int[] DaysToObservation,
                                       double[] DaysToObservationYFract,
                                       double[] CouponValues,
                                       double[] CouponTriggers,
                                       double[] AutocallValues,
                                       double[] AutocallTriggers,
                                       int[] MemoryFlags,
                                       double CouponInMemory,
                                       string? PDI_Style,
                                       double PDI_Strike,
                                       double PDI_Barrier,
                                       double CapitalValue,
                                       double ProtMinVal,
                                       int Airbag,
                                       int Sigma,
                                       int Twinwin,
                                       int Relief,
                                       double FattoreAirbag,
                                       int OneStar,
                                       double TriggerOneStar,
                                       double CAP,
                                       double Leva)
        {
            double FairValue = 0;
            string CasoFairValue = string.Empty;

            FairValueResultArray fvr = new FairValueResultArray(); // Istanzio l'oggetto FairValue per ritornare più valori (FairValue,CasoFairValue)

            double[][] S_t_mat = new double[DaysToMaturity + 1][];
            double[] MinPricesUL = new double[DaysToMaturity + 1];
            double[] PV_Digits = new double[num_sims];
            for (int i = 0; i < num_sims; i++)
            {
                S_t_mat = CalcFunctions.GBMMultiEquity(PricesUL, cor_mat, numSottostanti, DaysToMaturity, TassoInteresse, Dividends, Volatility);

                // Minimum stock price in % from all observed stock prices
                double MinPrice = S_t_mat.SelectMany(x => x).Min();

                // Stock price of the Worst performer at maturity
                double MinPriceAtMaturity = MinArrayAtRow(S_t_mat, numSottostanti, DaysToMaturity);


                double PV_digit = 0;
                double memory_coupon = CouponInMemory;
                double coupon_paid = 0;
                for (int k = 0; k < DaysToObservation.Length; k++)
                {
                    double minByRow = MinArrayAtRow(S_t_mat, numSottostanti, DaysToObservation[k]);
                    if (minByRow >= AutocallTriggers[k]) // caso rimborso autocall
                    {
                        PV_digit = (AutocallValues[k] + memory_coupon) * Math.Exp(-TassoInteresse * DaysToObservationYFract[k]) + coupon_paid;
                        break;
                    }
                    else
                    if (minByRow >= CouponTriggers[k]) // caso coupon pagato
                    {
                        coupon_paid += (CouponValues[k] + memory_coupon) * Math.Exp(-TassoInteresse * DaysToObservationYFract[k]);
                        memory_coupon = 0;
                    }
                    else
                    {
                        memory_coupon += (MemoryFlags[k] * CouponValues[k]);
                    }

                    // Blocco aggiornato per la gestione dei casi Fair Value
                    if (k == DaysToObservation.Length - 1)
                    {
                        // === CASI GESTITI ===
                        if (Airbag == 0 && Sigma == 0 && Relief == 0 && Twinwin == 0 && OneStar == 0)
                        {
                            PV_digit = CasoStandard(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                                   CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity,
                                                   PV_digit, memory_coupon, coupon_paid, k);
                            if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "Standard";
                        }
                        if (Airbag == 1 && Sigma == 0 && Relief == 0 && Twinwin == 0 && OneStar == 0)
                        {
                            PV_digit = CasoAirbag(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                              CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity,
                                              PV_digit, memory_coupon, coupon_paid, k, FattoreAirbag);
                            if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "Airbag";
                        }
                        if (Airbag == 0 && Sigma == 1 && Relief == 0 && Twinwin == 0 && OneStar == 0)
                        {
                            PV_digit = CasoSigma(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                             CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity,
                                             PV_digit, memory_coupon, coupon_paid, k, PDI_Strike);
                            if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "Sigma";
                        }
                        if (Airbag == 0 && Sigma == 0 && Relief == 1 && Twinwin == 0 && OneStar == 0)
                        {
                            double SecondMinPriceAtMaturity = SecondMinArrayAtRow(S_t_mat, numSottostanti, DaysToMaturity);
                            PV_digit = CasoRelief(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                              CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity, SecondMinPriceAtMaturity,
                                              PV_digit, memory_coupon, coupon_paid, k);
                            if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "Relief";
                        }
                        if (Airbag == 0 && Sigma == 0 && Relief == 0 && Twinwin == 1 && OneStar == 0)
                        {
                            PV_digit = CasoTwinWin(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                                 CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity,
                                                 PV_digit, memory_coupon, coupon_paid, k, PDI_Strike,
                                                 AutocallTriggers[k], CAP);
                            if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "TwinWin";
                        }
                        if (Airbag == 0 && Sigma == 0 && Relief == 0 && Twinwin == 0 && OneStar == 1)
                        {
                            double MaxPriceAtMaturity = MaxArrayAtRow(S_t_mat, numSottostanti, DaysToObservation[k]);
                            PV_digit = CasoOneStar(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity,
                                MaxPriceAtMaturity, PV_digit, memory_coupon, coupon_paid, k, TriggerOneStar,
                                CouponTriggers, CouponValues);

                            if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "OneStar";
                        }

                        // === NUOVI CASI COMBINATI ===
                        if (Airbag == 1 && Sigma == 0 && Relief == 0 && Twinwin == 0 && OneStar == 1)
                        {
                            double MaxPriceAtMaturity = MaxArrayAtRow(S_t_mat, numSottostanti, DaysToObservation[k]);
                            PV_digit = CasoAirbagOneStar(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                                      CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity, MaxPriceAtMaturity,
                                                      PV_digit, memory_coupon, coupon_paid, k, FattoreAirbag, TriggerOneStar);
                            if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "Airbag + OneStar";
                        }
                        if (Airbag == 0 && Sigma == 1 && Relief == 0 && Twinwin == 0 && OneStar == 1)
                        {
                            double MaxPriceAtMaturity = MaxArrayAtRow(S_t_mat, numSottostanti, DaysToObservation[k]);
                            PV_digit = CasoSigmaOneStar(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                                     CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity, MaxPriceAtMaturity,
                                                     PV_digit, memory_coupon, coupon_paid, k, PDI_Strike, TriggerOneStar);
                            if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "Sigma + OneStar";
                        }
                        if (Airbag == 0 && Sigma == 0 && Relief == 1 && Twinwin == 0 && OneStar == 1)
                        {
                            double SecondMinPriceAtMaturity = SecondMinArrayAtRow(S_t_mat, numSottostanti, DaysToMaturity);
                            double MaxPriceAtMaturity = MaxArrayAtRow(S_t_mat, numSottostanti, DaysToObservation[k]);
                            PV_digit = CasoReliefOneStar(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                                      CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity, SecondMinPriceAtMaturity, MaxPriceAtMaturity,
                                                      PV_digit, memory_coupon, coupon_paid, k, TriggerOneStar);
                            if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "Relief + OneStar";
                        }
                        if (Airbag == 0 && Sigma == 0 && Relief == 0 && Twinwin == 1 && OneStar == 1)
                        {
                            double MaxPriceAtMaturity = MaxArrayAtRow(S_t_mat, numSottostanti, DaysToObservation[k]);
                            PV_digit = CasoTwinWinOneStar(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                                      CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity, MaxPriceAtMaturity,
                                                      PV_digit, memory_coupon, coupon_paid, k, PDI_Strike, AutocallTriggers[k], CAP, TriggerOneStar);
                            if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "TwinWin + OneStar";
                        }
                        if (Airbag == 1 && Sigma == 0 && Relief == 0 && Twinwin == 1 && OneStar == 0)
                        {
                            PV_digit = CasoAirbagTwinWin(TassoInteresse, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                                                      CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity,
                                                      PV_digit, memory_coupon, coupon_paid, k,
                                                      PDI_Strike, AutocallTriggers[k], CAP, FattoreAirbag);
                            if (String.IsNullOrEmpty(fvr.CasoFairValue)) fvr.CasoFairValue = "Airbag + TwinWin";
                        }
                    }
                }

                PV_Digits[i] = PV_digit;
            }
            for (int i = 0; i < PV_Digits.Length; i++) PV_Digits[i] = PV_Digits[i] * 100;
            fvr.FairValueArray = PV_Digits;
            return fvr;
        }


        public class FairValueResult
        {
            public double FairValue { get; set; }
            public double[] FairValueArray { get; set; }  // se serve per Array()
            public double Stdev { get; set; }             // per confronto GARCH
            public int Simulations { get; set; }          // per confronto GARCH
            public string CasoFairValue { get; set; }     // <-- questo ti manca
        }


        public class FairValueResultArray
        {
            public double[] FairValueArray { get; set; }
            public string? CasoFairValue { get; set;}
        }

        //private static double CasoOneStar(double r, double[] DaysToObservationYFract, string? PDI_Style, double PDI_Barrier, double CapitalValue, double ProtMinVal, double MinPrice, double MinPriceAtMaturity, double MaxPriceAtMaturity, double PV_digit, double memory_coupon, double coupon_paid, int k, double TriggerOneStar)
        //{
        //    // Barriera Discreta (European)
        //    if (PDI_Style == "European" && MaxPriceAtMaturity >= TriggerOneStar) // E' Sopra triggeronestar
        //    {
        //        PV_digit = (Math.Exp(-r * DaysToObservationYFract[k]) * (CapitalValue + memory_coupon)) + coupon_paid;
        //    }
        //    if (PDI_Style == "European" && MaxPriceAtMaturity < TriggerOneStar) // E' sotto triggeronestar
        //    {
        //        if (PDI_Style == "European" && MinPriceAtMaturity >= PDI_Barrier) // Non ha rotto barriera
        //        {
        //            PV_digit = (Math.Exp(-r * DaysToObservationYFract[k]) * (CapitalValue + memory_coupon)) + coupon_paid;
        //        }
        //        if (PDI_Style == "European" && MinPriceAtMaturity < PDI_Barrier) // Ha rotto barriera
        //        {
        //            PV_digit = (Math.Max(ProtMinVal, MinPriceAtMaturity / 100)) * Math.Exp(-r * DaysToObservationYFract[k]) + coupon_paid;
        //        }               
        //    }

        //    // Barriere Continue (American)
        //    if (PDI_Style == "American" && MinPrice >= PDI_Barrier) // non ha rotto barriera
        //    {
        //        PV_digit = (Math.Exp(-r * DaysToObservationYFract[k]) * (CapitalValue + memory_coupon)) + coupon_paid;
        //    }
        //    if (PDI_Style == "American" && MinPrice < PDI_Barrier)
        //    {
        //        PV_digit = (Math.Max(ProtMinVal, MinPriceAtMaturity / 100)) * Math.Exp(-r * DaysToObservationYFract[k]) + coupon_paid;
        //    }

        //    return PV_digit;
        //}


        private static double CasoOneStar(double r, double[] DaysToObservationYFract, string? PDI_Style,
            double PDI_Barrier, double CapitalValue, double ProtMinVal,
            double MinPrice, double MinPriceAtMaturity, double MaxPriceAtMaturity,
            double PV_digit, double memory_coupon, double coupon_paid, int k,
            double TriggerOneStar, double[] CouponTriggers, double[] CouponValues)

        {
            double payoutCapitale;
            double payoutCedola = 0;

            // === CAPITALE ===
            if (MinPriceAtMaturity < PDI_Barrier)
            {
                if (MaxPriceAtMaturity < TriggerOneStar)
                {
                    // Caso: sotto barriera e sotto trigger OneStar → perdita
                    payoutCapitale = Math.Max(ProtMinVal, MinPriceAtMaturity / 100.0);
                }
                else
                {
                    // Caso: sotto barriera ma sopra trigger OneStar → capitale protetto
                    payoutCapitale = CapitalValue;
                }
            }
            else
            {
                // Caso: sopra barriera → capitale protetto a prescindere
                payoutCapitale = CapitalValue;
            }

            // === CEDOLA ===
            if (MinPriceAtMaturity >= CouponTriggers[k])
            {
                payoutCedola = memory_coupon + CouponValues[k];
            }

            // === RISULTATO ===
            return payoutCapitale * Math.Exp(-r * DaysToObservationYFract[k])
                    + payoutCedola * Math.Exp(-r * DaysToObservationYFract[k])
                    + coupon_paid;
        }








        private static double CasoTwinWin(double r, double[] DaysToObservationYFract, string? PDI_Style, double PDI_Barrier, double CapitalValue, double ProtMinVal, double MinPrice, double MinPriceAtMaturity, double PV_digit, double memory_coupon, double coupon_paid, int k , double PDI_Strike, double TriggerAutocall, double CAP)
        {
            if (PDI_Style == "European" && MinPriceAtMaturity >= PDI_Strike && TriggerAutocall == 999)
            {
                PV_digit = (Math.Exp(-r * DaysToObservationYFract[k]) * (memory_coupon + (Math.Min(MinPriceAtMaturity / 100,CAP)))) + coupon_paid;
            }
            // Barriera Discreta (European)
            if (PDI_Style == "European" && MinPriceAtMaturity >= PDI_Barrier) // non ha rotto barriera
            {
                PV_digit = (Math.Exp(-r * DaysToObservationYFract[k]) * ((2 * CapitalValue) + memory_coupon - (MinPriceAtMaturity / 100))) + coupon_paid;
            }
            if (PDI_Style == "European" && MinPriceAtMaturity < PDI_Barrier)
            {
                PV_digit = (Math.Max(ProtMinVal, MinPriceAtMaturity / 100)) * Math.Exp(-r * DaysToObservationYFract[k]) + coupon_paid;
            }

            // Barriere Continue (American)
            if (PDI_Style == "American" && MinPriceAtMaturity >= PDI_Strike && TriggerAutocall == 999)
            {
                PV_digit = (Math.Exp(-r * DaysToObservationYFract[k]) * (memory_coupon + (Math.Min(MinPriceAtMaturity / 100, CAP)))) + coupon_paid;
            }
            if (PDI_Style == "American" && MinPrice >= PDI_Barrier) // non ha rotto barriera
            {
                PV_digit = (Math.Exp(-r * DaysToObservationYFract[k]) * ((2 * CapitalValue) + memory_coupon - (MinPriceAtMaturity / 100))) + coupon_paid;
            }
            if (PDI_Style == "American" && MinPrice < PDI_Barrier)
            {
                PV_digit = (Math.Max(ProtMinVal, MinPriceAtMaturity / 100)) * Math.Exp(-r * DaysToObservationYFract[k]) + coupon_paid;
            }

            return PV_digit;
        }
        private static double CasoRelief(double r, double[] DaysToObservationYFract, string? PDI_Style, double PDI_Barrier, double CapitalValue, double ProtMinVal, double MinPrice, double MinPriceAtMaturity, double SecondMinPriceAtMaturity, double PV_digit, double memory_coupon, double coupon_paid, int k)
        {
            // Barriera Discreta (European)
            if (PDI_Style == "European" && MinPriceAtMaturity >= PDI_Barrier) // non ha rotto barriera
            {
                PV_digit = (Math.Exp(-r * DaysToObservationYFract[k]) * (CapitalValue + memory_coupon)) + coupon_paid;
            }
            if (PDI_Style == "European" && MinPriceAtMaturity < PDI_Barrier)
            {
                PV_digit = (Math.Max(ProtMinVal, SecondMinPriceAtMaturity / 100)) * Math.Exp(-r * DaysToObservationYFract[k]) + coupon_paid;
            }

            // Barriere Continue (American)
            if (PDI_Style == "American" && MinPrice >= PDI_Barrier) // non ha rotto barriera
            {
                PV_digit = (Math.Exp(-r * DaysToObservationYFract[k]) * (CapitalValue + memory_coupon)) + coupon_paid;
            }
            if (PDI_Style == "American" && MinPrice < PDI_Barrier)
            {
                PV_digit = (Math.Max(ProtMinVal, SecondMinPriceAtMaturity / 100)) * Math.Exp(-r * DaysToObservationYFract[k]) + coupon_paid;
            }

            return PV_digit;
        }

        private static double CasoSigma(double r, double[] DaysToObservationYFract, string? PDI_Style, double PDI_Barrier, double CapitalValue, double ProtMinVal, double MinPrice, double MinPriceAtMaturity, double PV_digit, double memory_coupon, double coupon_paid, int k, double PDI_Strike)
        {
            // Barriera Discreta (European)
            if (PDI_Style == "European" && MinPriceAtMaturity >= PDI_Barrier) // non ha rotto barriera
            {
                PV_digit = (Math.Exp(-r * DaysToObservationYFract[k]) * (CapitalValue + memory_coupon)) + coupon_paid;
            }
            if (PDI_Style == "European" && MinPriceAtMaturity < PDI_Barrier)
            {
                PV_digit = (Math.Max(ProtMinVal, (MinPriceAtMaturity + PDI_Strike - PDI_Barrier) / 100)) * Math.Exp(-r * DaysToObservationYFract[k]) + coupon_paid;
            }

            // Barriere Continue (American)
            if (PDI_Style == "American" && MinPrice >= PDI_Barrier) // non ha rotto barriera
            {
                PV_digit = (Math.Exp(-r * DaysToObservationYFract[k]) * (CapitalValue + memory_coupon)) + coupon_paid;
            }
            if (PDI_Style == "American" && MinPrice < PDI_Barrier)
            {
                PV_digit = (Math.Max(ProtMinVal, (MinPriceAtMaturity + PDI_Strike - PDI_Barrier) / 100)) * Math.Exp(-r * DaysToObservationYFract[k]) + coupon_paid;
            }

            return PV_digit;
        }

        private static double CasoAirbag(double r, double[] DaysToObservationYFract, string? PDI_Style, double PDI_Barrier, double CapitalValue, double ProtMinVal, double MinPrice, double MinPriceAtMaturity, double PV_digit, double memory_coupon, double coupon_paid, int k, double FattoreAirbag)
        {
            // Barriera Discreta (European)
            if (PDI_Style == "European" && MinPriceAtMaturity >= PDI_Barrier) // non ha rotto barriera
            {
                PV_digit = (Math.Exp(-r * DaysToObservationYFract[k]) * (CapitalValue + memory_coupon)) + coupon_paid;
            }
            if (PDI_Style == "European" && MinPriceAtMaturity < PDI_Barrier)
            {
                PV_digit = (Math.Max(ProtMinVal, MinPriceAtMaturity / 100)) * Math.Exp(-r * DaysToObservationYFract[k]) * FattoreAirbag + coupon_paid;
            }

            // Barriere Continue (American)
            if (PDI_Style == "American" && MinPrice >= PDI_Barrier) // non ha rotto barriera
            {
                PV_digit = (Math.Exp(-r * DaysToObservationYFract[k]) * (CapitalValue + memory_coupon)) + coupon_paid;
            }
            if (PDI_Style == "American" && MinPrice < PDI_Barrier)
            {
                PV_digit = (Math.Max(ProtMinVal, MinPriceAtMaturity / 100)) * Math.Exp(-r * DaysToObservationYFract[k]) * FattoreAirbag + coupon_paid;
            }

            return PV_digit;
        }

        // === DEFINIZIONI DEI METODI COMBINATI ===

        private static double CasoAirbagOneStar(double r, double[] DaysToObservationYFract, string? PDI_Style,
            double PDI_Barrier, double CapitalValue, double ProtMinVal, double MinPrice, double MinPriceAtMaturity,
            double MaxPriceAtMaturity, double PV_digit, double memory_coupon, double coupon_paid, int k,
            double FattoreAirbag, double TriggerOneStar)
        {
            if (MaxPriceAtMaturity >= TriggerOneStar)
                return (Math.Exp(-r * DaysToObservationYFract[k]) * (CapitalValue + memory_coupon)) + coupon_paid;
            return CasoAirbag(r, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                              CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity,
                              PV_digit, memory_coupon, coupon_paid, k, FattoreAirbag);
        }

        private static double CasoSigmaOneStar(double r, double[] DaysToObservationYFract, string? PDI_Style,
            double PDI_Barrier, double CapitalValue, double ProtMinVal, double MinPrice, double MinPriceAtMaturity,
            double MaxPriceAtMaturity, double PV_digit, double memory_coupon, double coupon_paid, int k,
            double PDI_Strike, double TriggerOneStar)
        {
            if (MaxPriceAtMaturity >= TriggerOneStar)
                return (Math.Exp(-r * DaysToObservationYFract[k]) * (CapitalValue + memory_coupon)) + coupon_paid;
            return CasoSigma(r, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                             CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity,
                             PV_digit, memory_coupon, coupon_paid, k, PDI_Strike);
        }

        private static double CasoReliefOneStar(double r, double[] DaysToObservationYFract, string? PDI_Style,
            double PDI_Barrier, double CapitalValue, double ProtMinVal, double MinPrice, double MinPriceAtMaturity,
            double SecondMinPriceAtMaturity, double MaxPriceAtMaturity, double PV_digit, double memory_coupon,
            double coupon_paid, int k, double TriggerOneStar)
        {
            if (MaxPriceAtMaturity >= TriggerOneStar)
                return (Math.Exp(-r * DaysToObservationYFract[k]) * (CapitalValue + memory_coupon)) + coupon_paid;
            return CasoRelief(r, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                              CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity, SecondMinPriceAtMaturity,
                              PV_digit, memory_coupon, coupon_paid, k);
        }

        private static double CasoTwinWinOneStar(double r, double[] DaysToObservationYFract, string? PDI_Style,
            double PDI_Barrier, double CapitalValue, double ProtMinVal, double MinPrice, double MinPriceAtMaturity,
            double MaxPriceAtMaturity, double PV_digit, double memory_coupon, double coupon_paid, int k,
            double PDI_Strike, double AutocallTrigger, double CAP, double TriggerOneStar)
        {
            if (MaxPriceAtMaturity >= TriggerOneStar)
                return (Math.Exp(-r * DaysToObservationYFract[k]) * (CapitalValue + memory_coupon)) + coupon_paid;
            return CasoTwinWin(r, DaysToObservationYFract, PDI_Style, PDI_Barrier,
                               CapitalValue, ProtMinVal, MinPrice, MinPriceAtMaturity,
                               PV_digit, memory_coupon, coupon_paid, k,
                               PDI_Strike, AutocallTrigger, CAP);
        }

        private static double CasoAirbagTwinWin(double r, double[] DaysToObservationYFract, string? PDI_Style,
            double PDI_Barrier, double CapitalValue, double ProtMinVal, double MinPrice, double MinPriceAtMaturity,
            double PV_digit, double memory_coupon, double coupon_paid, int k,
            double PDI_Strike, double AutocallTrigger, double CAP, double FattoreAirbag)
        {
            if (MinPriceAtMaturity >= PDI_Barrier)
            {
                if (AutocallTrigger == 999 && MinPriceAtMaturity >= PDI_Strike)
                {
                    return (Math.Exp(-r * DaysToObservationYFract[k]) * (memory_coupon + Math.Min(MinPriceAtMaturity / 100, CAP))) + coupon_paid;
                }
                return (Math.Exp(-r * DaysToObservationYFract[k]) * ((2 * CapitalValue) + memory_coupon - (MinPriceAtMaturity / 100))) + coupon_paid;
            }
            return (Math.Max(ProtMinVal, MinPriceAtMaturity / 100)) * Math.Exp(-r * DaysToObservationYFract[k]) * FattoreAirbag + coupon_paid;
        }


        private static double CasoStandard(double r, double[] DaysToObservationYFract, string? PDI_Style, double PDI_Barrier, double CapitalValue, double ProtMinVal, double MinPrice, double MinPriceAtMaturity, double PV_digit, double memory_coupon, double coupon_paid, int k)
        {
            // Barriera Discreta (European)
            if (PDI_Style == "European" && MinPriceAtMaturity >= PDI_Barrier) // non ha rotto barriera
            {
                PV_digit = (Math.Exp(-r * DaysToObservationYFract[k]) * (CapitalValue + memory_coupon)) + coupon_paid;
            }
            if (PDI_Style == "European" && MinPriceAtMaturity < PDI_Barrier)
            {
                PV_digit = (Math.Max(ProtMinVal, MinPriceAtMaturity / 100)) * Math.Exp(-r * DaysToObservationYFract[k]) + coupon_paid;
            }

            // Barriere Continue (American)
            if (PDI_Style == "American" && MinPrice >= PDI_Barrier) // non ha rotto barriera
            {
                PV_digit = (Math.Exp(-r * DaysToObservationYFract[k]) * (CapitalValue + memory_coupon)) + coupon_paid;
            }
            if (PDI_Style == "American" && MinPrice < PDI_Barrier)
            {
                PV_digit = (Math.Max(ProtMinVal, MinPriceAtMaturity / 100)) * Math.Exp(-r * DaysToObservationYFract[k]) + coupon_paid;
            }

            return PV_digit;
        }

        public static double[][] GBMMultiEquity(double[] PricesUL, double[,] cor_mat, int numSottostanti, int DaysToMaturity, double TassoInteresse, double[] q_assets, double[] vol_assets)
        {
            double dt = 1.0 / 252.0; // "daily"

            // Dataframe containing the correlated random variables from a standard normal distribution used in the GBM
            double[] mean = Enumerable.Repeat(0.0, numSottostanti).ToArray();
            var dist = new MultivariateNormalDistribution(mean, cor_mat);
            
            // Generate samples from this distribution:
            double[][] df_W = dist.Generate(DaysToMaturity);

            double[][] df_S = new double[DaysToMaturity + 1][]; // aggiunto +1 per considerare la riga dei prezzi aggiunta a df_S nel ciclo successivo // https://stackoverflow.com/questions/40333403/the-difference-between-and-in-c-sharp
            for (int k = 0; k < DaysToMaturity + 1; k++)
            {
                df_S[k] = new double[numSottostanti];
            }
            
            // Assegna nella prima riga della matrice df_S i prices UL 
            for (int i = 0; i < numSottostanti; i++)
            {
                df_S[0][i] = PricesUL[i];
            }

            // Elabora il resto della matride df_S partendo da 1 fino a num_steps (incluso)
            for (int i = 0; i < numSottostanti; i++)
            {
                double drift = ((TassoInteresse - q_assets[i]) - (0.5 * (Math.Pow(vol_assets[i],2))));
                
                double[] tempArray = new double[DaysToMaturity];
                for (int j = 0; j < DaysToMaturity; j++)
                {
                    tempArray[j] = (drift * dt) + (vol_assets[i] * Math.Sqrt(dt) * df_W[j][i]);
                }
                double[] cumSumArray = new double[DaysToMaturity];
                cumSumArray = CumulativeSums(tempArray);
                for (int k = 1; k < DaysToMaturity + 1; k++)
                {                   
                    df_S[k][i] = PricesUL[i] * Math.Exp(cumSumArray[k-1]);
                }

            }
            return df_S;
        }

        public static double MaxArrayAtRow(double[][] jaggedArray, int numSottostanti, int RowIndex)
        {
            double value = -999999;
            for (int j = 0; j < numSottostanti; j++)
            {
                if (jaggedArray[RowIndex][j] > value) value = jaggedArray[RowIndex][j];
            }
            return (value);
        }

        public static double MinArrayAtRow(double[][] jaggedArray, int numSottostanti, int RowIndex)
        {
            double value = 999999;
            for (int j = 0; j < numSottostanti; j++)
            {
                if (jaggedArray[RowIndex][j] < value) value = jaggedArray[RowIndex][j];
            }
            return (value);
        }

        public static double SecondMinArrayAtRow(double[][] jaggedArray, int numSottostanti, int RowIndex)
        {
            double value = 999999;
            double[] tempArray = new double[numSottostanti];
            for (int j = 0; j < numSottostanti; j++)
            {               
                tempArray[j] = jaggedArray[RowIndex][j];
            }
            tempArray.Sort(true,true); // Ordina i prezzi a scadenza in modo crescente (il primo parametro stable = true preserva l'ordine nel caso siano due numeri uguali nella sequenza)
            if (tempArray.Length >= 2) value = tempArray[1]; // prende il secondo più piccolo
            else value = tempArray[0]; // prende l'unico elemento esistente
            return (value);
        }

        public static double[] CumulativeSums(double[] values)
        {
            if (values == null || values.Length == 0) return new double[0];

            var results = new double[values.Length];
            results[0] = values[0];

            for (var i = 1; i < values.Length; i++)
            {
                results[i] = results[i - 1] + values[i];
            }

            return results;
        }


        /// <summary>
        /// Calcola la deviazione standard a partire da una List di valori 
        /// </summary>
        /// <param name="values"> La lista dei valori da cui ricavare la deviazione standard </param>
        /// <returns></returns>
        public static double StandardDeviation(double[] valori)
        {
            List<double> values = new List<double>();
            values.AddRange(valori);
            double average = values.Average();
            double sumOfDerivation = 0;
            foreach (double value in values)
            {
                sumOfDerivation += (value) * (value);
            }
            double sumOfDerivationAverage = sumOfDerivation / (values.Count - 1);
            return Math.Sqrt(sumOfDerivationAverage - (average * average));
        }

        /// <summary>
        /// Calcola la volatilità dei sottostanti a partire dall'ISIN del certificato
        /// </summary>
        /// <param name="ISIN">ISIN del certificato da cui verranno recuperati i sottostanti</param>
        /// <param name="numPrezziEOD">Il numero di prezzi da considerare</param>
        /// <param name="EnableWarning">Se true, consente di riprodurre un messaggio di warning nel caso onn siano reperibili abbastanza prezzi</param>
        /// <returns>Un array di double con un valore per ogni sottostante</returns>
        public static DataTable ArraytoDatatable(double[,] numbers, string[] columnNames)
        {
            DataTable dt = new DataTable();
            for (int i = 0; i < numbers.GetLength(1); i++)
            {
                //dt.Columns.Add("Column" + (i + 1));
                dt.Columns.Add(columnNames[i]);
            }

            for (var i = 0; i < numbers.GetLength(0); ++i)
            {
                DataRow row = dt.NewRow();
                for (var j = 0; j < numbers.GetLength(1); ++j)
                {
                    row[j] = Math.Round(numbers[i, j],4);
                }
                dt.Rows.Add(row);
            }
            return dt;
        }
        public static DataTable ArrayJaddedtoDatatable(double[][] numbers, string[] columnNames)
        {
            DataTable dt = new DataTable();
            for (int i = 0; i < columnNames.Length; i++)
            {
                dt.Columns.Add(columnNames[i]);
            }

            for (var i = 0; i < numbers.GetLength(0); ++i)
            {
                DataRow row = dt.NewRow();
                for (var j = 0; j < columnNames.Length; ++j)
                {
                    row[j] = Math.Round(numbers[i][j],4);
                }
                dt.Rows.Add(row);
            }
            return dt;
        }
    }
}
