package statistics

import (
	"errors"
	"testing"
	"time"

	"github.com/thrasher-corp/gocryptotrader/backtester/common"
	"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/compliance"
	"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/holdings"
	"github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/statistics/currencystatistics"
	"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event"
	"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/fill"
	"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/kline"
	"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order"
	"github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal"
	"github.com/thrasher-corp/gocryptotrader/currency"
	"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
	gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline"
	gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order"
)

const testExchange = "binance"

func TestReset(t *testing.T) {
	s := Statistic{
		TotalOrders: 1,
	}
	s.Reset()
	if s.TotalOrders != 0 {
		t.Error("expected 0")
	}
}

func TestAddDataEventForTime(t *testing.T) {
	tt := time.Now()
	exch := testExchange
	a := asset.Spot
	p := currency.NewPair(currency.BTC, currency.USDT)
	s := Statistic{}
	err := s.SetupEventForTime(nil)
	if !errors.Is(err, common.ErrNilEvent) {
		t.Errorf("expected: %v, received %v", common.ErrNilEvent, err)
	}
	err = s.SetupEventForTime(&kline.Kline{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt,
			Interval:     gctkline.OneDay,
			CurrencyPair: p,
			AssetType:    a,
		},
		Open:   1337,
		Close:  1337,
		Low:    1337,
		High:   1337,
		Volume: 1337,
	})
	if err != nil {
		t.Error(err)
	}
	if s.ExchangeAssetPairStatistics == nil {
		t.Error("expected not nil")
	}
	if len(s.ExchangeAssetPairStatistics[exch][a][p].Events) != 1 {
		t.Error("expected 1 event")
	}
}

func TestAddSignalEventForTime(t *testing.T) {
	tt := time.Now()
	exch := testExchange
	a := asset.Spot
	p := currency.NewPair(currency.BTC, currency.USDT)
	s := Statistic{}
	err := s.SetEventForOffset(nil)
	if !errors.Is(err, common.ErrNilEvent) {
		t.Errorf("expected: %v, received %v", common.ErrNilEvent, err)
	}
	err = s.SetEventForOffset(&signal.Signal{})
	if !errors.Is(err, errExchangeAssetPairStatsUnset) {
		t.Errorf("expected: %v, received %v", errExchangeAssetPairStatsUnset, err)
	}
	s.setupMap(exch, a)
	s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[currency.Pair]*currencystatistics.CurrencyStatistic)
	err = s.SetEventForOffset(&signal.Signal{})
	if !errors.Is(err, errCurrencyStatisticsUnset) {
		t.Errorf("expected: %v, received %v", errCurrencyStatisticsUnset, err)
	}

	err = s.SetupEventForTime(&kline.Kline{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt,
			Interval:     gctkline.OneDay,
			CurrencyPair: p,
			AssetType:    a,
		},
		Open:   1337,
		Close:  1337,
		Low:    1337,
		High:   1337,
		Volume: 1337,
	})
	if err != nil {
		t.Error(err)
	}
	err = s.SetEventForOffset(&signal.Signal{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt,
			Interval:     gctkline.OneDay,
			CurrencyPair: p,
			AssetType:    a,
		},
		ClosePrice: 1337,
		Direction:  gctorder.Buy,
	})
	if err != nil {
		t.Error(err)
	}
}

func TestAddExchangeEventForTime(t *testing.T) {
	tt := time.Now()
	exch := testExchange
	a := asset.Spot
	p := currency.NewPair(currency.BTC, currency.USDT)
	s := Statistic{}
	err := s.SetEventForOffset(nil)
	if !errors.Is(err, common.ErrNilEvent) {
		t.Errorf("expected: %v, received %v", common.ErrNilEvent, err)
	}
	err = s.SetEventForOffset(&order.Order{})
	if !errors.Is(err, errExchangeAssetPairStatsUnset) {
		t.Errorf("expected: %v, received %v", errExchangeAssetPairStatsUnset, err)
	}
	s.setupMap(exch, a)
	s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[currency.Pair]*currencystatistics.CurrencyStatistic)
	err = s.SetEventForOffset(&order.Order{})
	if !errors.Is(err, errCurrencyStatisticsUnset) {
		t.Errorf("expected: %v, received %v", errCurrencyStatisticsUnset, err)
	}

	err = s.SetupEventForTime(&kline.Kline{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt,
			Interval:     gctkline.OneDay,
			CurrencyPair: p,
			AssetType:    a,
		},
		Open:   1337,
		Close:  1337,
		Low:    1337,
		High:   1337,
		Volume: 1337,
	})
	if err != nil {
		t.Error(err)
	}
	err = s.SetEventForOffset(&order.Order{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt,
			Interval:     gctkline.OneDay,
			CurrencyPair: p,
			AssetType:    a,
		},
		ID:        "1337",
		Direction: gctorder.Buy,
		Status:    gctorder.New,
		Price:     1337,
		Amount:    1337,
		OrderType: gctorder.Stop,
		Leverage:  1337,
	})
	if err != nil {
		t.Error(err)
	}
}

func TestAddFillEventForTime(t *testing.T) {
	tt := time.Now()
	exch := testExchange
	a := asset.Spot
	p := currency.NewPair(currency.BTC, currency.USDT)
	s := Statistic{}
	err := s.SetEventForOffset(nil)
	if !errors.Is(err, common.ErrNilEvent) {
		t.Errorf("expected: %v, received %v", common.ErrNilEvent, err)
	}
	err = s.SetEventForOffset(&fill.Fill{})
	if err != nil && err.Error() != "exchangeAssetPairStatistics not setup" {
		t.Error(err)
	}
	s.setupMap(exch, a)
	s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[currency.Pair]*currencystatistics.CurrencyStatistic)
	err = s.SetEventForOffset(&fill.Fill{})
	if !errors.Is(err, errCurrencyStatisticsUnset) {
		t.Errorf("expected: %v, received %v", errCurrencyStatisticsUnset, err)
	}

	err = s.SetupEventForTime(&kline.Kline{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt,
			Interval:     gctkline.OneDay,
			CurrencyPair: p,
			AssetType:    a,
		},
		Open:   1337,
		Close:  1337,
		Low:    1337,
		High:   1337,
		Volume: 1337,
	})
	if err != nil {
		t.Error(err)
	}
	err = s.SetEventForOffset(&fill.Fill{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt,
			Interval:     gctkline.OneDay,
			CurrencyPair: p,
			AssetType:    a,
		},
		Direction:           gctorder.Buy,
		Amount:              1337,
		ClosePrice:          1337,
		VolumeAdjustedPrice: 1337,
		PurchasePrice:       1337,
		ExchangeFee:         1337,
		Slippage:            1337,
	})
	if err != nil {
		t.Error(err)
	}
}

func TestAddHoldingsForTime(t *testing.T) {
	tt := time.Now()
	exch := testExchange
	a := asset.Spot
	p := currency.NewPair(currency.BTC, currency.USDT)
	s := Statistic{}
	err := s.AddHoldingsForTime(&holdings.Holding{})
	if !errors.Is(err, errExchangeAssetPairStatsUnset) {
		t.Errorf("expected: %v, received %v", errExchangeAssetPairStatsUnset, err)
	}
	s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[currency.Pair]*currencystatistics.CurrencyStatistic)
	err = s.AddHoldingsForTime(&holdings.Holding{})
	if !errors.Is(err, errCurrencyStatisticsUnset) {
		t.Errorf("expected: %v, received %v", errCurrencyStatisticsUnset, err)
	}

	err = s.SetupEventForTime(&kline.Kline{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt,
			Interval:     gctkline.OneDay,
			CurrencyPair: p,
			AssetType:    a,
		},
		Open:   1337,
		Close:  1337,
		Low:    1337,
		High:   1337,
		Volume: 1337,
	})
	if err != nil {
		t.Error(err)
	}
	err = s.AddHoldingsForTime(&holdings.Holding{
		Pair:                         p,
		Asset:                        a,
		Exchange:                     exch,
		Timestamp:                    tt,
		InitialFunds:                 1337,
		PositionsSize:                1337,
		PositionsValue:               1337,
		SoldAmount:                   1337,
		SoldValue:                    1337,
		BoughtAmount:                 1337,
		BoughtValue:                  1337,
		RemainingFunds:               1337,
		TotalValueDifference:         1337,
		ChangeInTotalValuePercent:    1337,
		BoughtValueDifference:        1337,
		SoldValueDifference:          1337,
		PositionsValueDifference:     1337,
		TotalValue:                   1337,
		TotalFees:                    1337,
		TotalValueLostToVolumeSizing: 1337,
		TotalValueLostToSlippage:     1337,
		TotalValueLost:               1337,
		RiskFreeRate:                 1337,
	})
	if err != nil {
		t.Error(err)
	}
}

func TestAddComplianceSnapshotForTime(t *testing.T) {
	tt := time.Now()
	exch := testExchange
	a := asset.Spot
	p := currency.NewPair(currency.BTC, currency.USDT)
	s := Statistic{}

	err := s.AddComplianceSnapshotForTime(compliance.Snapshot{}, nil)
	if !errors.Is(err, common.ErrNilEvent) {
		t.Errorf("expected: %v, received %v", common.ErrNilEvent, err)
	}
	err = s.AddComplianceSnapshotForTime(compliance.Snapshot{}, &fill.Fill{})
	if !errors.Is(err, errExchangeAssetPairStatsUnset) {
		t.Errorf("expected: %v, received %v", errExchangeAssetPairStatsUnset, err)
	}
	s.setupMap(exch, a)
	s.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[currency.Pair]*currencystatistics.CurrencyStatistic)
	err = s.AddComplianceSnapshotForTime(compliance.Snapshot{}, &fill.Fill{})
	if !errors.Is(err, errCurrencyStatisticsUnset) {
		t.Errorf("expected: %v, received %v", errCurrencyStatisticsUnset, err)
	}

	err = s.SetupEventForTime(&kline.Kline{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt,
			Interval:     gctkline.OneDay,
			CurrencyPair: p,
			AssetType:    a,
		},
		Open:   1337,
		Close:  1337,
		Low:    1337,
		High:   1337,
		Volume: 1337,
	})
	if err != nil {
		t.Error(err)
	}
	err = s.AddComplianceSnapshotForTime(compliance.Snapshot{
		Timestamp: tt,
	}, &fill.Fill{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt,
			Interval:     gctkline.OneDay,
			CurrencyPair: p,
			AssetType:    a,
		},
	})
	if err != nil {
		t.Error(err)
	}
}

func TestSerialise(t *testing.T) {
	s := Statistic{}
	_, err := s.Serialise()
	if err != nil {
		t.Error(err)
	}
}

func TestSetStrategyName(t *testing.T) {
	s := Statistic{}
	s.SetStrategyName("test")
	if s.StrategyName != "test" {
		t.Error("expected test")
	}
}

func TestPrintTotalResults(t *testing.T) {
	s := Statistic{}
	s.BiggestDrawdown = s.GetTheBiggestDrawdownAcrossCurrencies([]FinalResultsHolder{
		{
			Exchange: "test",
			MaxDrawdown: currencystatistics.Swing{
				DrawdownPercent: 1337,
			},
		},
	})
	s.BestStrategyResults = s.GetBestStrategyPerformer([]FinalResultsHolder{
		{
			Exchange:         "test",
			Asset:            asset.Spot,
			Pair:             currency.NewPair(currency.BTC, currency.DOGE),
			MaxDrawdown:      currencystatistics.Swing{},
			MarketMovement:   1337,
			StrategyMovement: 1337,
		},
	})
	s.BestMarketMovement = s.GetBestMarketPerformer([]FinalResultsHolder{
		{
			Exchange:       "test",
			MarketMovement: 1337,
		},
	})
	s.PrintTotalResults()
}

func TestGetBestStrategyPerformer(t *testing.T) {
	s := Statistic{}
	resp := s.GetBestStrategyPerformer(nil)
	if resp.Exchange != "" {
		t.Error("expected unset details")
	}

	resp = s.GetBestStrategyPerformer([]FinalResultsHolder{
		{
			Exchange:         "test",
			Asset:            asset.Spot,
			Pair:             currency.NewPair(currency.BTC, currency.DOGE),
			MaxDrawdown:      currencystatistics.Swing{},
			MarketMovement:   1337,
			StrategyMovement: 1337,
		},
		{
			Exchange:         "test2",
			Asset:            asset.Spot,
			Pair:             currency.NewPair(currency.BTC, currency.DOGE),
			MaxDrawdown:      currencystatistics.Swing{},
			MarketMovement:   1338,
			StrategyMovement: 1338,
		},
	})

	if resp.Exchange != "test2" {
		t.Error("expected test2")
	}
}

func TestGetTheBiggestDrawdownAcrossCurrencies(t *testing.T) {
	s := Statistic{}
	result := s.GetTheBiggestDrawdownAcrossCurrencies(nil)
	if result.Exchange != "" {
		t.Error("expected empty")
	}

	result = s.GetTheBiggestDrawdownAcrossCurrencies([]FinalResultsHolder{
		{
			Exchange: "test",
			MaxDrawdown: currencystatistics.Swing{
				DrawdownPercent: 1337,
			},
		},
		{
			Exchange: "test2",
			MaxDrawdown: currencystatistics.Swing{
				DrawdownPercent: 1338,
			},
		},
	})
	if result.Exchange != "test2" {
		t.Error("expected test2")
	}
}

func TestGetBestMarketPerformer(t *testing.T) {
	s := Statistic{}
	result := s.GetBestMarketPerformer(nil)
	if result.Exchange != "" {
		t.Error("expected empty")
	}

	result = s.GetBestMarketPerformer([]FinalResultsHolder{
		{
			Exchange:       "test",
			MarketMovement: 1337,
		},
		{
			Exchange:       "test2",
			MarketMovement: 1336,
		},
	})
	if result.Exchange != "test" {
		t.Error("expected test")
	}
}

func TestPrintAllEvents(t *testing.T) {
	s := Statistic{}
	s.PrintAllEvents()
	tt := time.Now()
	exch := testExchange
	a := asset.Spot
	p := currency.NewPair(currency.BTC, currency.USDT)
	err := s.SetupEventForTime(nil)
	if !errors.Is(err, common.ErrNilEvent) {
		t.Errorf("expected: %v, received %v", common.ErrNilEvent, err)
	}
	err = s.SetupEventForTime(&kline.Kline{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt,
			Interval:     gctkline.OneDay,
			CurrencyPair: p,
			AssetType:    a,
		},
		Open:   1337,
		Close:  1337,
		Low:    1337,
		High:   1337,
		Volume: 1337,
	})
	if err != nil {
		t.Error(err)
	}

	err = s.SetEventForOffset(&fill.Fill{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt,
			Interval:     gctkline.OneDay,
			CurrencyPair: p,
			AssetType:    a,
		},
		Direction:           gctorder.Buy,
		Amount:              1337,
		ClosePrice:          1337,
		VolumeAdjustedPrice: 1337,
		PurchasePrice:       1337,
		ExchangeFee:         1337,
		Slippage:            1337,
	})
	if err != nil {
		t.Error(err)
	}

	err = s.SetEventForOffset(&signal.Signal{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt,
			Interval:     gctkline.OneDay,
			CurrencyPair: p,
			AssetType:    a,
		},
		ClosePrice: 1337,
		Direction:  gctorder.Buy,
	})
	if err != nil {
		t.Error(err)
	}

	s.PrintAllEvents()
}

func TestCalculateTheResults(t *testing.T) {
	s := Statistic{}
	err := s.CalculateAllResults()
	if err != nil {
		t.Error(err)
	}

	tt := time.Now()
	tt2 := time.Now().Add(time.Hour)
	exch := testExchange
	a := asset.Spot
	p := currency.NewPair(currency.BTC, currency.USDT)
	p2 := currency.NewPair(currency.DOGE, currency.DOGE)
	err = s.SetupEventForTime(nil)
	if !errors.Is(err, common.ErrNilEvent) {
		t.Errorf("expected: %v, received %v", common.ErrNilEvent, err)
	}
	err = s.SetupEventForTime(&kline.Kline{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt,
			Interval:     gctkline.OneDay,
			CurrencyPair: p,
			AssetType:    a,
		},
		Open:   1337,
		Close:  1337,
		Low:    1337,
		High:   1337,
		Volume: 1337,
	})
	if err != nil {
		t.Error(err)
	}
	err = s.SetEventForOffset(&signal.Signal{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt,
			Interval:     gctkline.OneDay,
			CurrencyPair: p,
			AssetType:    a,
		},
		OpenPrice:  1337,
		HighPrice:  1337,
		LowPrice:   1337,
		ClosePrice: 1337,
		Volume:     1337,
		Direction:  gctorder.Buy,
	})
	if err != nil {
		t.Error(err)
	}
	err = s.SetupEventForTime(&kline.Kline{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt,
			Interval:     gctkline.OneDay,
			CurrencyPair: p2,
			AssetType:    a,
		},
		Open:   1338,
		Close:  1338,
		Low:    1338,
		High:   1338,
		Volume: 1338,
	})
	if err != nil {
		t.Error(err)
	}

	err = s.SetEventForOffset(&signal.Signal{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt,
			Interval:     gctkline.OneDay,
			CurrencyPair: p2,
			AssetType:    a,
		},
		OpenPrice:  1337,
		HighPrice:  1337,
		LowPrice:   1337,
		ClosePrice: 1337,
		Volume:     1337,
		Direction:  gctorder.Buy,
	})
	if err != nil {
		t.Error(err)
	}

	err = s.SetupEventForTime(&kline.Kline{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt2,
			Interval:     gctkline.OneDay,
			CurrencyPair: p,
			AssetType:    a,
		},
		Open:   1338,
		Close:  1338,
		Low:    1338,
		High:   1338,
		Volume: 1338,
	})
	if err != nil {
		t.Error(err)
	}
	err = s.SetEventForOffset(&signal.Signal{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt2,
			Interval:     gctkline.OneDay,
			CurrencyPair: p,
			AssetType:    a,
		},
		OpenPrice:  1338,
		HighPrice:  1338,
		LowPrice:   1338,
		ClosePrice: 1338,
		Volume:     1338,
		Direction:  gctorder.Buy,
	})
	if err != nil {
		t.Error(err)
	}

	err = s.SetupEventForTime(&kline.Kline{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt2,
			Interval:     gctkline.OneDay,
			CurrencyPair: p2,
			AssetType:    a,
		},
		Open:   1338,
		Close:  1338,
		Low:    1338,
		High:   1338,
		Volume: 1338,
	})
	if err != nil {
		t.Error(err)
	}
	err = s.SetEventForOffset(&signal.Signal{
		Base: event.Base{
			Exchange:     exch,
			Time:         tt2,
			Interval:     gctkline.OneDay,
			CurrencyPair: p2,
			AssetType:    a,
		},
		OpenPrice:  1338,
		HighPrice:  1338,
		LowPrice:   1338,
		ClosePrice: 1338,
		Volume:     1338,
		Direction:  gctorder.Buy,
	})
	if err != nil {
		t.Error(err)
	}

	s.ExchangeAssetPairStatistics[exch][a][p].Events[1].Holdings.InitialFunds = 1337
	s.ExchangeAssetPairStatistics[exch][a][p].Events[1].Holdings.TotalValue = 13337
	s.ExchangeAssetPairStatistics[exch][a][p2].Events[1].Holdings.InitialFunds = 1337
	s.ExchangeAssetPairStatistics[exch][a][p2].Events[1].Holdings.TotalValue = 13337

	err = s.CalculateAllResults()
	if err != nil {
		t.Error(err)
	}
}
