package com.m2pool.pool.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.m2pool.common.core.text.Convert;
import com.m2pool.common.core.utils.StringUtils;
import com.m2pool.pool.entity.NotifyMsgParams;
import lombok.Data;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.Blake3;

import java.io.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.MathContext;
import java.math.RoundingMode;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.SecureRandom;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/**
 * @Description alph 矿机接入模拟 可对矿池进行并发测试
 * @Date 2025/3/17 10:00
 * @Author 杜懿
 */
@Data
public class Miner {

    private final  char[] digits = "0123456789abcdef".toCharArray();

    private  final ObjectMapper mapper = new ObjectMapper();

    private String coin;
    private String pool;
    private String user;
    private String password;
    private int id;
    private String state;
    // net.conn
    private Socket conn;
    //private OutputStream conn;
    private String extraNonce1;
    private int extraNonce2Size;
    private double difficulty;
    private String jobId;
    private String header;
    //private String nbits;
    //64位无符号整数
    //private long jobTime;
    private String blockData;
    //64位无符号整数-> 48位 前8固定为extraNonce+"0000"
    private BigInteger nonce;
    //private final AtomicLong nonce = new AtomicLong(0);

    private BigInteger blockNonce;
    //chan bool
    private BlockingQueue<Boolean> newJobCh;
    private volatile boolean running = false;
    //private final AtomicBoolean running = new AtomicBoolean(false);
    private volatile boolean newJob = false;
    //private final AtomicBoolean newJob = new AtomicBoolean(false);
    private String targetBlob;

    public static void main(String[] args) {
        //Miner m = new Miner();
        //m.setCoin("alph");
        //m.setPool("192.168.3.79:21233");
        //m.setUser("alphtest");
        //m.setPassword("jxys123546");
        int count =1;
        int start =0;
        ExecutorService executor = Executors.newFixedThreadPool(count);  // 创建一个固定大小的线程池
        for(int cnt=0;cnt < count;cnt++){
            int id = start+cnt;
            System.out.println(id);
            executor.execute(() -> {
                Miner m = new Miner();
                m.setCoin("alph");
                m.setPool("10.168.2.167:21233");
                m.setUser("alphtest");
                m.setPassword("jxys123546");
                String newUser = m.user + "." + Convert.toStr(id);
                m.setUser(newUser);
                System.out.println(m.getUser()+"已创建线程");

                m.newJobCh = new LinkedBlockingQueue<>(1);

                m.setNewJob(false);
                try {
                    //String host, int port
                    //Socket socket = new Socket("192.168.3.79",
                    //        Integer.parseInt(m.getPool().split(":")[1]));
                    Socket socket = new Socket("10.168.2.167",21233);
                    //Socket socket = new Socket("10.168.2.167",12973);
                    //Socket socket = new Socket("192.168.3.82",12973);
                    m.setConn(socket);
                    //System.out.println("miner:"+m);
                    //BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(m.getConn().getOutputStream()));
                    //System.out.println("writer:"+writer);
                    System.out.println("out:"+m.getConn().getOutputStream());
                    //m.getConn().getOutputStream().write(m.subscribe().getBytes());
                    m.getConn().getOutputStream().write(m.authorize().getBytes());
                    m.getConn().getOutputStream().flush();

                    //InputStream reader = m.getConn().getInputStream();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(m.getConn().getInputStream()));
                    String message;
                    while ((message = reader.readLine()) != null) {
                        System.out.println("Received: " + message);
                        Map<String, Object> json = m.mapper.readValue(message, Map.class);
                        if (json.containsKey("method")) {
                            m.handle(message,m.getConn());
                        } else {
                            System.out.println("未解析到method属性:"+message);
                        }
                    }
                } catch (IOException e) {
                    System.out.println(newUser+"连接矿池["+m.getPool()+"]失败！");
                    System.out.println("------------------------------------原因如下------------------------------------");
                    e.printStackTrace();
                    System.out.println("------------------------------------分割线------------------------------------");
                }

            });
        }
    }

    private  String subscribe(){
        //this.setState("subscribe");
        state="subscribe";
        // fmt.Sprintf("{\"id\": %d,
        // \"method\": \"mining.subscribe\",
        // \"params\": [\"YxMiner/v20.0.0\"]}\n", m.Id)
        return String.format("{\"id\": %d, \"method\": \"mining.subscribe\", \"params\": [\"YxMiner/v20.0.0\"]}\n",id);
    }

    private  String authorize(){
        state="authorize";
        // fmt.Sprintf(
        //		"{\"id\": %d, \"jsonrpc\": \"2.0\", \"method\": \"mining.authorize\", \"params\": [\"%s\"]}\n",
        return String.format("{\"id\": %d, \"jsonrpc\": \"2.0\", \"method\": \"mining.authorize\", \"params\": [\"%s\"]}\n",id,user);
    }

    private  String submit(String nonce){
        state = "submit";
        if(StringUtils.isNull(extraNonce1)){
            extraNonce1="";
        }
        String finalNonce = extraNonce1.concat(nonce);
        //fmt.Sprintf(
        //		"{\"params\": [\"%s\", \"%s\", \"%s\"], \"id\": %d, \"method\": \"mining.submit\"}\n",
        //		m.User,
        //		m.JobId,
        //		final_nonce,
        //		m.Id,
        return String.format("{\"params\": [\"%s\", \"%s\", \"%s\"], \"id\": %d, \"method\": \"mining.submit\"}\n",
                user,
                jobId,
                finalNonce,
                id);

    }

    private  String pong(double pingId){
        BigInteger pongID = BigInteger.valueOf(Convert.toLong(pingId,0L));
        //
        return String.format("{\"id\": %d, \"result\": \"pong\", \"error\": null}\n",
                pongID);
    }

    private  byte[] hash(Miner m) throws DecoderException {
        // 解码Header
        //byte[] headerBytes = Hex.decodeHex(m.getHeader().toCharArray());
        ////自行生成40位？
        //long nonce = m.getNonce();
        //m.setBlockNonce(m.getNonce());
        //byte[] nonceBytes = ByteBuffer.allocate(8)
        //        .order(ByteOrder.LITTLE_ENDIAN)
        //        .putLong(nonce)
        //        .array();
        //// 解码ExtraNonce1
        //byte[] extraBytes = null;
        //extraBytes = Hex.decodeHex(m.getExtraNonce1().toCharArray());
        //byte[] inb = new byte[49];
        //System.arraycopy(headerBytes, 0, inb, 0, 32);    // 前32字节
        //inb[32] = 0x10;                                   // 固定分隔符
        //System.arraycopy(nonceBytes, 0, inb, 33, 8);      // 33-40字节
        //System.arraycopy(extraBytes, 0, inb, 41, 8);      // 41-48字节
        //
        //m.setBlockData(Hex.encodeHexString(inb));

        //header+nonce nonce是自行生成是长度为40的十六进制字符串转的bigint
        byte[] bytes = new byte[20];
        new SecureRandom().nextBytes(bytes);

        // 转换为正数（BigInteger 自动处理符号）
        BigInteger nonce = new BigInteger(1, bytes);
        System.out.println("随机生成的nonce:"+String.format("%040x", nonce));
        System.out.println("(BigInt)nonce:"+nonce);

        m.setNonce(nonce);
        m.blockNonce = m.nonce;
        System.out.println("header "+m.getHeader());
        System.out.println("in  "+m.getExtraNonce1().concat(String.format("%040x", nonce)).concat(m.getHeader()));
        System.out.println("------------ in end ------------");
        //byte[] hashResult = hexStringToBytes(hashHandle(m.getExtraNonce1().concat(String.format("%040x", nonce)).concat(m.getHeader())));
        String s = hashHandle(m.getExtraNonce1().concat(String.format("%040x", nonce)).concat(m.getHeader()));
        System.out.println("result:"+s);
        return s.getBytes();
    }

    public  BigInteger  difficultyToTarget(double diff) throws ArithmeticException{
        if (diff <= 0) {
            throw new ArithmeticException("Difficulty must be greater than zero");
        }

        // 创建大整数 2^224
        BigInteger twoTo224 = BigInteger.ONE.shiftLeft(224);

        // 使用 big.Float 进行高精度计算
        BigDecimal difficultyDecimal = new BigDecimal(diff);

        // 将 2^224 转换为 big.Float
        BigDecimal   twoTo224Decimal = new BigDecimal(twoTo224);

        // 计算目标值 = 2^224 / 难度
        try {
            // 使用UNNECESSARY模式确保精确除法
            BigDecimal targetDecimal = twoTo224Decimal.divide(
                    difficultyDecimal,
                    new MathContext(0, RoundingMode.UNNECESSARY)
            );

            // 4. 转换为BigInteger
            return targetDecimal.toBigIntegerExact();

        } catch (ArithmeticException e) {
            throw new ArithmeticException("Conversion resulted in inexact result: " + e.getMessage());
        }
    }

    private  void lookup(Miner m){
        //
        System.out.println("Lookup start");
        m.running = true;

        while (m.running){
            try {
                if(m.newJob){
                    m.newJob = false;
                    //m.getNewJobCh().poll(10, TimeUnit.MILLISECONDS);.
                    m.getNewJobCh().poll(10, TimeUnit.MILLISECONDS);
                    //m.getNewJobCh().take();//阻塞直到有通知
                    System.out.println("Lookup New job");
                }
                byte[] hashBytes = hash(m);
                m.nonce = m.nonce.add(BigInteger.ONE);
                // 比较哈希难度
                BigInteger hashInt = new BigInteger(1, hashBytes);
                System.out.println("hashInt "+hashInt);
                //BigInteger diffInt = difficultyToTarget(m.difficulty);
                BigInteger diffInt = new BigInteger(1,targetBlob.getBytes());
                System.out.println("diffInt "+diffInt);

                if (hashInt.compareTo(diffInt) <= 0) {
                    // 比较当前target（hashint）和下发的任务target （diffint） <=0代表

                    System.out.printf("calc diff %s\ntarget diff %s\n",
                            hashInt.toString(), diffInt.toString());
                    System.out.println("block data " + m.blockData);

                    // 准备提交数据
                    ByteBuffer buffer = ByteBuffer.allocate(20)
                            .order(ByteOrder.LITTLE_ENDIAN)
                            .putLong(m.blockNonce.longValue());
                    System.out.println(buffer);
                    //alph的submitData应该是40个字符
                    String submitData = Hex.encodeHexString(buffer.array());

                    // 发送提交请求
                    String response = submit(submitData);
                    System.out.println("submit " + response);
                    m.getConn().getOutputStream().write(response.getBytes());
                }
                Thread.sleep(10); // 10毫秒间隔
            }catch (InterruptedException | IOException | DecoderException e){
                System.out.println("Error in lookup: " + e.getMessage());
                m.running = false;
            }

        }
        System.out.println("Lookup end");
    }

    public  void handle(String msg, Socket conn){
        //
        System.out.println("handle执行");
        this.conn = conn;
        try {
            Map<String,Object> ret = mapper.readValue(msg,Map.class);
            if (ret.containsKey("method")){
                String method = Convert.toStr(ret.get("method"));
                switch (method){
                    case "mining.notify":
                        handleNotify(msg);
                        break;
                    case "mining.set_difficulty":
                        handleSetDifficulty(msg);
                        break;
                    case "mining.set_extranonce":
                        handleSetExtranoce(msg);
                        break;
                    case "mining.ping":
                        handlePing(msg);
                        break;
                }
            }else {
                handleStatefulMessage(msg);
            }
        }catch (IOException e){
            System.out.println("JSON解析错误: " + e.getMessage());
        }

    }
    static class NotifyMsgNexa {
        public Object id;
        public String method;
        public List<NotifyMsgParams> params;
    }
    //static class NotifyMsgParams {
    //    public String jobId;
    //
    //    //jobId
    //    public int fromGroup;
    //    //header
    //    public int toGroup;
    //    //header
    //    public String headerBlob;
    //    public String txsBlob;
    //    public String targetBlob;
    //    public long height;
    //    public double diff;
    //    public String nonce;
    //    public String extranonce1;
    //    public long extranonce2_size;
    //}



    public  void handleNotify(String msg) throws IOException {
        System.out.println("handleNotify执行");
        NotifyMsgNexa notify = mapper.readValue(msg, NotifyMsgNexa.class);

        if (running) {
            newJob = true;
            while (newJob) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
        this.jobId = notify.params.get(0).getJobId();
        this.header = notify.params.get(0).getHeader();
        this.targetBlob = notify.params.get(0).getTarget();
        //this.nbits = (String) notify.params[2];
        //this.jobTime = Convert.toLong(notify.params[3],0l);
        //是否要将解析到的diff、nonce、extranonce1赋值到矿机？
        this.nonce = BigInteger.ZERO;

        if (!running) {
            //System.out.println("jobId:"+this.jobId+",header:"+this.header);
            System.out.println("this      "+this+"      |");
            this.lookup(this);
        } else {
            newJobCh.offer(true);
        }
    }

    private void handleSetDifficulty(String msg) throws IOException {
        //stratum.Difficulty_msg stratum是第三方库"pool/internal/stratum"
        System.out.println("setDiff msg:|   "+msg+"   |");
        DifficultyMsg diff = mapper.readValue(msg, DifficultyMsg.class);
        System.out.println(diff);
        //stratum
        this.difficulty = diff.params[0];
    }

    private void handleSetExtranoce(String msg) throws IOException {
        //stratum.Difficulty_msg stratum是第三方库"pool/internal/stratum"
        ExtranonceMsg extranonce = mapper.readValue(msg, ExtranonceMsg.class);

        //stratum
        this.extraNonce1 = extranonce.params[0]+"0000";
        System.out.println("extranonce"+extranonce+",miner extranonce:"+this.extraNonce1);
    }
    private void handlePing(String msg) throws IOException {
        System.out.println("执行ping响应");
        System.out.println("ping msg:|   "+msg+"   |");
        PingMsg ping = mapper.readValue(msg, PingMsg.class);
        String pong = pong(ping.id);
        System.out.println(this.user+" pong:"+pong);
        conn.getOutputStream().write(pong.getBytes());
        System.out.println("ping响应执行完毕");
    }

    private void handleStatefulMessage(String msg) throws IOException {
        switch (state) {
            case "subscribe":
                SubscribeReply subscribeReply = mapper.readValue(msg, SubscribeReply.class);
                this.extraNonce1 = (String) subscribeReply.result[1];
                this.extraNonce2Size = ((Number) subscribeReply.result[2]).intValue();
                this.id++;
                conn.getOutputStream().write(authorize().getBytes());
                break;

            case "authorize":
                AuthorizeReply authReply = mapper.readValue(msg, AuthorizeReply.class);
                // 处理授权响应
                //authorize();
                break;

            case "submit":
                SubmitAck submitAck = mapper.readValue(msg, SubmitAck.class);
                // 处理提交确认
                //submit(nonce);
                break;
        }
    }



    private  String hashHandle(String in)     {
        //字符串判空
        if(StringUtils.isBlank(in)){
            return null;
        }
        Blake3 hasher = Blake3.initHash();
        byte[] hash = new byte[32];
        hasher.update(hexStringToBytes(in));
        hasher.doFinalize(hash);
        String result1 = bytesToHexString(hash);
        System.out.println(result1);

        hasher.reset();
        byte[] hash2 = new byte[32];
        hasher.update(hexStringToBytes(result1));
        hasher.doFinalize(hash2);
        String result2 = bytesToHexString(hash2);
        System.out.println(result2);
        return  result2;
    }

    /**
     * 高效写法 16进制字符串转成byte数组
     *
     * @param hex 16进制字符串，支持大小写
     * @return byte数组
     */
    public  byte[] hexStringToBytes(String hex) {
        System.out.println("hex:"+hex+" |");
        System.out.println(hex.length() / 2);
        byte[] result = new byte[hex.length() / 2];
        char[] chars = hex.toCharArray();
        for (int i = 0, j = 0; i < result.length; i++) {
            result[i] = (byte) (toByte(chars[j++]) << 4 | toByte(chars[j++]));
        }
        return result;
    }

    private  int toByte(char c) {
        if (c >= '0' && c <= '9') return (c - '0');
        if (c >= 'A' && c <= 'F') return (c - 'A' + 0x0A);
        if (c >= 'a' && c <= 'f') return (c - 'a' + 0x0a);
        throw new RuntimeException("invalid hex char '" + c + "'");
    }


    /**
     * 高效写法 byte数组转成16进制字符串
     *
     * @param bytes byte数组
     * @return 16进制字符串
     */
    public  String bytesToHexString(byte[] bytes) {
        char[] buf = new char[bytes.length * 2];
        int c = 0;
        for (byte b : bytes) {
            buf[c++] = digits[(b >> 4) & 0x0F];
            buf[c++] = digits[b & 0x0F];
        }
        return new String(buf);
    }

    static class DifficultyMsg {
        public int id;
        public String method;
        public double[] params;
    }

    static class ExtranonceMsg {
        public int id;
        public String method;
        public String[] params;
    }

    static class PingMsg {
        public int id;
        public String method;
        public String params;
    }

    static class SubscribeReply {
        public Object[] result;
    }

    static class AuthorizeReply {
        public boolean result;
    }

    static class SubmitAck {
        public boolean result;
    }
}
