package report

import (
	"fmt"

	"github.com/shopspring/decimal"
	"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/statistics"
	gctcommon "github.com/thrasher-corp/gocryptotrader/common"
	"github.com/thrasher-corp/gocryptotrader/currency"
	"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
)

// createUSDTotalsChart used for creating a chart in the HTML report
// to show how much the overall assets are worth over time
func createUSDTotalsChart(items []statistics.ValueAtTime, stats []statistics.FundingItemStatistics) (*Chart, error) {
	if items == nil {
		return nil, fmt.Errorf("%w missing values at time", gctcommon.ErrNilPointer)
	}
	if stats == nil {
		return nil, fmt.Errorf("%w missing funding item statistics", gctcommon.ErrNilPointer)
	}
	response := &Chart{
		AxisType: "logarithmic",
	}
	usdTotalChartPlot := make([]LinePlot, len(items))
	for i := range items {
		usdTotalChartPlot[i] = LinePlot{
			Value:     items[i].Value.InexactFloat64(),
			UnixMilli: items[i].Time.UTC().UnixMilli(),
		}
	}
	response.Data = append(response.Data, ChartLine{
		Name:      "Total USD value",
		LinePlots: usdTotalChartPlot,
	})

	for i := range stats {
		var plots []LinePlot
		if stats[i].ReportItem.AppendedViaAPI {
			continue
		}
		for j := range stats[i].ReportItem.Snapshots {
			if stats[i].ReportItem.Snapshots[j].Available.IsZero() {
				response.ShowZeroDisclaimer = true
			}
			plots = append(plots, LinePlot{
				Value:     stats[i].ReportItem.Snapshots[j].USDValue.InexactFloat64(),
				UnixMilli: stats[i].ReportItem.Snapshots[j].Time.UTC().UnixMilli(),
			})
		}
		response.Data = append(response.Data, ChartLine{
			Name:      fmt.Sprintf("%v %v %v USD value", stats[i].ReportItem.Exchange, stats[i].ReportItem.Asset, stats[i].ReportItem.Currency),
			LinePlots: plots,
		})
	}

	return response, nil
}

// createHoldingsOverTimeChart used for creating a chart in the HTML report
// to show how many holdings of each type was held over the time of backtesting
func createHoldingsOverTimeChart(stats []statistics.FundingItemStatistics) (*Chart, error) {
	if stats == nil {
		return nil, fmt.Errorf("%w missing funding item statistics", gctcommon.ErrNilPointer)
	}
	response := &Chart{
		AxisType: "logarithmic",
	}
	for i := range stats {
		var plots []LinePlot
		if stats[i].ReportItem.AppendedViaAPI {
			continue
		}
		for j := range stats[i].ReportItem.Snapshots {
			if stats[i].ReportItem.Snapshots[j].Available.IsZero() {
				response.ShowZeroDisclaimer = true
			}
			plots = append(plots, LinePlot{
				UnixMilli: stats[i].ReportItem.Snapshots[j].Time.UTC().UnixMilli(),
				Value:     stats[i].ReportItem.Snapshots[j].Available.InexactFloat64(),
			})
		}
		response.Data = append(response.Data, ChartLine{
			Name:      fmt.Sprintf("%v %v %v holdings", stats[i].ReportItem.Exchange, stats[i].ReportItem.Asset, stats[i].ReportItem.Currency),
			LinePlots: plots,
		})
	}

	return response, nil
}

// createPNLCharts shows a running history of all realised and unrealised PNL values
// over time
func createPNLCharts(items map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) (*Chart, error) {
	if items == nil {
		return nil, fmt.Errorf("%w missing currency pair statistics", gctcommon.ErrNilPointer)
	}
	response := &Chart{
		AxisType: "linear",
	}
	for exch, assetMap := range items {
		for item, baseMap := range assetMap {
			for b, quoteMap := range baseMap {
				for q, result := range quoteMap {
					id := fmt.Sprintf("%v %v %v%v",
						exch,
						item,
						b,
						q)
					uPNLName := fmt.Sprintf("%v Unrealised PNL", id)
					rPNLName := fmt.Sprintf("%v Realised PNL", id)

					unrealisedPNL := ChartLine{Name: uPNLName}
					realisedPNL := ChartLine{Name: rPNLName}
					for i := range result.Events {
						if result.Events[i].PNL != nil {
							realisedPNL.LinePlots = append(realisedPNL.LinePlots, LinePlot{
								Value:     result.Events[i].PNL.GetRealisedPNL().PNL.InexactFloat64(),
								UnixMilli: result.Events[i].Time.UnixMilli(),
							})
							unrealisedPNL.LinePlots = append(unrealisedPNL.LinePlots, LinePlot{
								Value:     result.Events[i].PNL.GetUnrealisedPNL().PNL.InexactFloat64(),
								UnixMilli: result.Events[i].Time.UnixMilli(),
							})
						}
					}
					if len(unrealisedPNL.LinePlots) == 0 || len(realisedPNL.LinePlots) == 0 {
						continue
					}
					response.Data = append(response.Data, unrealisedPNL, realisedPNL)
				}
			}
		}
	}
	return response, nil
}

// createFuturesSpotDiffChart highlights the difference in futures and spot prices
// over time
func createFuturesSpotDiffChart(items map[string]map[asset.Item]map[*currency.Item]map[*currency.Item]*statistics.CurrencyPairStatistic) (*Chart, error) {
	if items == nil {
		return nil, fmt.Errorf("%w missing currency pair statistics", gctcommon.ErrNilPointer)
	}
	currs := make(map[currency.Pair]linkCurrencyDiff)
	response := &Chart{
		AxisType: "linear",
	}

	for _, assetMap := range items {
		for item, baseMap := range assetMap {
			for b, quoteMap := range baseMap {
				for q, result := range quoteMap {
					cp := currency.NewPair(b.Currency(), q.Currency())
					if item.IsFutures() {
						p := result.UnderlyingPair.Format(currency.EMPTYFORMAT)
						diff, ok := currs[p]
						if !ok {
							diff = linkCurrencyDiff{}
						}
						diff.FuturesPair = cp
						diff.SpotPair = p
						diff.FuturesEvents = result.Events
						currs[p] = diff
					} else {
						p := cp.Format(currency.EMPTYFORMAT)
						diff, ok := currs[p]
						if !ok {
							diff = linkCurrencyDiff{}
						}
						diff.SpotEvents = result.Events
						currs[p] = diff
					}
				}
			}
		}
	}

	for i := range currs {
		if currs[i].FuturesEvents == nil || currs[i].SpotEvents == nil {
			continue
		}
		if len(currs[i].SpotEvents) != len(currs[i].FuturesEvents) {
			continue
		}
		line := ChartLine{
			Name: fmt.Sprintf("%v %v diff %%", currs[i].FuturesPair, currs[i].SpotPair),
		}
		for j := range currs[i].SpotEvents {
			spotPrice := currs[i].SpotEvents[j].DataEvent.GetClosePrice()
			futuresPrice := currs[i].FuturesEvents[j].DataEvent.GetClosePrice()
			diff := futuresPrice.Sub(spotPrice).Div(spotPrice).Mul(decimal.NewFromInt(100))
			line.LinePlots = append(line.LinePlots, LinePlot{
				Value:     diff.InexactFloat64(),
				UnixMilli: currs[i].SpotEvents[j].Time.UnixMilli(),
			})
		}
		response.Data = append(response.Data, line)
	}
	return response, nil
}
