using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Platform.Storage;
using CommunityToolkit.Mvvm.Input;
using Eremex.AvaloniaUI.Controls;
using MarketData;
using MarketData.DataAccess;
using MarketData.Generator;
using MarketData.Generator.CMMomentum;
using MarketData.Generator.Interface;
using MarketData.Generator.Model;
using MarketData.MarketDataModel;
using MarketData.Utils;
using PortfolioManager.DataSeriesViewModels;
using PortfolioManager.Dialogs;
using PortfolioManager.Models;
using PortfolioManager.UIUtils;
using Position = MarketData.Generator.CMMomentum.Position;

namespace PortfolioManager.ViewModels
{
  public partial class CMMomentumViewModel : WorkspaceViewModel
  {
    private bool isBusy = false;
    private CMSessionParams sessionParams;
    private CMParams cmParams;
    private ObservableCollection<String> nvpDictionaryKeys = default;
    private NVPDictionary nvpDictionary = null;
    private String selectedParameter = null;
    private String pathFileName;
    private ModelPerformanceSeries modelPerformanceSeries = null;
    private ModelStatistics modelStatistics = null;
    private bool showAsGainLoss = true;
    private CMPositionModelCollection positions = null;
    private CMPositionModel selectedPosition = null;
    private bool showMarkers = false;

    public CMMomentumViewModel()
    {
      DisplayName = "CMMomentum Model";
      PropertyChanged += OnViewModelPropertyChanged;
    }

    protected override void OnDispose()
    {
      MDTrace.WriteLine(LogLevel.DEBUG,$"Dispose CMMomentumViewModel");
      base.OnDispose();
    }    

    private void OnViewModelPropertyChanged(object sender, PropertyChangedEventArgs eventArgs)
    {
    }

    public override String Title
    {
      get
      {
        return DisplayName;
      }
    }

    public override String DisplayName
    {
      get
      {
        if (null == pathFileName) return "CMMomentum Model";
        String pureFileName = Utility.GetFileNameNoExtension(pathFileName);
        return "CMMomentum Model (" + pureFileName + ")";
      }
    }

    public bool IsBusy
    {
      get
      {
        return isBusy;
      }
      set
      {
        isBusy = value;
        base.OnPropertyChanged("IsBusy");
      }
    }

    public bool ShowMarkers
    {
      get
      {
        return showMarkers;
      }
      set
      {
        showMarkers = value;
        base.OnPropertyChanged("ShowMarkers");
      }
    }

    public String TradeDate
    {
      get
      {
        if (null == sessionParams) return Constants.CONST_DASHES;
        DateGenerator dateGenerator = new DateGenerator();
        return Utility.DateTimeToStringMMSDDSYYYY(dateGenerator.FindPrevBusinessDay(sessionParams.TradeDate));
      }
    }

    public ObservableCollection<String> Parameters
    {
      get { return nvpDictionaryKeys; }
    }

    public String SelectedParameter
    {
      get
      {
        return selectedParameter;
      }
      set
      {
        selectedParameter = value;
        base.OnPropertyChanged("SelectedParameter");
        base.OnPropertyChanged("ParameterValue");
      }
    }

    public String ParameterValue
    {
      get
      {
        if (null == nvpDictionary || null == nvpDictionaryKeys || null == selectedParameter) return null;
        return nvpDictionary[selectedParameter].Value;
      }
    }

    public String CashBalance
    {
      get
      {
        if (null == sessionParams) return Constants.CONST_DASHES;
        return Utility.FormatCurrency(sessionParams.CashBalance);
      }
    }

    public ObservableCollection<MenuItem> PositionsMenuItems
    {
      get
      {
        ObservableCollection<MenuItem> collection = new ObservableCollection<MenuItem>();
        collection.Add(new MenuItem() { Header = "Bollinger Bands...", Command = BollingerBandsCommand, StaysOpenOnClick = false });
        collection.Add(new MenuItem() { Header = "Close Position...", Command = CloseCommand, StaysOpenOnClick = false });
        collection.Add(new MenuItem() { Header = "Edit Position...", Command = EditCommand, StaysOpenOnClick = false });
        collection.Add(new MenuItem() { Header = "Add To WatchList", Command = AddToWatchListCommand, StaysOpenOnClick = false });
        collection.Add(new MenuItem() { Header = "Remove From WatchList", Command = RemoveFromWatchListCommand, StaysOpenOnClick = false });
        return collection;
      }
    }

    public bool ReloadEnabled
    {
      get
      {
        return !String.IsNullOrEmpty(pathFileName);
      }
    }

    public CMPositionModelCollection AllPositions
    {
      get { return positions; }
    }

    public String NonTradeableCash
    {
      get
      {
        if (null == sessionParams) return Constants.CONST_DASHES;
        return Utility.FormatCurrency(sessionParams.NonTradeableCash);
      }
    }

    public String PercentButtonText
    {
      get
      {
        if (!showAsGainLoss) return "Show $";
        else return "Show %";
      }
    }

    public String ModelExpectation
    {
      get
      {
        if (null == modelStatistics) return "";
        return Utility.FormatNumber(modelStatistics.Expectancy, 2);
      }
    }

    public IBrush ExpectationColor
    {
      get
      {
        if (null == modelStatistics) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
        if (modelStatistics.Expectancy > 0.00) return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Black);
        return BrushCollection.GetContextBrush(BrushCollection.BrushColor.Red);
      }
    }

    public CMPositionModel SelectedPosition
    {
      get
      {
        return selectedPosition;
      }
      set
      {
        selectedPosition = value;
        base.OnPropertyChanged("SelectedPosition");
      }
    }

    public CompositeDataSource Data
    {
      get
      {
        if (null == modelPerformanceSeries) return GainLossModel.Empty();
        CompositeDataSource compositeDataSource = null;
        compositeDataSource = GainLossModel.GainLoss(modelPerformanceSeries, showAsGainLoss);
        return compositeDataSource;
      }
    }

    public String GraphTitle
    {
      get
      {
        if (null == sessionParams || null == modelPerformanceSeries) return "";
        StringBuilder sb = new StringBuilder();
        Positions allPositions = sessionParams.GetCombinedPositions();
        DateTime minDate = allPositions.Min(x => x.PurchaseDate);
        DateTime maxDate = PricingDA.GetLatestDate();

        if (modelPerformanceSeries.Count < 2)
        {
          sb.Append(showAsGainLoss ? "$ GainLoss" : "% Return");
          sb.Append(" ");
          sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
          sb.Append(showAsGainLoss ? Utility.FormatCurrency(modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumulativeGainLoss) : Utility.FormatPercent(modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumProdMinusOne));
          return sb.ToString();
        }
        if (showAsGainLoss)
        {
          double latestGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumulativeGainLoss;
          double change = modelPerformanceSeries[modelPerformanceSeries.Count - 1].GainLossDOD;
          sb.Append("$ GainLoss");
          sb.Append(" ");
          sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
          sb.Append(" ");
          sb.Append(Utility.FormatCurrency(latestGainLoss));
          sb.Append(",");
          sb.Append(" ");
          sb.Append(change > 0.00 ? "+" : "").Append(Utility.FormatCurrency(change));
        }
        else
        {
          double latestCumGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 1].CumProdMinusOne;
          double prevCumGainLoss = modelPerformanceSeries[modelPerformanceSeries.Count - 2].CumProdMinusOne;
          double change = latestCumGainLoss - prevCumGainLoss;
          sb.Append("% Return");
          sb.Append(" ");
          sb.Append("(").Append(minDate.ToShortDateString()).Append("-").Append(maxDate.ToShortDateString()).Append(")");
          sb.Append(" ");
          sb.Append(Utility.FormatPercent(latestCumGainLoss));
          sb.Append(",");
          sb.Append(" ");
          sb.Append(change > 0.00 ? "+" : "").Append(Utility.FormatPercent(change));
        }
        return sb.ToString();
      }
    }

    // *********************************************************** R E L A Y  ***************************************************************

    [RelayCommand(CanExecute = nameof(CanExecuteBollingerBands))]
    public async Task BollingerBands()
    {
      await ExecuteBollingerBands();
    }

    public bool CanExecuteBollingerBands()
    {
      if (null == selectedPosition || null == selectedPosition.Symbol) return false;
      return true;
    }    

    [RelayCommand]
    public async Task LoadFile()
    {
      await LoadTradeFile();
    }

    [RelayCommand]
    public async Task Reload()
    {
      await ReloadTradeFile();
    }

    [RelayCommand(CanExecute = nameof(CanClosePosition))]
    public async Task Close()
    {
      await OpenCloseDialog();
    }

    [RelayCommand(CanExecute = nameof(CanEdit))]
    public async Task Edit()
    {
      await OpenEditDialog();
    }

    [RelayCommand]
    public void ToggleReturnOrPercent()
    {
      HandleToggleReturnOrPercent();
    }

    [RelayCommand(CanExecute = nameof(CanAddToWatchList))]
    public async Task AddToWatchList()
    {
      WatchListDA.AddToWatchList(selectedPosition.Symbol);
      MxMessageBox.Show(GetTopLevelWindow(), $"Added {selectedPosition.Symbol} to WatchList", "Add To WatchList");
      await Task.FromResult(true);
    }

    [RelayCommand(CanExecute = nameof(CanRemoveFromWatchList))]
    public async Task RemoveFromWatchList()
    {
      WatchListDA.RemoveFromWatchList(selectedPosition.Symbol);
      MxMessageBox.Show(GetTopLevelWindow(), $"Removed {selectedPosition.Symbol} from WatchList", "Remove From WatchList");
      await Task.FromResult(true);
    }

    // ***************************************************************************************************************************************

    public async Task ExecuteBollingerBands()
    {
      SaveParameters saveParams = SaveParameters.Parse("Type,PortfolioManager.ViewModels.BollingerBandViewModel,SelectedSymbol," + selectedPosition.Symbol + ",SelectedWatchList,{All},SelectedDayCount,360");
      saveParams.Referer=this;
      WorkspaceInstantiator.Invoke(saveParams);      
      await Task.FromResult(true);
    }

    public async Task OpenCloseDialog()
    {
      IPurePosition clonedPosition = Position.Clone(selectedPosition.Position);
      ClosePositionDialog dialog = new ClosePositionDialog();
      ClosePositionDialogViewModel closePositionViewModel = new ClosePositionDialogViewModel(dialog, clonedPosition);
      dialog.DataContext = closePositionViewModel;
      await dialog.ShowDialog(GetTopLevelWindow());
      if (!closePositionViewModel.IsSuccess) return;
      CMMomentumBacktest momentumModel = new CMMomentumBacktest();
      if (!momentumModel.ClosePosition(clonedPosition.Symbol, clonedPosition.PurchaseDate, clonedPosition.SellDate, clonedPosition.CurrentPrice, pathFileName))
      {
        MxMessageBox.Show(GetTopLevelWindow(), "Failed to close the position, check log for details.", "Close Position");
        return;
      }
      String strMessage = String.Format("Closed position for {0}, Purchase Date:{1}, Sell Date{2}, Current Price:{3}. Saved to {4}.  A backup was created.",
      clonedPosition.Symbol,
      clonedPosition.PurchaseDate.ToShortDateString(),
      clonedPosition.SellDate.ToShortDateString(),
      Utility.FormatCurrency(clonedPosition.CurrentPrice),
      pathFileName);
      MxMessageBox.Show(GetTopLevelWindow(), strMessage, "Close Position");
      LoadSessionFile();
      await Task.FromResult(true);
    }

    public async Task OpenEditDialog()
    {
      EditPositionDialogNoStop dialog = new EditPositionDialogNoStop();
      Position clonedPosition = Position.Clone(selectedPosition.Position);
      EditPositionDialogNoStopViewModel editPositionViewModel = new EditPositionDialogNoStopViewModel(dialog, clonedPosition);
      dialog.DataContext = editPositionViewModel;
      await dialog.ShowDialog(GetTopLevelWindow());
      GetTopLevelWindow().BringIntoView();
      if (!editPositionViewModel.IsSuccess) return;
      CMMomentumBacktest momentumBacktest = new CMMomentumBacktest();
      if (!momentumBacktest.EditPosition(clonedPosition.Symbol, clonedPosition.PurchaseDate, clonedPosition.PurchasePrice, pathFileName))
      {
        MxMessageBox.Show(GetTopLevelWindow(), "Failed to edit the position, check log for details.", "Edit Position");
        return;
      }
      selectedPosition.PurchaseDate = clonedPosition.PurchaseDate;
      selectedPosition.PurchasePrice = clonedPosition.PurchasePrice;
      String strMessage = String.Format("Edited Position for {0} Purchase Date:{1} Purchase Price:{2}. A backup was created.",
      selectedPosition.Symbol, selectedPosition.PurchaseDate.ToShortDateString(), Utility.FormatCurrency(selectedPosition.PurchasePrice));
      MxMessageBox.Show(GetTopLevelWindow(), strMessage, "Edit Position");
      LoadSessionFile();
    }

    private void HandleToggleReturnOrPercent()
    {
      showAsGainLoss = !showAsGainLoss;
      base.OnPropertyChanged("Data");
      base.OnPropertyChanged("PercentButtonText");
      base.OnPropertyChanged("GraphTitle");
    }

    public bool CanAddToWatchList()
    {
      if (null == selectedPosition) return false;
      return WatchListDA.IsInWatchList(selectedPosition.Symbol) ? false : true;
    }

    public bool CanClosePosition()
    {
      if (null == selectedPosition || null == selectedPosition.Symbol) return false;
      return true;
    }

    public bool CanEdit()
    {
      if (null == selectedPosition || null == selectedPosition.Symbol || !Utility.IsEpoch(selectedPosition.SellDate)) return false;
      return true;
    }

    public bool CanRemoveFromWatchList()
    {
      if (null == selectedPosition) return false;
      if (!WatchListDA.IsInWatchList(selectedPosition.Symbol)) return false;
      PortfolioTrades portfolioTrades = PortfolioDA.GetOpenTrades();
      if (portfolioTrades.Any(x => x.Symbol.Equals(selectedPosition.Symbol))) return false;
      return true;
    }

    public async Task ReloadTradeFile()
    {
      LoadSessionFile();
      await Task.FromResult(true);
    }

    public async Task LoadTradeFile()
    {
      TopLevel topLevel = TopLevel.GetTopLevel(GetTopLevelWindow());
      IReadOnlyList<IStorageFile> files = await topLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions
      {
        Title = "Open Trade File",
        AllowMultiple = false,
      });
      IStorageFile storageFile = files.FirstOrDefault();
      if (null == storageFile) return;
      Uri uri = storageFile.Path;
      pathFileName = uri.LocalPath;
      if (null == pathFileName) return;
      if (!CMSessionManager.IsValidSessionFile(pathFileName))
      {
        pathFileName = null;
      }
      else LoadSessionFile();
    }

    public bool LoadSessionFile()
    {
      IsBusy = true;

      Task workerTask = Task.Factory.StartNew(() =>
      {
        try
        {
          if (!CMSessionManager.IsValidSessionFile(pathFileName)) return false;
          sessionParams = CMSessionManager.RestoreSession(pathFileName);
          if (null == sessionParams) { MxMessageBox.Show(GetTopLevelWindow(), String.Format("Unable to open {0}", pathFileName)); pathFileName = null; return false; }
          modelStatistics = CMMomentumBacktest.GetModelStatistics(sessionParams);
          modelPerformanceSeries = CMMomentumBacktest.GetModelPerformance(sessionParams);
          cmParams = sessionParams.CMParams;
          NVPCollection nvpCollection = sessionParams.CMParams.ToNVPCollection();
          nvpDictionary = nvpCollection.ToDictionary();
          List<String> dictionaryKeys = new List<String>(nvpDictionary.Keys);
          dictionaryKeys.Sort();
          nvpDictionaryKeys = new ObservableCollection<String>(dictionaryKeys);
          selectedParameter = nvpDictionaryKeys[0];
          positions = new CMPositionModelCollection();
          positions.Add(sessionParams.ActivePositions);
          positions.Add(sessionParams.AllPositions);
          UpdatePositionPrices(false);
          UpdatePositionRSI3(true);
          RunPerformance();
          return true;
        }
        catch (Exception exception)
        {
          MxMessageBox.Show(GetTopLevelWindow(), String.Format("Exception {0}", exception.ToString()), "Error");
          return false;
        }
      });
      workerTask.ContinueWith(continuation =>
      {
        IsBusy = false;
        base.OnPropertyChanged("Parameters");
        base.OnPropertyChanged("SelectedParameter");
        base.OnPropertyChanged("ParameterValue");
        base.OnPropertyChanged("Title");
        base.OnPropertyChanged("DisplayName");
        base.OnPropertyChanged("AllPositions");
        //        base.OnPropertyChanged("CanMonitor");
        base.OnPropertyChanged("CashBalance");
        base.OnPropertyChanged("NonTradeableCash");
        base.OnPropertyChanged("ModelExpectation");
        base.OnPropertyChanged("ExpectationColor");
        UpdateTooltipProperties();
      });
      return true;
    }

    private void UpdateTooltipProperties()
    {
      base.OnPropertyChanged("ExpectationDescription");
      base.OnPropertyChanged("BetaDescription");
      base.OnPropertyChanged("CompanyDescriptionSelectedPosition");
    }

    private void UpdatePositionPrices(bool change = true)
    {
      try
      {
        DateTime today = DateTime.Now;
        if (null == positions || 0 == positions.Count) return;
        List<String> symbols = (from CMPositionModel position in positions where position.IsActivePosition select position.Symbol).Distinct().ToList();
        foreach (String symbol in symbols)
        {
          var selectedPositions = (from CMPositionModel position in positions where position.IsActivePosition && position.Symbol.Equals(symbol) select position);
          foreach (CMPositionModel selectedPosition in selectedPositions)
          {
            Price price = PricingDA.GetPrice(symbol);
            if (null == price) continue;
            selectedPosition.CurrentPrice = price.Close;
            selectedPosition.LastUpdated = price.Date.Date < today.Date ? price.Date : today;
          }
        }
        if (change) positions.OnCollectionChanged();
      }
      catch (Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString());
      }
    }

    private void UpdatePositionRSI3(bool change = true)
    {
      try
      {
        if (null == positions || 0 == positions.Count) return;
        List<String> symbols = (from CMPositionModel position in positions where position.IsActivePosition select position.Symbol).Distinct().ToList();
        foreach (String symbol in symbols)
        {
          var selectedPositions = (from CMPositionModel position in positions where position.IsActivePosition && position.Symbol.Equals(symbol) select position);
          foreach (CMPositionModel selectedPosition in selectedPositions)
          {
            RSICollection rsiCollection = RSIGenerator.GenerateRSI(symbol, 30, 3);
            if (null == rsiCollection || 0 == rsiCollection.Count) continue;
            selectedPosition.RSI3 = rsiCollection[rsiCollection.Count - 1].RSI;
          }
        }
        if (change) positions.OnCollectionChanged();
      }
      catch (Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG, exception.ToString());
      }
    }

    private void RunPerformance()
    {
      if (null == sessionParams) return;
      modelPerformanceSeries = CMMomentumBacktest.GetModelPerformance(sessionParams);
      base.OnPropertyChanged("Data");
      base.OnPropertyChanged("GraphTitle");
    }

    // ********************************************************** P E R S I S T E N C E ******************************************************

    public override bool CanPersist()
    {
      return true;
    }

    public override SaveParameters GetSaveParameters()
    {
      SaveParameters saveParams = new SaveParameters();
      if (null == pathFileName) return null;
      saveParams.Add(new KeyValuePair<String, String>("Type", GetType().Namespace + "." + GetType().Name));
      saveParams.Add(new KeyValuePair<String, String>("PathFileName", pathFileName));
      return saveParams;
    }

    public override void SetSaveParameters(SaveParameters saveParameters)
    {
      try
      {
        pathFileName = (from KeyValuePair<String, String> item in saveParameters where item.Key.Equals("PathFileName") select item).FirstOrDefault().Value;
        if (!LoadSessionFile()) pathFileName = null;
      }
      catch (Exception exception)
      {
        MDTrace.WriteLine(LogLevel.DEBUG, String.Format("Exception:{0}", exception.ToString()));
      }
    }

    // ************************************************** T O O L T I P S  *************************************************

    public String ExpectationDescription
    {
      get
      {
        if (null == modelStatistics) return "";
        StringBuilder sb = new StringBuilder();
        sb.Append("Expectancy is (percentage of winning trades * average gain) / (percentage of losing trades * average loss).").Append("\n");
        sb.Append("Total Trades : ").Append(modelStatistics.TotalTrades).Append("\n");
        sb.Append("Winning Trades : ").Append(modelStatistics.WinningTrades).Append("\n");
        sb.Append("Losing Trades : ").Append(modelStatistics.LosingTrades).Append("\n");
        sb.Append("Winning Trades : ").Append(Utility.FormatNumber(modelStatistics.WinningTradesPercent, 2)).Append("%").Append("\n");
        sb.Append("Losing Trades : ").Append(Utility.FormatNumber(modelStatistics.LosingTradesPercent, 2)).Append("%").Append("\n");
        sb.Append("Average Winning Trade Gain : ").Append(Utility.FormatNumber(modelStatistics.AverageWinningTradePercentGain, 2)).Append("%").Append("\n");
        sb.Append("Average Losing Trade Loss : ").Append(Utility.FormatNumber(modelStatistics.AverageLosingTradePercentLoss, 2)).Append("%").Append("\n");
        sb.Append("Expectancy : ").Append(Utility.FormatNumber(modelStatistics.Expectancy, 2)).Append("\n");
        sb.Append("\n");
        sb.Append("Maintain a positive Expectancy and you're a winner.");
        sb.Append("\n");
        sb.Append("The calculations are based on closed positions.");
        return sb.ToString();
      }
    }

    public String BetaDescription
    {
      get
      {
        if (null == selectedPosition || null == selectedPosition.Symbol) return "No row selected.";
        return "A beta of less than 1 means that the security is theoretically less volatile than the market. A beta of greater than 1 indicates that the security's price is theoretically more volatile than the market. For example, if a stock's beta is 1.2, it's theoretically 20% more volatile than the market.";
      }
    }
    
    public String CompanyDescriptionSelectedPosition
    {
      get
      {
        if (null == selectedPosition || null == selectedPosition.Symbol) return "No row selected.";
        CompanyProfile companyProfile = CompanyProfileDA.GetCompanyProfile(selectedPosition.Symbol);
        if (null == companyProfile || null == companyProfile.Description || "".Equals(companyProfile.Description)) return "No description found.";
        StringBuilder sb = new StringBuilder();
        sb.Append(companyProfile.Symbol).Append(" - ").Append(companyProfile.CompanyName).Append("\n");
        sb.Append(companyProfile.Sector).Append("/").Append(companyProfile.Industry).Append("\n").Append(companyProfile.Description);
        return sb.ToString();
      }
    }    
  }
}
