// utility.go
package utility

import (
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"strconv"

	"bytes"
	"log"
	"math"
	"math/big"
	"os"
	"os/exec"
	"strings"

	"time"

	"github.com/zeromq/goczmq"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"gopkg.in/natefinch/lumberjack.v2"
)

const BITCOIND_ZMQ_HASHBLOCK string = "hashblock"

type CoinConfig struct {
	Coin string `json:"coin"`
}

type ZmqConfig struct {
	Pub string `json:"pub"`
	Sub string `json:"sub"`
}

type RedisConfig struct {
	Addr     string `json:"addr"`
	Password string `json:"password"`
	DB       int    `json:"db"`
}

type LogRotateConfig struct {
	MaxSize    int  `json:"maxsize"`
	MaxBackups int  `json:"maxbackups"`
	MaxAge     int  `json:"maxage"`
	Compress   bool `json:"compress"`
}

func InitLogg(zaplog *zap.Config, rotate *LogRotateConfig, coinname string, modulename string) (*zap.Logger, *lumberjack.Logger, error) {
	os.MkdirAll("logs/"+coinname, os.ModePerm)
	logfile := "./logs/" + coinname + "/" + modulename + ".log"

	logRotate := &lumberjack.Logger{
		Filename:   logfile,
		MaxSize:    rotate.MaxSize,
		MaxBackups: rotate.MaxBackups,
		MaxAge:     rotate.MaxAge,
		Compress:   rotate.Compress,
	}

	zaplog.EncoderConfig = zap.NewProductionEncoderConfig()
	zaplog.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder

	opath := []string{"", ""}
	opath[0] = zaplog.OutputPaths[0]
	opath[1] = logfile
	zaplog.OutputPaths = opath

	l, err := zaplog.Build(
		zap.WrapCore(func(core zapcore.Core) zapcore.Core {
			return zapcore.NewCore(
				zapcore.NewJSONEncoder(zaplog.EncoderConfig),
				zapcore.AddSync(logRotate),
				zaplog.Level,
			)
		}),
	)
	if err != nil {
		log.Fatal("[gbt]", err.Error())
		return nil, nil, err
	}
	return l, logRotate, nil
}

func GetCoin(config_file string) string {
	var config CoinConfig
	data, err := ioutil.ReadFile(config_file)
	if err != nil {
		panic(err.Error())
	}
	if err = json.Unmarshal(data, &config); err != nil {
		panic(err.Error())
	}

	return config.Coin
}

func confirmXPubSubscriptions(pub *goczmq.Channeler, count int) {
	for i := 0; i < count; i++ {
		select {
		case <-pub.RecvChan:
		case <-time.After(time.Second * 2):
			log.Println("confirmXPubSubscriptions, timeout")
		}
	}
}

func InitZmqPub(pub_host string) *goczmq.Sock {
	pub_ch, err := goczmq.NewXPub(pub_host)
	if err != nil {
		log.Fatal("[server]", zap.String("NewXPub", "zmq pub create failed!"))
	}
	//pub_ch.SetMaxmsgsize(1024 * 1024 * 8)
	return pub_ch
}

func InitZmqSub(sub_to string, topic string) *goczmq.Sock {

	sub_ch, err := goczmq.NewSub(sub_to, topic)
	if err != nil {
		log.Fatal("[server]", zap.String("NewSub", "zmq sub connect failed!"))
	}
	//sub_ch.SetMaxmsgsize(1024 * 1024 * 8)
	return sub_ch

}

func InitZmqPush(sub_to string) *goczmq.Sock {
	push_ch, err := goczmq.NewPush(sub_to)
	if err != nil {
		log.Fatal("[server]", zap.String("NewPushChanneler", "zmq push connect failed!"))
	}
	//push_ch.Bind(sub_to)
	//push_ch.SetMaxmsgsize(1024 * 1024 * 8)
	return push_ch
}

func InitZmqPull(sub_to string) *goczmq.Sock {
	pull_ch, err := goczmq.NewPull(sub_to)
	if err != nil {
		log.Fatal("[server]", zap.String("InitZmqPull", "zmq pull connect failed! "+err.Error()))
	}
	//pull_ch.SetMaxmsgsize(1024 * 1024 * 8)
	return pull_ch
}

func Int32ToString(n uint32) string {
	buf := [11]byte{}
	pos := len(buf)
	i := int64(n)
	signed := i < 0
	if signed {
		i = -i
	}
	for {
		pos--
		buf[pos], i = '0'+byte(i%10), i/10
		if i == 0 {
			if signed {
				pos--
				buf[pos] = '-'
			}
			return string(buf[pos:])
		}
	}
}

func ByteToUint32(bytes []byte) uint32 {
	return binary.LittleEndian.Uint32(bytes)
}

func Reverse_string(instr string) string {
	var outstr string = ""
	for i := 0; i < len(instr)/2; i++ {
		outstr = outstr + instr[len(instr)-i*2-2:len(instr)-i*2]
	}
	return outstr
}

// CompactToBig converts a compact representation of a whole number N to an
// unsigned 32-bit number.  The representation is similar to IEEE754 floating
// point numbers.
//
// Like IEEE754 floating point, there are three basic components: the sign,
// the exponent, and the mantissa.  They are broken out as follows:
//
// - the most significant 8 bits represent the unsigned base 256 exponent
// - bit 23 (the 24th bit) represents the sign bit
// - the least significant 23 bits represent the mantissa
//
//	-------------------------------------------------
//	|   Exponent     |    Sign    |    Mantissa     |
//	-------------------------------------------------
//	| 8 bits [31-24] | 1 bit [23] | 23 bits [22-00] |
//	-------------------------------------------------
//
// The formula to calculate N is:
//
//	N = (-1^sign) * mantissa * 256^(exponent-3)
//
// This compact form is only used in bitcoin to encode unsigned 256-bit numbers
// which represent difficulty targets, thus there really is not a need for a
// sign bit, but it is implemented here to stay consistent with bitcoind.
func CompactToBig(compact uint32) *big.Int {
	// Extract the mantissa, sign bit, and exponent.
	mantissa := compact & 0x007fffff
	isNegative := compact&0x00800000 != 0
	exponent := uint(compact >> 24)

	// Since the base for the exponent is 256, the exponent can be treated
	// as the number of bytes to represent the full 256-bit number.  So,
	// treat the exponent as the number of bytes and shift the mantissa
	// right or left accordingly.  This is equivalent to:
	// N = mantissa * 256^(exponent-3)
	var bn *big.Int
	if exponent <= 3 {
		mantissa >>= 8 * (3 - exponent)
		bn = big.NewInt(int64(mantissa))
	} else {
		bn = big.NewInt(int64(mantissa))
		bn.Lsh(bn, 8*(exponent-3))
	}

	// Make it negative if the sign bit is set.
	if isNegative {
		bn = bn.Neg(bn)
	}

	return bn
}

const truediffone float64 = 26959535291011309493156476344723991336010898738574164086137773096960.0
const bits192 float64 = 6277101735386680763835789423207666416102355444464034512896.0
const bits128 float64 = 340282366920938463463374607431768211456.0
const bits64 float64 = 18446744073709551616.0

func target2float(target []byte) float64 {
	var b64 float64 = float64(binary.LittleEndian.Uint64(target[24:32])) * bits192
	b64 += (float64(binary.LittleEndian.Uint64(target[16:24])) * bits128)
	b64 += (float64(binary.LittleEndian.Uint64(target[8:16])) * bits64)
	b64 += (float64(binary.LittleEndian.Uint64(target[0:8])))
	return b64
}

// convert target to difficulty
func Target2Diff(target []byte) float64 {
	//var f64 float64 = truediffone
	max, _ := new(big.Int).SetString("00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16) // 2 ^ 256 -1
	//max, _ := new(big.Int).SetString("00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16)
	maxf, _ := new(big.Float).SetString(max.String())
	f64, _ := maxf.Float64()
	var fcut64 float64 = target2float(target)
	//log.Println("diff", f64, fcut64, f64/fcut64)
	return f64 / fcut64
}

func DiffToTarget(diff float64 /*, powLimit *big.Int*/) (*big.Int, error) {
	if diff <= 0 {
		return nil, fmt.Errorf("invalid pool difficulty %v (0 or less than "+
			"zero passed)", diff)
	}

	// Round down in the case of a non-integer diff since we only support
	// ints (unless diff < 1 since we don't allow 0)..
	if diff <= 1 {
		diff = 1
	} else {
		diff = math.Floor(diff)
	}
	divisor := new(big.Int).SetInt64(int64(diff))
	//max, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16)
	max, _ := new(big.Int).SetString("00000000FFFF0000000000000000000000000000000000000000000000000000", 16) // BTC -> MAX_TARGET
	target := new(big.Int)
	//log.Println("target calc", hex.EncodeToString(max.Bytes()), hex.EncodeToString(divisor.Bytes()))
	target.Div(max, divisor)

	return target, nil
}

func Convert_big_endian(src []byte) []byte {
	var dst []byte = make([]byte, 32)
	for i := 0; i < 8; i++ {
		dst[0+4*i] = src[3+4*i]
		dst[1+4*i] = src[2+4*i]
		dst[2+4*i] = src[1+4*i]
		dst[3+4*i] = src[0+4*i]
	}
	return dst
}

func ReverseS(s string) (string, error) {
	a := strings.Split(s, "")
	sRev := ""
	if len(a)%2 != 0 {
		return "", fmt.Errorf("Incorrect input length")
	}
	for i := 0; i < len(a); i += 2 {
		tmp := []string{a[i], a[i+1], sRev}
		sRev = strings.Join(tmp, "")
	}
	return sRev, nil
}

func Reverse(src []byte) []byte {
	dst := make([]byte, len(src))
	for i := len(src); i > 0; i-- {
		dst[len(src)-i] = src[i-1]
	}
	return dst
}

func Uint32ToByte(targetu uint32) []byte {
	bytes := make([]byte, 4)
	binary.LittleEndian.PutUint32(bytes, targetu)
	return bytes
}

func Uint32ToByteBig(targetu uint32) []byte {
	bytes := make([]byte, 4)
	binary.BigEndian.PutUint32(bytes, targetu)
	return bytes
}

func ExecShellCmd(s string) (string, error) {
	cmd := exec.Command("/bin/bash", "-c", s)
	var out bytes.Buffer
	cmd.Stdout = &out
	err := cmd.Run()
	if err != nil {
		fmt.Println(err)
	}
	//fmt.Println(out.String(), s)
	return out.String(), err
}

func BytesToHexStr(b []byte) string {
	hexString := hex.EncodeToString(b)
	return hexString
}

func NormalStrToHexStr(s string) string {
	hexStr := fmt.Sprintf("%x", s) // 将字符串转换为16进制字符串
	return hexStr
}

func HexStrToBytes(s string) []byte {
	// 将16进制字符串转换为[]byte
	bytes, err := hex.DecodeString(s)
	if err != nil {
		log.Fatal(err)
	}
	return bytes
}

func ChainIndexStr(fromGroup uint32, toGroup uint32) string {
	return fmt.Sprintf("%d -> %d", fromGroup, toGroup)
}

func AlphDiff1Target() *big.Int {
	// 计算 2^226 - 1
	result := new(big.Int).Lsh(big.NewInt(1), 226) // 1 << 226
	result.Sub(result, big.NewInt(1))              // 2^226 - 1
	return result
}

// fromBuffer: 将字节切片解析为大整数
func fromBuffer(buf []byte) *big.Int {
	return new(big.Int).SetBytes(buf)
}

func AlphShareDiff(hash []byte) float64 {
	hashBigNum := fromBuffer(hash)
	diff1Target := AlphDiff1Target()
	temp := new(big.Int).Mul(diff1Target, big.NewInt(1024))
	result := new(big.Int).Div(temp, hashBigNum)
	finalResult := new(big.Float).SetInt(result)
	finalResult.Quo(finalResult, big.NewFloat(1024.0))
	diffStr := finalResult.Text('f', 8)
	diff, err := strconv.ParseFloat(diffStr, 64)
	if err != nil {
		log.Fatal(err)
		return 0
	}
	return diff
}

func AlphDiffToTarget(diff float64) (*big.Int, error) {
	if diff <= 0 {
		return nil, fmt.Errorf("invalid pool difficulty %v (0 or less than "+
			"zero passed)", diff)
	}

	// Round down in the case of a non-integer diff since we only support
	// ints (unless diff < 1 since we don't allow 0)..
	if diff <= 1 {
		diff = 1
	} else {
		diff = math.Floor(diff)
	}
	divisor := new(big.Int).SetInt64(int64(diff))
	//max, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16)
	max, _ := new(big.Int).SetString("000000003FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", 16) //
	target := new(big.Int)
	//log.Println("target calc", hex.EncodeToString(max.Bytes()), hex.EncodeToString(divisor.Bytes()))
	target.Div(max, divisor)

	return target, nil
}

// 结果等同于上面的AlphDiffToTarget方法
func AlphDiffToTarget2(diff float64) string {
	alphTarget1Diff := new(big.Int).Lsh(big.NewInt(1), 226)
	alphTarget1Diff.Sub(alphTarget1Diff, big.NewInt(1)) // 难度1的 target
	// 直接在原变量上操作，避免不必要的变量创建
	alphTarget1Diff.Mul(alphTarget1Diff, big.NewInt(1024))
	// 计算 difficulty * 1024 并转换为 big.Int
	ceilDifficultyInt := new(big.Int).SetUint64(uint64(math.Ceil(diff * 1024)))
	// 计算最终 target
	target := new(big.Int).Div(alphTarget1Diff, ceilDifficultyInt)
	return target.Text(16)
}
