package com.lanyuanxiaoyao.service.zookeeper;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lanyuanxiaoyao.service.common.Constants;
import com.lanyuanxiaoyao.service.common.entity.RunMeta;
import com.lanyuanxiaoyao.service.common.utils.NameHelper;
import com.lanyuanxiaoyao.service.configuration.entity.zookeeper.ZookeeperNode;
import java.util.concurrent.TimeUnit;
import javax.annotation.PreDestroy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.data.Stat;
import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.list.ImmutableList;
import org.eclipse.collections.api.list.MutableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * Zookeeper 查询
 *
 * @author lanyuanxiaoyao
 * @date 2023-05-13
 */

@RestController
@RequestMapping("zookeeper")
public class ZookeeperController {
    private static final Logger logger = LoggerFactory.getLogger(ZookeeperController.class);

    private final CuratorFramework client;
    private final ObjectMapper mapper;

    public ZookeeperController(ZookeeperConfiguration zookeeperConfiguration, Jackson2ObjectMapperBuilder builder) {
        this.client = CuratorFrameworkFactory.builder()
                .connectString(zookeeperConfiguration.getConnectUrl())
                .retryPolicy(new ExponentialBackoffRetry((int) Constants.SECOND, 3))
                .sessionTimeoutMs((int) Constants.SECOND)
                .connectionTimeoutMs((int) (30 * Constants.SECOND))
                .build();
        client.start();

        this.mapper = builder.build();
    }

    @PreDestroy
    public void destroy() {
        if (ObjectUtil.isNotNull(client)) {
            client.close();
        }
    }

    @GetMapping("exists_path")
    public Boolean existsPath(@RequestParam("path") String path) throws Exception {
        return ObjectUtil.isNotNull(client.checkExists().forPath(path));
    }

    @GetMapping("get_data")
    public String getData(@RequestParam("path") String path) throws Exception {
        if (existsPath(path)) {
            return new String(client.getData().forPath(path));
        } else {
            return "";
        }
    }

    @GetMapping("get_children")
    public ImmutableList<ZookeeperNode> children(@RequestParam("path") String path) throws Exception {
        if (existsPath(path)) {
            MutableList<ZookeeperNode> nodes = Lists.mutable.empty();
            for (String label : client.getChildren().forPath(path)) {
                String childPath = path + "/" + label;
                Stat stat = client.checkExists().forPath(childPath);
                nodes.add(new ZookeeperNode(childPath, label, stat.getCtime(), stat.getMtime()));
            }
            return nodes.toImmutable();
        }
        return Lists.immutable.empty();
    }

    @GetMapping("exists_lock")
    public Boolean existsLock(@RequestParam("path") String path) throws Exception {
        InterProcessLock lock = new InterProcessMutex(client, path);
        try {
            return !lock.acquire(1, TimeUnit.SECONDS);
        } finally {
            if (ObjectUtil.isNotNull(lock) && lock.isAcquiredInThisProcess()) {
                try {
                    lock.release();
                } catch (Exception e) {
                    logger.error("Release lock failure", e);
                }
            }
        }
    }

    @GetMapping("get_sync_meta_by_flink_job_id")
    public RunMeta getSyncRunMeta(@RequestParam("flink_job_id") Long flinkJobId) throws Exception {
        return getRunMeta(NameHelper.syncRunningLockPath(flinkJobId));
    }

    @GetMapping("get_sync_meta_by_flink_job_id_and_alias")
    public RunMeta getSyncRunMeta(@RequestParam("flink_job_id") Long flinkJobId, @RequestParam("alias") String alias) throws Exception {
        return getRunMeta(NameHelper.syncRunningLockPath(flinkJobId, alias));
    }

    @GetMapping("get_compaction_meta_by_flink_job_id_and_alias")
    public RunMeta getCompactionRunMeta(@RequestParam("flink_job_id") Long flinkJobId, @RequestParam("alias") String alias) throws Exception {
        return getRunMeta(NameHelper.compactionRunningLockPath(flinkJobId, alias));
    }

    @GetMapping("get_run_meta")
    public RunMeta getRunMeta(@RequestParam("path") String path) throws Exception {
        String data = getData(path);
        if (StrUtil.isNotBlank(data)) {
            return mapper.readValue(data, RunMeta.class);
        } else {
            throw new RuntimeException(StrUtil.format("{} is empty", path));
        }
    }
}
