package main

import (
	"context"
	"errors"
	"fmt"
	"log"
	"reflect"
	"sync"

	"github.com/thrasher-corp/gocryptotrader/common"
	"github.com/thrasher-corp/gocryptotrader/engine"
	exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
)

func main() {
	var err error
	engine.Bot, err = engine.New()
	if err != nil {
		log.Fatalf("Failed to initialise engine. Err: %s", err)
	}

	engine.Bot.Settings = engine.Settings{
		DisableExchangeAutoPairUpdates: true,
		EnableDryRun:                   true,
	}

	engine.Bot.Config.PurgeExchangeAPICredentials()
	engine.Bot.ExchangeManager = engine.SetupExchangeManager()

	log.Printf("Loading exchanges..")
	var wg sync.WaitGroup
	for x := range exchange.Exchanges {
		if exchange.Exchanges[x] == "ftx" {
			log.Println("Skipping exchange FTX...")
			continue
		}
		err = engine.Bot.LoadExchange(exchange.Exchanges[x], &wg)
		if err != nil {
			log.Printf("Failed to load exchange %s. Err: %s",
				exchange.Exchanges[x],
				err)
			continue
		}
	}
	wg.Wait()
	log.Println("Done.")

	log.Printf("Testing exchange wrappers..")
	results := make(map[string][]string)
	var mtx sync.Mutex

	exchanges := engine.Bot.GetExchanges()
	for x := range exchanges {
		wg.Add(1)
		go func(exch exchange.IBotExchange) {
			strResults, err := testWrappers(exch)
			if err != nil {
				log.Printf("Failed to test wrappers for %s. Err: %s", exch.GetName(), err)
			}
			mtx.Lock()
			results[exch.GetName()] = strResults
			mtx.Unlock()
			wg.Done()
		}(exchanges[x])
	}
	wg.Wait()
	log.Println("Done.")

	var dummyInterface exchange.IBotExchange
	totalWrappers := reflect.TypeOf(&dummyInterface).Elem().NumMethod()

	log.Println()
	for name, funcs := range results {
		pct := float64(totalWrappers-len(funcs)) / float64(totalWrappers) * 100
		log.Printf("Exchange %s wrapper coverage [%d/%d - %.2f%%] | Total missing: %d",
			name,
			totalWrappers-len(funcs),
			totalWrappers,
			pct,
			len(funcs))
		log.Printf("\t Wrappers not implemented:")

		for x := range funcs {
			log.Printf("\t - %s", funcs[x])
		}
		log.Println()
	}
}

// testWrappers executes and checks each IBotExchange's function return for the
// error common.ErrNotYetImplemented to verify whether the wrapper function has
// been implemented yet.
func testWrappers(e exchange.IBotExchange) ([]string, error) {
	iExchange := reflect.TypeOf(&e).Elem()
	actualExchange := reflect.ValueOf(e)
	errType := reflect.TypeOf(common.ErrNotYetImplemented)

	contextParam := reflect.TypeOf((*context.Context)(nil)).Elem()

	var funcs []string
	for x := 0; x < iExchange.NumMethod(); x++ {
		name := iExchange.Method(x).Name
		method := actualExchange.MethodByName(name)
		inputs := make([]reflect.Value, method.Type().NumIn())

		for y := 0; y < method.Type().NumIn(); y++ {
			input := method.Type().In(y)

			if input.Implements(contextParam) {
				// Need to deploy a context.Context value as nil value is not
				// checked throughout codebase. Cancelled to minimise external
				// calls and speed up operation.
				cancelled, cancelfn := context.WithTimeout(context.Background(), 0)
				cancelfn()
				inputs[y] = reflect.ValueOf(cancelled)
				continue
			}
			inputs[y] = reflect.Zero(input)
		}

		outputs := method.Call(inputs)
		if method.Type().NumIn() == 0 {
			// Some empty functions will reset the exchange struct to defaults,
			// so turn off verbosity.
			e.GetBase().Verbose = false
		}

		for y := range outputs {
			incoming := outputs[y].Interface()
			if reflect.TypeOf(incoming) == errType {
				err, ok := incoming.(error)
				if !ok {
					return nil, fmt.Errorf("%s type assertion failure for %v", name, incoming)
				}
				if errors.Is(err, common.ErrNotYetImplemented) {
					funcs = append(funcs, name)
				}
				// found error; there should not be another error in this slice.
				break
			}
		}
	}
	return funcs, nil
}
