package enx

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"pool/internal/msg"
	"pool/internal/server/coin"
	"pool/internal/stratum"
	"pool/internal/utility"

	"github.com/btcsuite/btcd/wire"
	"go.uber.org/zap"
)

const SERVER_ENX_VERSION string = "enx v1.15.2"

type ServerEnxContext struct {
	ServerCtx *coin.ServerContext
	logg      *zap.Logger
	Job       msg.StratumJob
}

var logg *zap.Logger
var ServerEnxCtx ServerEnxContext

// headerHash:本身的pow计算结果,收到后根据nonce和timestamp重新计算该值,确保该值正确
// headerHash本身可以通过计算得出难度
func handle_submit(miner *coin.MinerObj, id float64, miner_user string, job_id string, headerHash string, ntime string, nonce string) (bool, bool, bool) {
	var submit_item coin.BlockMsg
	/*var user_blk_item coin.UserBlockMsg*/
	var pool_blk_item coin.PoolBlkMsg

	var blk_detail_height int64
	var blk_detail_hash string
	var blk_detail_success bool
	var blk_detail_miner_diff float64
	var blk_detail_pool_diff float64

	if miner.Authorized != true {
		miner.ErrOthers = miner.ErrOthers + 1
		stratum.Handle_exception(miner, id, stratum.MINER_ERR_UNAUTH_WORKER)
		stratum.Send_reconnect_msg(miner)
		return false, false, false
	}
	var new_found bool = false
	var ack stratum.Submit_ack
	ack.ID = id
	ack.Result = true
	//logg.Warn("[server]", zap.String("user", miner.User), zap.String("miner", miner.Miner))
	//logg.Debug("[server]", zap.Float64("id", id), zap.String("job_id", job_id))
	//logg.Debug("[server]", zap.String("nonce2", nonce2), zap.String("ntime", ntime), zap.String("nonce", nonce))
	//stratum.UpdateJobs(miner)
	v, ok := miner.Jobs.Load(job_id)
	if ok {
		job := v.(msg.StratumJob)
		if job.Height < miner.CurHeight-1 {
			ack.Result = false
			stratum.Handle_exception(miner, id, stratum.MINER_ERR_STALED_JOB)
			miner.ErrStaleds = miner.ErrStaleds + 1
			return false, false, false
		}

		if miner.LastNonce != nonce {
			miner.LastNonce = nonce
			if miner.ZlogInit {
				miner.Zlog.Info().Msg("height " + fmt.Sprintf("%d", job.Height) + " target " + job.Target + " extra1 " + job.Extranonce1 + " size " + fmt.Sprintf("%d", job.Extranonce2_size) + " " + miner.User + "." + miner.Miner)
			}
		}
		vb := make([]byte, 4)
		binary.LittleEndian.PutUint32(vb, uint32(job.Version))
		vBuffer := bytes.NewBuffer(vb)
		binary.Read(vBuffer, binary.BigEndian, &(miner.Version))

		job.Nonce = nonce
		job.Extranonce2 = headerHash

		var calc_hash []byte
		var header wire.BlockHeader

	}
}

// 构造提交至节点的区块
func Produce_block_submit(miner *coin.MinerObj, header wire.BlockHeader, job *msg.StratumJob, PowHash string, SubIdx int64) {
}

// server-->miner
func enx_parse_miner_notify(miner *coin.MinerObj, msg msg.StratumJob) int {}

func Init(server *coin.ServerContext) {
	ServerEnxCtx.ServerCtx = server
	logg = server.Logg
	logg.Info("[server]", zap.String("server_mona_version", SERVER_ENX_VERSION))
	coin.Init_diff_db()
}

func Start() {

}

func Stop() {
	coin.DiffStop()
}

func InitMiner(miner *coin.MinerObj) {}

func Handle_subscribe_enx(miner *coin.MinerObj, id float64, extranonce1 string) {
	stratum.Handle_subscribe(miner, id, extranonce1)
}

func HandleMinerSubscribe(miner *coin.MinerObj, id float64, extranonce1 string, msg string) {
	Handle_subscribe_enx(miner, id, extranonce1)
}

func HandleMinerAuth(miner *coin.MinerObj) {

}

func HandleMinerSubmit(miner *coin.MinerObj, id float64, miner_user string, job_id string, nonce2 string, ntime string, nonce string) (bool, bool, bool) {

	accept_ok, submit_ok, handle_ok := handle_submit(miner, id, miner_user, job_id, nonce2, ntime, nonce)
	return accept_ok, submit_ok, handle_ok
}

func set_difficulty(miner *coin.MinerObj) {}

func SetDifficulty(miner *coin.MinerObj) {
	set_difficulty(miner)
}

/**
id: int, method: string, params:[string(jobid), string(headerHash, 32 byte serialized header, 8 bytes timestamp)]
*/
func EnxNotify(miner *coin.MinerObj) {
	miner.TxLock.Lock()
	if !((miner.Status == coin.MINER_STATUS_AUTHORIZED) || (miner.Status == coin.MINER_STATUS_RUNNING)) {
		miner.TxLock.Unlock()
		return
	}
	miner.TxLock.Unlock()
	if miner.DifficultyNext > -1 {
		ratio := miner.DifficultyNext / miner.Difficulty
		if ratio > 1.1 || ratio < 0.9 {
			miner.Difficulty = miner.DifficultyNext
			miner.DifficultyNext = -1
			//Set_difficulty(miner)
			miner.Server.CoinCtx.SetDifficulty(miner)
		} else {
			miner.DifficultyNext = -1
		}
	}
	miner.TxLock.Lock()
	//log.Println("[server]extra1, id", miner.Job.Extranonce1, miner.Job.Job_id, miner.MinerId)
	var params [9]interface{}
	var tlist []string = make([]string, 0)
	idb := make([]byte, 4)
	binary.BigEndian.PutUint32(idb, miner.JobId)
	miner.Job.Job_id = hex.EncodeToString(idb)
	params[0] = miner.Job.Job_id
	if len(miner.Job.PrevblockS) > 0 {
		params[1] = miner.Job.PrevblockBig
	} else {
		p_big := utility.Convert_big_endian(miner.Job.Prevblock.CloneBytes())
		params[1] = hex.EncodeToString(p_big)
	}
	params[2] = miner.Job.Coinbase1
	params[3] = miner.Job.Coinbase2
	params[4] = tlist

	miner.CurHeight = miner.Job.Height

	if miner.Job.Transactions != nil {
		if len(*miner.Job.Transactions) > 0 {
			params[4] = miner.Job.Transactions

			/*miner.Server.Logg.Error("[notify]", zap.String("coinbase1", miner.Job.Coinbase1), zap.String("coinbase2", miner.Job.Coinbase2), zap.Uint32("height", miner.Job.Height))
			for i := 0; i < len(*miner.Job.Transactions); i++ {
				miner.Server.Logg.Error("[notify]", zap.String("trans", (*miner.Job.Transactions)[i]))
			}*/

		}
	}
	vb := make([]byte, 4)
	binary.LittleEndian.PutUint32(vb, uint32(miner.Job.Version))
	params[5] = hex.EncodeToString(vb)
	bb := make([]byte, 4)
	binary.LittleEndian.PutUint32(bb, miner.Job.Bits)
	params[6] = hex.EncodeToString(bb)
	t := miner.Job.Timestamp.Unix()
	if t > int64(^uint32(0)) {
		tb := make([]byte, 8)
		binary.LittleEndian.PutUint64(tb, uint64(t))
		params[7] = hex.EncodeToString(tb)
	} else {
		tb := make([]byte, 4)
		binary.LittleEndian.PutUint32(tb, uint32(t))
		params[7] = hex.EncodeToString(tb)
	}
	if miner.Reconnect {
		params[8] = true
		miner.Reconnect = false
	} else {
		params[8] = miner.Job.IsClean
	}
	miner.Job.JobDifficulty = miner.Difficulty

	//miner.Jobs[miner.Job.Job_id] = miner.Job
	miner.Jobs.LoadOrStore(miner.Job.Job_id, miner.Job)

	/*var entry coin.JobListEntry
	entry.Job_id = miner.Job.Job_id
	entry.Ts = time.Now()

	miner.LockForJobs.Lock()
	miner.JobList.PushFront(entry)
	var removes string = ""
	if miner.JobList.Len() > int(coin.LOCAL_JOBS_TOTAL_SIZE) {
		e := miner.JobList.Back()
		entry := e.Value.(coin.JobListEntry)
		removes = entry.Job_id
		miner.JobList.Remove(e)
	}
	miner.LockForJobs.Unlock()
	if len(removes) > 0 {
		miner.Jobs.Delete(removes)
	}*/
	stratum.AddAndUpdateJob(miner)
	stratum.UpdateJobs(miner)

	//miner.LastJobId = miner.Job.Job_id
	miner.JobId++

	var msg stratum.Notify_msg
	msg.ID = nil
	msg.Method = "mining.notify"
	msg.Params = params
	body, err := json.Marshal(msg)
	if err != nil {
		miner.Server.Logg.Error("[server]", zap.String("Marshal", err.Error()))
		miner.TxLock.Unlock()
		return
	}
	var body_string = string(body) + "\n"
	err = stratum.Conn_tx(miner.Conn, []byte(body_string))
	if err != nil {
		//delete(miner.Server.Miners, miner.MinerId)
		//miner.Server.Miners.Delete(miner.MinerId)
	}
	//miner.Server.Logg.Debug("[server]", zap.String("tx", body_string))
	miner.TxLock.Unlock()

	if miner.ZlogInit {
		miner.Zlog.Info().Msg(body_string)
	}
}

func Notify(miner *coin.MinerObj) {
	EnxNotify(miner)
}

func HandleJobMsg(server *coin.ServerContext, Msg []byte) {}

func IsMhsLow(miner *coin.MinerObj) bool {
	return miner.Mhs5M < 1
}

func GetBlockInterval() int {
	return 30 // 30秒没获取到任务发送退出信号
}