// gbt.go
package gbt

/*
#cgo LDFLAGS: -lzmq
#include <zmq.h>

void set_max_msg_size(void *ctx, int size) {
    zmq_ctx_set(ctx, ZMQ_MAXMSGSIZE, size);
}
*/
import "C"
import (
	"encoding/json"
	"fmt"
	"log"

	"io/ioutil"
	"pool/internal/db"
	"sync/atomic"

	//"pool/internal/cache"
	"pool/internal/gbt/alph"
	"pool/internal/gbt/alph/http"
	"pool/internal/gbt/coin"
	"pool/internal/gbt/dgb"
	"pool/internal/gbt/enx"
	"pool/internal/gbt/grs"
	"pool/internal/gbt/mona"
	"pool/internal/gbt/nexa"
	"pool/internal/utility"

	"os"
	"os/signal"
	"syscall"

	"time"

	"github.com/btcsuite/btcd/rpcclient"
	kaspad "github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
)

var logg *zap.Logger
var GbtCtx coin.GbtContext

func InitConfig(config *coin.GbtConfig) {
	data, err := ioutil.ReadFile("gbt.conf")
	if err != nil {
		panic(err.Error())
	}
	if err = json.Unmarshal(data, &config); err != nil {
		panic(err.Error())
	}
}

func InitClient(gbt *coin.GbtContext) error {
	switch gbt.Coin {
	case "alph":
		// alph初始化逻辑
		host := gbt.Config.Rpc.Host
		port := gbt.Config.Rpc.Port
		apiHost := gbt.Config.Rpc.ApiHost
		apiPort := gbt.Config.Rpc.ApiPort
		apiKey := gbt.Config.Rpc.ApiKey
		client, err := alph.NewAlphPool(host, port)
		apiClient := http.NewHttpClient(apiHost, apiPort, apiKey)
		if err != nil {
			logg.Info("[gbt]", zap.String("rpcclient new ", err.Error()))
			return err
		}
		gbt.ClientAlph = client
		gbt.ClientAlphApi = apiClient
		// blockCount := apiClient.GetBlockCount()
		return nil
	case "enx":
		host := gbt.Config.Rpc.Host
		port := gbt.Config.Rpc.Port
		client, err := kaspad.NewRPCClient(host + ":" + port)
		if err != nil {
			logg.Info("[gbt]", zap.String("rpcclient new ", err.Error()))
			return err
		}
		gbt.ClientEnx = client
		dagInfoMsg, err := client.GetBlockDAGInfo()
		if err != nil {
			logg.Info("[gbt]", zap.String("GetBlockDAGInfo ", err.Error()))
			return err
		}
		blockCount := dagInfoMsg.VirtualDAAScore
		logg.Info("[gbt]", zap.Int64("Block count ", int64(blockCount)))
		return nil
	default:
		var config rpcclient.ConnConfig
		if gbt.Config.Rpc.Type == "testnet" {
			config.Host = gbt.Config.Rpc.Testnet
		} else {
			config.Host = gbt.Config.Rpc.Host
		}
		config.User = gbt.Config.Rpc.User
		config.Pass = gbt.Config.Rpc.Pass
		config.HTTPPostMode = true
		config.DisableTLS = true
		client, err := rpcclient.New(&config, nil)
		if err != nil {
			logg.Info("[gbt]", zap.String("rpcclient new ", err.Error()))
			return err
		}
		gbt.Client = client
		blockCount, err := client.GetBlockCount()
		if err != nil {
			logg.Info("[gbt]", zap.String("GetBlockCount ", err.Error()))
			return err
		}
		logg.Info("[gbt]", zap.Int64("Block count ", blockCount))
		return nil
	}
}

/*func GbtLivingHandler(gbt *coin.GbtContext) {
	timer := time.NewTimer(time.Duration(600) * time.Second)
	for {
		select {
		case aliving := <-gbt.AlivingChan:
			if !aliving {
				timer.Stop()
				//log.Println("gbt aliving", aliving)
				return
			} else {
				timer.Reset(time.Duration(600) * time.Second)
			}
		case <-time.After(time.Duration(600) * time.Second):
			gbt.ExitGbtChan <- true

			return
		}
	}
}*/

func GbtLivingHandler(gbt *coin.GbtContext) {
	var to_cnt int = 0
	for {
		flagAliving := atomic.LoadInt32(&(gbt.FlagAliving))
		flagExit := atomic.LoadInt32(&(gbt.FlagAlivingExit))

		if flagExit == 1 {
			logg.Error("[server]", zap.String("GbtLivingHandler exited", "exit"))
			break
		}

		if flagAliving == 0 {
			//if to_cnt > 240 {
			if to_cnt > gbt.Config.Rpc.Timeout*3/1000 {
				logg.Error("[server]", zap.String("GbtLivingHandler exited", "timer expired"))
				cmd := "killall gbt_" + gbt.Coin + " &"
				utility.ExecShellCmd(cmd)
				gbt.ExitGbtChan <- true
				break
			}
			to_cnt++
		} else {
			to_cnt = 0
			atomic.StoreInt32(&(gbt.FlagAliving), 0)
		}

		time.Sleep(time.Second)
	}
}

type coinobj struct {
	Coin  string
	Init  func(GbtCtx *coin.GbtContext, DbCtx *db.DbContext)
	Start func()
	Stop  func()
}

var coinobjs = []coinobj{
	{Coin: "nexa", Init: nexa.Init, Start: nexa.Start, Stop: nexa.Stop},
	{Coin: "grs", Init: grs.Init, Start: grs.Start, Stop: grs.Stop},
	{Coin: "mona", Init: mona.Init, Start: mona.Start, Stop: mona.Stop},
	{Coin: "dgbo", Init: dgb.Init, Start: dgb.Start, Stop: dgb.Stop},
	{Coin: "dgbq", Init: dgb.Init, Start: dgb.Start, Stop: dgb.Stop},
	{Coin: "dgbs", Init: dgb.Init, Start: dgb.Start, Stop: dgb.Stop},
	{Coin: "alph", Init: alph.Init, Start: alph.Start, Stop: alph.Stop},
	{Coin: "enx", Init: enx.Init, Start: enx.Start, Stop: enx.Stop},
}

func register_signal(dbctx *db.DbContext) {
	signal_ch := make(chan os.Signal, 1)
	signal.Notify(signal_ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
	go signal_handle(signal_ch, dbctx)
}

func signal_handle(signal_ch chan os.Signal, dbctx *db.DbContext) {
	for s := range signal_ch {
		switch s {
		case syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT:
			log.Println("stop!")
			dbctx.AppExit <- true
		default:
			//fmt.Println("other signal", s)
		}
	}
}

/*func LoadCache(gbt *coin.GbtContext) {
	val := cache.LoadPoolCache(gbt.RedisClient, gbt.Coin, "blocks")
	if val != nil {
		if intVal, ok := val.(int64); ok {
			gbt.Blocks = intVal
		}
	}

	val_f := cache.LoadPoolCache(gbt.RedisClient, gbt.Coin, "rewards")
	if val_f != nil {
		if fVal, ok := val_f.(float64); ok {
			gbt.Reward = fVal
		}
	}

	val_f = cache.LoadPoolCache(gbt.RedisClient, gbt.Coin, "fee")
	if val_f != nil {
		if fVal, ok := val_f.(float64); ok {
			gbt.Fee = fVal
		}
	}
}*/

func Start(Coin string, DbCtx *db.DbContext) {
	/*zmqctx := C.zmq_ctx_new()
	defer C.zmq_ctx_term(zmqctx)
	C.set_max_msg_size(zmqctx, 1024*1024*8)*/

	GbtCtx.DbCtx = DbCtx

	GbtCtx.Coin = Coin

	atomic.StoreInt32(&(GbtCtx.FlagAliving), 0)
	atomic.StoreInt32(&(GbtCtx.FlagAlivingExit), 0)

	InitConfig(&GbtCtx.Config)
	l, lr, err := utility.InitLogg(&(GbtCtx.Config.Zaplog), &(GbtCtx.Config.Logrotae), Coin, "gbt")
	logg = l

	//defer logg.Sync()

	GbtCtx.Log = l
	GbtCtx.LogR = lr

	GbtCtx.RedisClient = redis.NewClient(&redis.Options{
		Addr:     GbtCtx.Config.Redis.Addr,
		Password: GbtCtx.Config.Redis.Password,
		DB:       GbtCtx.Config.Redis.DB,
	})

	register_signal(DbCtx)

	GbtCtx.PubCh = utility.InitZmqPub(GbtCtx.Config.Zmq.Pub)

	GbtCtx.SubCh = utility.InitZmqSub(GbtCtx.Config.Zmq.Sub, "blk"+Coin)

	//GbtCtx.PushCh = utility.InitZmqPush(GbtCtx.Config.Profit.Push)

	for {
		err = InitClient(&GbtCtx)
		if err != nil {
			logg.Error("[gbt]", zap.String("InitClient", err.Error()))
			time.Sleep(time.Duration(5) * time.Second)
			continue
		}
		break
	}

	// if len(GbtCtx.Config.Rpc.ZmqSub) > 0 {
	// 	GbtCtx.NodeSubCh = utility.InitZmqSub(GbtCtx.Config.Rpc.ZmqSub, utility.BITCOIND_ZMQ_HASHBLOCK)
	// }

	GbtCtx.Started = true

	GbtCtx.ExitGbtChan = make(chan bool, 256)

	//GbtCtx.AlivingChan = make(chan bool, 256)

	GbtCtx.MinerAddrs = db.GetAddressFromTable(DbCtx)
	// GbtCtx.MinerAddrs = make([]string, 4)
	// GbtCtx.MinerAddrs[0] = "1EjmgB5GzbvVpu63a3GxLPjk4DNedhpjNZvVz5oCMpHg5"
	// GbtCtx.MinerAddrs[1] = "1BHkVwr7VNW2ViqphAR2wy3QdmAm29jYaTVt4w62kH9V1"
	// GbtCtx.MinerAddrs[2] = "15aeh8ugDpDjWRWhVHeLhyz3KTKSqKaoydTdMFxjD99BJ"
	// GbtCtx.MinerAddrs[3] = "17sEVHyyfHiwFXDH3onX9MxsbP6kh2B8hrtPd7mk3Qa6r"
	// GbtCtx.MinerAddrIndex = 0

	//LoadCache(&GbtCtx)

	for _, coinobj := range coinobjs {
		if coinobj.Coin == Coin {
			coinobj.Init(&GbtCtx, DbCtx)
			go coinobj.Start()
			break
		}
	}

	go GbtLivingHandler(&GbtCtx)

	<-DbCtx.AppExit
}

func Stop(Coin string) {
	fmt.Println("gbt退出了")
	GbtCtx.Started = false

	GbtCtx.ExitGbtChan <- true

	//GbtCtx.AlivingChan <- false
	atomic.StoreInt32(&(GbtCtx.FlagAlivingExit), 1)

	for _, coinobj := range coinobjs {
		if coinobj.Coin == Coin {
			coinobj.Stop()
			break
		}
	}

	//time.Sleep(1 * time.Second)

	defer close(GbtCtx.ExitGbtChan)

	//defer close(GbtCtx.AlivingChan)

	if GbtCtx.NodeSubCh != nil {
		defer GbtCtx.NodeSubCh.Destroy()
	}
	if GbtCtx.PubCh != nil {
		defer GbtCtx.PubCh.Destroy()
	}
	if GbtCtx.SubCh != nil {
		defer GbtCtx.SubCh.Destroy()
	}
	/*if GbtCtx.PushCh != nil {
		defer GbtCtx.PushCh.Destroy()
	}*/
	defer GbtCtx.RedisClient.Close()
	defer logg.Sync()
}
