package cn.reghao.tnb.sb.zk.cluster;

import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.*;

import jakarta.annotation.PostConstruct;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @author reghao
 * @date 2023-03-28 15:05:01
 */
@Slf4j
//@Component
public class ClusterNode {
    private Status status;
    private final String MASTER_NODE = "/leader-info";
    private final CuratorFramework client;

    public ClusterNode() throws IOException {
        String connectString = "127.0.0.1:2181";
        client = CuratorFrameworkFactory.builder()
                .connectString(connectString)
                .sessionTimeoutMs(5000)
                .connectionTimeoutMs(3000)
                .retryPolicy(new ExponentialBackoffRetry(1000, 3))
                .build();
        client.start();
    }

    public List<String> getElectionNodes() throws Exception {
        return client.getChildren().forPath("/election");
    }

    //@PostConstruct
    public void electLeader() {
        status = Status.LOOKING;
        try {
            String leaderInfo = Thread.currentThread().getName();

            /*CuratorOp op = client.transactionOp().create()
                    .withMode(CreateMode.EPHEMERAL)
                    .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
                    .forPath(MASTER_NODE, leaderInfo.getBytes(StandardCharsets.UTF_8));
            List<CuratorTransactionResult> results = client.transaction().forOperations(op);
            Stat stat = results.get(0).getResultStat();
            long czxId = stat.getCzxid();*/

            // 临时节点, 连接断开 zk 会自动删除
            client.create()
                    .withMode(CreateMode.EPHEMERAL)
                    .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
                    .forPath(MASTER_NODE, leaderInfo.getBytes(StandardCharsets.UTF_8));
            status = Status.LEADER;
            String logMsg = Thread.currentThread().getName() + " is leader";
            System.out.println(logMsg);
        } catch (Exception e) {
            if (e instanceof KeeperException.NodeExistsException) {
                status = Status.FOLLOWER;
                try {
                    byte[] leaderInfoBytes = client.getData()
                            .usingWatcher((Watcher) event -> {
                                // 监听 leader 节点状态
                                if (event.getType() == Watcher.Event.EventType.NodeDeleted) {
                                    electLeader();
                                }
                            })
                            .forPath(MASTER_NODE);

                    String logMsg = Thread.currentThread().getName() + " is follower, master is " + new String(leaderInfoBytes, "UTF-8");
                    System.out.println(logMsg);
                } catch (Exception e1) {
                    if (e1 instanceof KeeperException.NoNodeException) {
                        electLeader();
                    } else {
                        e1.printStackTrace();
                    }
                }
            } else {
                e.printStackTrace();
            }
        }
    }

    public void shutdown() {
        client.close();
    }

    public Status getStatus() {
        return status;
    }

    // 当前节点的状态，节点的状态必须在这三个中的一个
    public enum Status {
        LOOKING, // 选举中
        LEADER, // 选举完毕，当前节点为leader
        FOLLOWER; // 选举完毕，当前节点为follower
    }
}
