using System.Diagnostics.Metrics;

namespace LibraryPricer.Payoffs;

public interface IPayoffEvaluator
{
    double Evaluate(double[][] path, PayoffContext context);
}

public class PayoffContext
{
    public int DaysToMaturity;
    public double r;
    public double CapitalValue;
    public double ProtMinVal;
    public double PDI_Barrier;
    public string PDI_Style;
    public double CouponInMemory;
    public double[] DaysToObsYFract;
    public double[] CouponTriggers;
    public double[] CouponValues;
    public double[] AutocallTriggers;
    public double[] AutocallValues;
    public int[] MemoryFlags;
    public string Caso;
    public double PDI_Strike;
    public double FattoreAirbag;
    public double TriggerOneStar;
    public double CAP;
    public int[] DaysToObs;
    public double[] Volatility;
    public int Airbag;
    public int Sigma;
    public int Relief;
    public int TwinWin;
    public int OneStar;
    public double Leva { get; set; }
    public double NominalAmount { get; set; }
    public double[] PricesUL { get; set; }
    public bool EnableDebug { get; set; }

}


public static class PayoffFactory
{
    public static IPayoffEvaluator GetEvaluator(string caso)
    {
        return caso switch
        {
            "Standard" => new PayoffStandard(),
            "Airbag" => new PayoffAirbag(),
            "Sigma" => new PayoffSigma(),
            "Relief" => new PayoffRelief(),
            "TwinWin" => new PayoffTwinWin(),
            "OneStar" => new PayoffOneStar(),
            "Airbag + OneStar" => new PayoffAirbagOneStar(),
            "Sigma + OneStar" => new PayoffSigmaOneStar(),
            "Relief + OneStar" => new PayoffReliefOneStar(),
            "TwinWin + OneStar" => new PayoffTwinWinOneStar(),
            _ => throw new ArgumentException($"Payoff '{caso}' non gestito."),
        };
    }
}


public class PayoffStandard : IPayoffEvaluator
{
    public string LastLabel { get; private set; } = "";

    public double Evaluate(double[][] path, PayoffContext c)
    {
        double memory = c.CouponInMemory;
        double coupons = 0;
        double payoff = 0;

        for (int k = 0; k < c.DaysToObs.Length; k++)
        {
            double min = path[c.DaysToObs[k]].Min();

            // Autocall
            if (min >= c.AutocallTriggers[k])
            {
                payoff = (c.AutocallValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]) + coupons;
                LastLabel = $"Autocall ␦ Trigger={c.AutocallTriggers[k]:F2}, Min={min:F2} ␦ Payoff={payoff:F2}";
                if (c.EnableDebug) Console.WriteLine("DEBUG " + LastLabel);
                return payoff;
            }

            // Cedola
            if (min >= c.CouponTriggers[k])
            {
                coupons += (c.CouponValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]);
                memory = 0;
            }
            else
            {
                memory += c.MemoryFlags[k] * c.CouponValues[k];
            }

            // Scadenza
            if (k == c.DaysToObs.Length - 1)
            {
                double minMat = path[c.DaysToMaturity].Min();    // Worst at maturity
                double minTotal = path.SelectMany(p => p).Min(); // Worst overall
                double barrierCheckValue = (c.PDI_Style == "European") ? minMat : minTotal;

                if (barrierCheckValue >= c.PDI_Barrier)
                {
                    payoff = (c.CapitalValue + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]) + coupons;
                    LastLabel = $"RIMBORSO standard ␦ minMat={minMat:F4}, minTotal={minTotal:F4} ␦ Payoff={payoff:F2}";
                }
                else
                {
                    payoff = Math.Max(c.ProtMinVal, minMat / 100.0) * Math.Exp(-c.r * c.DaysToObsYFract[k]) + coupons;
                    LastLabel = $"PERDITA standard ␦ minMat={minMat:F4}, minTotal={minTotal:F4} ␦ Payoff={payoff:F2}";
                }

                if (c.EnableDebug) Console.WriteLine("DEBUG " + LastLabel);
                return payoff;
            }
        }

        LastLabel = "UNKNOWN";
        if (c.EnableDebug) Console.WriteLine("DEBUG " + LastLabel);
        return 0;
    }
}



public class PayoffAirbag : IPayoffEvaluator
{
    public double Evaluate(double[][] path, PayoffContext c)
    {
        double memory = c.CouponInMemory;
        double coupons = 0;

        for (int k = 0; k < c.DaysToObs.Length; k++)
        {
            double min = path[c.DaysToObs[k]].Min();

            if (min >= c.AutocallTriggers[k])
                return (c.AutocallValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]) + coupons;

            else if (min >= c.CouponTriggers[k])
            {
                coupons += (c.CouponValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]);
                memory = 0;
            }
            else
                memory += c.MemoryFlags[k] * c.CouponValues[k];

            if (k == c.DaysToObs.Length - 1)
            {
                double minMat = path[c.DaysToMaturity].Min();
                double minTotal = path.SelectMany(p => p).Min();

                bool sopraBarriera = (c.PDI_Style == "European" && minMat >= c.PDI_Barrier)
                                  || (c.PDI_Style == "American" && minTotal >= c.PDI_Barrier);

                double payoff;
                if (sopraBarriera)
                    payoff = c.CapitalValue + memory;
                else
                    payoff = Math.Max(c.ProtMinVal, (minMat * c.FattoreAirbag / 100.0));

                return payoff * Math.Exp(-c.r * c.DaysToObsYFract[k]) + coupons;
            }
        }

        return 0;
    }
}

public class PayoffTwinWin : IPayoffEvaluator
{
    public double Evaluate(double[][] path, PayoffContext c)
    {
        double memory = c.CouponInMemory;
        double coupons = 0;

        for (int k = 0; k < c.DaysToObs.Length; k++)
        {
            double min = path[c.DaysToObs[k]].Min();

            // Autocall
            if (min >= c.AutocallTriggers[k])
                return (c.AutocallValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]) + coupons;

            // Coupon pagato
            else if (min >= c.CouponTriggers[k])
            {
                coupons += (c.CouponValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]);
                memory = 0;
            }
            else
            {
                // Coupon in memoria
                memory += c.MemoryFlags[k] * c.CouponValues[k];
            }

            // Ultima osservazione: valutazione payoff finale
            if (k == c.DaysToObs.Length - 1)
            {
                double minMat = path[c.DaysToMaturity].Min();          // Minimo al tempo di maturità
                double minTotal = path.SelectMany(p => p).Min();       // Minimo assoluto di tutto il path

                bool sopraStrike = minMat >= c.PDI_Strike && c.AutocallTriggers[k] == 999;

                // Barriera europea → minMat; americana → minTotal
                bool sopraBarriera = (c.PDI_Style == "European" && minMat >= c.PDI_Barrier) ||
                                     (c.PDI_Style == "American" && minTotal >= c.PDI_Barrier);

                double payoff;

                if (sopraStrike)
                {
                    payoff = memory + Math.Min(minMat / 100.0, c.CAP);
                }
                else if (sopraBarriera)
                {
                    // TwinWin attivo: payoff speculare inverso
                    payoff = (200.0 - minMat) * c.CapitalValue / 100.0 + memory;
                }
                else
                {
                    // Sotto barriera → perdita parziale
                    payoff = Math.Max(c.ProtMinVal, minMat / 100.0) + memory;
                }

                return payoff * Math.Exp(-c.r * c.DaysToObsYFract[k]) + coupons;
            }
        }

        return 0;
    }
}



public class PayoffOneStar : IPayoffEvaluator
{
    public double Evaluate(double[][] path, PayoffContext c)
    {
        double memory = c.CouponInMemory;
        double coupons = 0;

        for (int k = 0; k < c.DaysToObs.Length; k++)
        {
            double min = path[c.DaysToObs[k]].Min();

            // Autocall: rimborso anticipato
            if (min >= c.AutocallTriggers[k])
                return (c.AutocallValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]) + coupons;

            // Coupon trigger
            if (min >= c.CouponTriggers[k])
            {
                coupons += (c.CouponValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]);
                memory = 0;
            }
            else
            {
                memory += c.MemoryFlags[k] * c.CouponValues[k];
            }

            // Scadenza
            if (k == c.DaysToObs.Length - 1)
            {
                double maxMat = path[c.DaysToObs[k]].Max();       // Best
                double minMat = path[c.DaysToMaturity].Min();     // Worst alla scadenza
                double minTotal = path.SelectMany(p => p).Min();  // Worst su tutto il periodo

                bool sopraTrigger = maxMat >= c.TriggerOneStar;

                bool sopraBarriera = (c.PDI_Style == "European" && minMat >= c.PDI_Barrier)
                                  || (c.PDI_Style == "American" && minTotal >= c.PDI_Barrier);

                // === Capitale ===
                double capitale = sopraTrigger || sopraBarriera
                                ? c.CapitalValue
                                : Math.Max(c.ProtMinVal, minMat / 100.0);

                // === Cedole finali solo se supera la barriera cedola ===
                double cedolaFinale = 0;
                if (minMat >= c.CouponTriggers[k])
                    cedolaFinale = memory + c.CouponValues[k];

                return capitale * Math.Exp(-c.r * c.DaysToObsYFract[k])
                     + cedolaFinale * Math.Exp(-c.r * c.DaysToObsYFract[k])
                     + coupons;
            }
        }

        return 0;
    }
}


public class PayoffSigma : IPayoffEvaluator
{
    public double Evaluate(double[][] path, PayoffContext c)
    {
        double memory = c.CouponInMemory;
        double coupons = 0;

        for (int k = 0; k < c.DaysToObs.Length; k++)
        {
            double min = path[c.DaysToObs[k]].Min();

            if (min >= c.AutocallTriggers[k])
                return (c.AutocallValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]) + coupons;

            else if (min >= c.CouponTriggers[k])
            {
                coupons += (c.CouponValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]);
                memory = 0;
            }
            else
                memory += c.MemoryFlags[k] * c.CouponValues[k];

            if (k == c.DaysToObs.Length - 1)
            {
                double minMat = path[c.DaysToMaturity].Min();
                double minTotal = path.SelectMany(p => p).Min();

                bool sopraBarriera =
                    (c.PDI_Style == "European" && minMat >= c.PDI_Barrier) ||
                    (c.PDI_Style == "American" && minTotal >= c.PDI_Barrier);

                double payoff;

                if (sopraBarriera)
                    payoff = c.CapitalValue + memory;
                else
                    payoff = Math.Max(c.ProtMinVal, (minMat + c.PDI_Strike - c.PDI_Barrier) / 100.0);

                return payoff * Math.Exp(-c.r * c.DaysToObsYFract[k]) + coupons;
            }
        }

        return 0;
    }
}


public class PayoffRelief : IPayoffEvaluator
{
    public double Evaluate(double[][] path, PayoffContext c)
    {
        double memory = c.CouponInMemory;
        double coupons = 0;

        for (int k = 0; k < c.DaysToObs.Length; k++)
        {
            double min = path[c.DaysToObs[k]].Min();

            if (min >= c.AutocallTriggers[k])
                return (c.AutocallValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]) + coupons;

            else if (min >= c.CouponTriggers[k])
            {
                coupons += (c.CouponValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]);
                memory = 0;
            }
            else
                memory += c.MemoryFlags[k] * c.CouponValues[k];

            if (k == c.DaysToObs.Length - 1)
            {
                double[] pricesAtMat = path[c.DaysToMaturity];
                double secondMin = pricesAtMat.OrderBy(x => x).Skip(1).FirstOrDefault();
                double minMat = pricesAtMat.Min();
                double minTotal = path.SelectMany(p => p).Min();

                bool sopraBarriera =
                    (c.PDI_Style == "European" && minMat >= c.PDI_Barrier) ||
                    (c.PDI_Style == "American" && minTotal >= c.PDI_Barrier);

                double payoff;

                if (sopraBarriera)
                    payoff = c.CapitalValue + memory;
                else
                    payoff = Math.Max(c.ProtMinVal, secondMin / 100.0);

                return payoff * Math.Exp(-c.r * c.DaysToObsYFract[k]) + coupons;
            }
        }

        return 0;
    }
}



public class PayoffAirbagOneStar : IPayoffEvaluator
{
    public double Evaluate(double[][] path, PayoffContext c)
    {
        double memory = c.CouponInMemory;
        double coupons = 0;

        for (int k = 0; k < c.DaysToObs.Length; k++)
        {
            double min = path[c.DaysToObs[k]].Min();

            // Autocall: uscita anticipata
            if (min >= c.AutocallTriggers[k])
            {
                return (c.AutocallValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]) + coupons;
            }

            // Cedola
            if (min >= c.CouponTriggers[k])
            {
                coupons += (c.CouponValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]);
                memory = 0;
            }
            else
            {
                memory += c.MemoryFlags[k] * c.CouponValues[k];
            }

            // Scadenza
            if (k == c.DaysToObs.Length - 1)
            {
                double maxMat = path[c.DaysToObs[k]].Max();       // Best performer finale
                double minMat = path[c.DaysToMaturity].Min();     // Worst performer finale
                double minTotal = path.SelectMany(p => p).Min();  // Worst performer sull'intero path

                bool sopraTrigger = maxMat >= c.TriggerOneStar;

                // Barriera osservata in stile europeo (minMat) o americano (minTotal)
                bool sopraBarriera = (c.PDI_Style == "European" && minMat >= c.PDI_Barrier)
                                   || (c.PDI_Style == "American" && minTotal >= c.PDI_Barrier);

                double capitale;
                string debugLabel;

                if (sopraTrigger || sopraBarriera)
                {
                    capitale = c.CapitalValue;
                    debugLabel = sopraTrigger ? "ONESTAR" : "BARRIERA";
                }
                else
                {
                    double perc = Math.Max(c.ProtMinVal, minMat / 100.0) * c.FattoreAirbag;
                    capitale = Math.Min(c.CapitalValue, perc);
                    debugLabel = "AIRBAG";
                }

                // Cedola finale solo se worst performer finale ≥ trigger cedola
                double cedolaFinale = 0;
                if (minMat >= c.CouponTriggers[k])
                {
                    cedolaFinale = memory + c.CouponValues[k];
                }

                //Console.WriteLine($"DEBUG minMat={minMat:F4}, maxMat={maxMat:F4} ? {debugLabel}");


                return capitale * Math.Exp(-c.r * c.DaysToObsYFract[k])
                     + cedolaFinale * Math.Exp(-c.r * c.DaysToObsYFract[k])
                     + coupons;
            }
        }

        return 0;
    }
}



public class PayoffSigmaOneStar : IPayoffEvaluator
{
    public double Evaluate(double[][] path, PayoffContext c)
    {
        double memory = c.CouponInMemory;
        double coupons = 0;

        for (int k = 0; k < c.DaysToObs.Length; k++)
        {
            double min = path[c.DaysToObs[k]].Min();

            // Autocall
            if (min >= c.AutocallTriggers[k])
                return (c.AutocallValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]) + coupons;

            // Cedola
            if (min >= c.CouponTriggers[k])
            {
                coupons += (c.CouponValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]);
                memory = 0;
            }
            else
            {
                memory += c.MemoryFlags[k] * c.CouponValues[k];
            }

            // Scadenza
            if (k == c.DaysToObs.Length - 1)
            {
                double maxMat = path[c.DaysToObs[k]].Max();
                double minMat = path[c.DaysToMaturity].Min();
                double minTotal = path.SelectMany(p => p).Min();

                bool sopraTrigger = maxMat >= c.TriggerOneStar;
                bool sopraBarriera = (c.PDI_Style == "European" && minMat >= c.PDI_Barrier)
                                   || (c.PDI_Style == "American" && minTotal >= c.PDI_Barrier);

                double capitale;

                if (sopraTrigger || sopraBarriera)
                {
                    // Capitale protetto
                    capitale = c.CapitalValue;
                }
                else
                {
                    // Sigma fallback logic
                    capitale = Math.Max(c.ProtMinVal, (minMat + c.PDI_Strike - c.PDI_Barrier) / 100.0);
                }

                // Cedola finale solo se worst sopra coupon trigger
                double cedolaFinale = 0;
                if (minMat >= c.CouponTriggers[k])
                {
                    cedolaFinale = memory + c.CouponValues[k];
                }

                return capitale * Math.Exp(-c.r * c.DaysToObsYFract[k])
                     + cedolaFinale * Math.Exp(-c.r * c.DaysToObsYFract[k])
                     + coupons;
            }
        }

        return 0;
    }
}


public class PayoffReliefOneStar : IPayoffEvaluator
{
    public double Evaluate(double[][] path, PayoffContext c)
    {
        double memory = c.CouponInMemory;
        double coupons = 0;

        for (int k = 0; k < c.DaysToObs.Length; k++)
        {
            double min = path[c.DaysToObs[k]].Min();

            // Autocall
            if (min >= c.AutocallTriggers[k])
                return (c.AutocallValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]) + coupons;

            // Coupon
            if (min >= c.CouponTriggers[k])
            {
                coupons += (c.CouponValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]);
                memory = 0;
            }
            else
            {
                memory += c.MemoryFlags[k] * c.CouponValues[k];
            }

            // Scadenza
            if (k == c.DaysToObs.Length - 1)
            {
                double[] pricesAtMat = path[c.DaysToMaturity];
                double secondMin = pricesAtMat.OrderBy(x => x).Skip(1).FirstOrDefault();
                double minMat = pricesAtMat.Min();
                double maxMat = path[c.DaysToObs[k]].Max();
                double minTotal = path.SelectMany(p => p).Min();

                bool sopraTrigger = maxMat >= c.TriggerOneStar;
                bool sopraBarriera = (c.PDI_Style == "European" && minMat >= c.PDI_Barrier)
                                   || (c.PDI_Style == "American" && minTotal >= c.PDI_Barrier);

                double capitale;

                if (sopraTrigger || sopraBarriera)
                {
                    capitale = c.CapitalValue;
                }
                else
                {
                    capitale = Math.Max(c.ProtMinVal, secondMin / 100.0);
                }

                double cedolaFinale = 0;
                if (minMat >= c.CouponTriggers[k])
                {
                    cedolaFinale = memory + c.CouponValues[k];
                }

                return capitale * Math.Exp(-c.r * c.DaysToObsYFract[k])
                     + cedolaFinale * Math.Exp(-c.r * c.DaysToObsYFract[k])
                     + coupons;
            }
        }

        return 0;
    }
}


public class PayoffTwinWinOneStar : IPayoffEvaluator
{
    public double Evaluate(double[][] path, PayoffContext c)
    {
        double memory = c.CouponInMemory;
        double coupons = 0;

        for (int k = 0; k < c.DaysToObs.Length; k++)
        {
            double min = path[c.DaysToObs[k]].Min();

            // Autocall
            if (min >= c.AutocallTriggers[k])
                return (c.AutocallValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]) + coupons;

            // Cedola
            if (min >= c.CouponTriggers[k])
            {
                coupons += (c.CouponValues[k] + memory) * Math.Exp(-c.r * c.DaysToObsYFract[k]);
                memory = 0;
            }
            else
            {
                memory += c.MemoryFlags[k] * c.CouponValues[k];
            }

            // Scadenza
            if (k == c.DaysToObs.Length - 1)
            {
                double maxMat = path[c.DaysToObs[k]].Max();       // Best performer
                double minMat = path[c.DaysToMaturity].Min();     // Worst at maturity
                double minTotal = path.SelectMany(p => p).Min();  // Worst on full path

                bool sopraStrike = minMat >= c.PDI_Strike;
                bool sopraBarriera = (c.PDI_Style == "European" && minMat >= c.PDI_Barrier)
                                  || (c.PDI_Style == "American" && minTotal >= c.PDI_Barrier);

                double capitale;
                string debugLabel;

                if (sopraStrike)
                {
                    capitale = Math.Min(minMat / 100.0, c.CAP);
                    debugLabel = "PARTECIPAZIONE";
                }
                else if (sopraBarriera)
                {
                    capitale = (2 * c.CapitalValue) - (minMat / 100.0);
                    debugLabel = "TWINWIN";
                }
                else
                {
                    if (maxMat >= c.TriggerOneStar)
                    {
                        capitale = c.CapitalValue;
                        debugLabel = "ONESTAR";
                    }
                    else
                    {
                        capitale = Math.Max(c.ProtMinVal, minMat / 100.0);
                        debugLabel = "PERDITA";
                    }
                }

                //Console.WriteLine($"DEBUG minMat={minMat:F4}, maxMat={maxMat:F4} ? {debugLabel}");

                double cedolaFinale = 0;
                if (minMat >= c.CouponTriggers[k])
                {
                    cedolaFinale = memory + c.CouponValues[k];
                }

                return capitale * Math.Exp(-c.r * c.DaysToObsYFract[k])
                     + cedolaFinale * Math.Exp(-c.r * c.DaysToObsYFract[k])
                     + coupons;
            }
        }

        return 0;
    }
}


