using System.Collections.Generic;
using System.Linq;

namespace MarketData.ValueAtRisk
{
    public class HistoricalVaR
    {
        private int returnDays = 1;

        private HistoricalVaR() { }

        public static VaRResult GetVaR(PortfolioHoldings portfolioHoldings, double percentile, int returnDays = 1)
        {
            VaRResult varResult = new VaRResult();

            // Validate portfolio
            if (portfolioHoldings == null || portfolioHoldings.Count == 0)
            {
                varResult.Success = false;
                varResult.Message = "Portfolio is null or empty.";
                return varResult;
            }

            // Determine the minimum common price history across holdings
            int minPriceCount = int.MaxValue;
            for (int i = 0; i < portfolioHoldings.Count; i++)
            {
                int count = portfolioHoldings[i].Prices.Count;
                if (count < minPriceCount)
                    minPriceCount = count;
            }

            // Enforce minimum observation requirement
            if (minPriceCount < 30) // or whatever threshold you prefer
            {
                varResult.Success = false;
                varResult.Message = $"Insufficient common price history ({minPriceCount} observations).";
                return varResult;
            }

            // Truncate all holdings to the common window
            for (int i = 0; i < portfolioHoldings.Count; i++)
            {
                if (portfolioHoldings[i].Prices.Count > minPriceCount)
                {
                    portfolioHoldings[i].Prices = new MarketDataModel.Prices(
                        portfolioHoldings[i].Prices
                            .Skip(portfolioHoldings[i].Prices.Count - minPriceCount)
                            .ToList()
                    );
                }
            }

            // Calculate total market value and holdings weightings
            double marketValue = portfolioHoldings.GetMarketValue();
            if (marketValue == 0)
            {
                varResult.Success = false;
                varResult.Message = "Portfolio market value is zero.";
                return varResult;
            }

            for (int index = 0; index < portfolioHoldings.Count; index++)
            {
                PortfolioHolding portfolioHolding = portfolioHoldings[index];
                portfolioHolding.Weight = portfolioHolding.MarketValue / marketValue;
            }

            // Calculate weighted returns for the observation period
            portfolioHoldings.SetReturnDays(returnDays);
            int numReturns = portfolioHoldings.Min(p => p.Returns.Length);

            WeightedReturnsWithContribution weightedReturnsWithContribution = new WeightedReturnsWithContribution();
            for (int index = 0; index < numReturns; index++)
            {
                for (int portfolioIndex = 0; portfolioIndex < portfolioHoldings.Count; portfolioIndex++)
                {
                    PortfolioHolding portfolioHolding = portfolioHoldings[portfolioIndex];
                    WeightedReturn weightedReturn = new WeightedReturn(
                        portfolioHolding.Symbol,
                        portfolioHolding.Prices[index].Date,
                        portfolioHolding.Returns[index] * portfolioHolding.Weight
                    );

                    weightedReturnsWithContribution.Add(index, weightedReturn);
                }
            }

            double[] weightedReturns = weightedReturnsWithContribution.GetWeightedReturns();
            List<Contributions> contributionsList = weightedReturnsWithContribution.GetContributions();

            // Organize the weighted returns into bins for percentile access
            BinManager<Contributions> binManager = new BinManager<Contributions>();
            BinResult<Contributions> binResult = binManager.GetVaRReturn(weightedReturns, contributionsList, percentile);
            Contributions contributions = binResult.Item;

            if (contributions == null)
            {
                varResult.Success = false;
                return varResult;
            }

            // Map contributions back to portfolio holdings
            Dictionary<string, PortfolioHolding> portfolioHoldingsBySymbol = new Dictionary<string, PortfolioHolding>();
            foreach (PortfolioHolding portfolioHolding in portfolioHoldings)
            {
                if (!portfolioHoldingsBySymbol.ContainsKey(portfolioHolding.Symbol))
                    portfolioHoldingsBySymbol.Add(portfolioHolding.Symbol, portfolioHolding);
            }

            foreach (Contribution contribution in contributions)
            {
                if (!portfolioHoldingsBySymbol.ContainsKey(contribution.Symbol))
                    continue;

                portfolioHoldingsBySymbol[contribution.Symbol].Contribution = contribution.ContributionValue;
                portfolioHoldingsBySymbol[contribution.Symbol].ContributionDate = contribution.AnalysisDate;
            }

            return new VaRResult(binResult.Value, marketValue * binResult.Value);
        }

        public int ReturnDays
        {
            get => returnDays;
            set => returnDays = value;
        }
    }
}