package com.lanyuanxiaoyao.service.web.controller;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.lanyuanxiaoyao.service.common.entity.FlinkJob;
import com.lanyuanxiaoyao.service.common.entity.RunMeta;
import com.lanyuanxiaoyao.service.common.entity.SyncState;
import com.lanyuanxiaoyao.service.common.entity.TableMeta;
import com.lanyuanxiaoyao.service.common.utils.NameHelper;
import com.lanyuanxiaoyao.service.configuration.ExecutorProvider;
import com.lanyuanxiaoyao.service.configuration.entity.Item;
import com.lanyuanxiaoyao.service.configuration.entity.PageResponse;
import com.lanyuanxiaoyao.service.configuration.entity.info.CompactionMetrics;
import com.lanyuanxiaoyao.service.configuration.entity.info.JobIdAndAlias;
import com.lanyuanxiaoyao.service.forest.service.InfoService;
import com.lanyuanxiaoyao.service.forest.service.ZookeeperService;
import com.lanyuanxiaoyao.service.web.controller.base.AmisCrudResponse;
import com.lanyuanxiaoyao.service.web.controller.base.AmisMapResponse;
import com.lanyuanxiaoyao.service.web.controller.base.AmisResponse;
import com.lanyuanxiaoyao.service.web.controller.base.BaseController;
import com.lanyuanxiaoyao.service.web.entity.CompactionMetricsVO;
import com.lanyuanxiaoyao.service.web.entity.FlinkJobVO;
import com.lanyuanxiaoyao.service.web.entity.SyncStateVO;
import com.lanyuanxiaoyao.service.web.entity.TableVO;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.list.ImmutableList;
import org.eclipse.collections.api.map.MutableMap;
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;

/**
 * 表
 *
 * @author lanyuanxiaoyao
 * @date 2023-04-24
 */
@RestController
@RequestMapping("table")
public class TableController extends BaseController {
    private static final Logger logger = LoggerFactory.getLogger(TableController.class);
    private final InfoService infoService;
    private final ZookeeperService zookeeperService;
    private final ObjectMapper mapper;

    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    public TableController(InfoService infoService, ZookeeperService zookeeperService, Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder) {
        this.infoService = infoService;
        this.zookeeperService = zookeeperService;
        this.mapper = jackson2ObjectMapperBuilder.build();
    }

    @GetMapping("list")
    public AmisResponse list(
            @RequestParam(value = "page", defaultValue = "1") Integer page,
            @RequestParam(value = "count", defaultValue = "10") Integer count,
            @RequestParam(value = "order", required = false) String order,
            @RequestParam(value = "direction", required = false) String direction,
            @RequestParam(value = "search_flink_job_id", required = false) String searchFlinkJobId,
            @RequestParam(value = "search_alias", required = false) String searchAlias,
            @RequestParam(value = "filter_hudi_table_type", required = false) List<String> hudiTableType,
            @RequestParam(value = "filter_run_mode", required = false) List<String> runMode,
            @RequestParam(value = "filter_compaction_status", required = false) List<String> compactionStatus
    ) {
        MutableMap<String, Object> queryMap = buildQueryMap(page, count, order, direction, searchFlinkJobId, searchAlias);
        if (ObjectUtil.isNotEmpty(hudiTableType)) {
            queryMap.put("filter_hudi_table_type", hudiTableType);
        }
        if (ObjectUtil.isNotEmpty(runMode)) {
            queryMap.put("filter_run_mode", runMode);
        }
        if (ObjectUtil.isNotEmpty(compactionStatus)) {
            queryMap.put("filter_compaction_status", compactionStatus);
        }
        PageResponse<JobIdAndAlias> pageResponse = infoService.jobIdAndAlias(queryMap);
        Long total = pageResponse.getTotal();
        ImmutableList<TableVO> tableVOS = Lists.immutable.ofAll(pageResponse.getData())
                .asParallel(ExecutorProvider.EXECUTORS, 1)
                .collect(item -> {
                    CompletableFuture<FlinkJob> flinkJobFuture = CompletableFuture.supplyAsync(() -> infoService.flinkJobDetail(item.getId()), ExecutorProvider.EXECUTORS);
                    CompletableFuture<TableMeta> tableMetaFuture = CompletableFuture.supplyAsync(() -> infoService.tableMetaDetail(item.getId(), item.getAlias()), ExecutorProvider.EXECUTORS);
                    CompletableFuture<SyncState> syncStateFuture = CompletableFuture.supplyAsync(() -> infoService.syncStateDetail(item.getId(), item.getAlias()), ExecutorProvider.EXECUTORS);
                    CompletableFuture<RunMeta> syncRuntime = CompletableFuture
                            .supplyAsync(() -> zookeeperService.existsPath(NameHelper.syncRunningLockPath(item.getId(), item.getAlias())), ExecutorProvider.EXECUTORS)
                            .thenApply(running -> {
                                if (running) {
                                    try {
                                        String data = zookeeperService.getData(NameHelper.syncRunningLockPath(item.getId(), item.getAlias()));
                                        if (StrUtil.isNotBlank(data)) {
                                            return mapper.readValue(data, RunMeta.class);
                                        }
                                    } catch (Exception e) {
                                        logger.error("Parse sync runtime info for: {}", item.getId());
                                    }
                                }
                                return null;
                            });
                    CompletableFuture<RunMeta> compactionRuntime = CompletableFuture
                            .supplyAsync(() -> zookeeperService.existsPath(NameHelper.compactionRunningLockPath(item.getId(), item.getAlias())), ExecutorProvider.EXECUTORS)
                            .thenApply(running -> {
                                if (running) {
                                    try {
                                        String data = zookeeperService.getData(NameHelper.compactionRunningLockPath(item.getId(), item.getAlias()));
                                        if (StrUtil.isNotBlank(data)) {
                                            return mapper.readValue(data, RunMeta.class);
                                        }
                                    } catch (Exception e) {
                                        logger.error("Parse sync runtime info for: {}", item.getId());
                                    }
                                }
                                return null;
                            });
                    try {
                        CompletableFuture.allOf(
                                flinkJobFuture,
                                tableMetaFuture,
                                syncStateFuture,
                                syncRuntime,
                                compactionRuntime
                        ).get();
                        RunMeta syncRunMeta = syncRuntime.get();
                        RunMeta compactionRunMeta = compactionRuntime.get();
                        return new TableVO(
                                flinkJobFuture.get(),
                                tableMetaFuture.get(),
                                Optional.of(syncStateFuture.get()).map(SyncStateVO::new).orElse(null),
                                ObjectUtil.isNotNull(syncRunMeta),
                                syncRunMeta,
                                ObjectUtil.isNotNull(compactionRunMeta),
                                compactionRunMeta
                        );
                    } catch (InterruptedException | ExecutionException e) {
                        logger.error("Something bad", e);
                        return null;
                    }
                })
                .reject(ObjectUtil::isNull)
                .toList()
                .toImmutable();
        return AmisResponse.responseCrudData(tableVOS, tableVOS.size() == 0 ? 0 : total);
    }

    @GetMapping("list_metas")
    public AmisCrudResponse listTableMetas(@RequestParam("flink_job_id") Long flinkJobId) throws Exception {
        if (ObjectUtil.isNull(flinkJobId)) {
            throw new Exception("flink job id is null");
        }
        return AmisResponse.responseCrudData(infoService.tableMetaList(flinkJobId).collect(meta -> new TableVO(null, meta, null, null, null, null, null)));
    }

    @GetMapping("detail")
    public AmisMapResponse getMeta(@RequestParam("flink_job_id") Long flinkJobId, @RequestParam("alias") String alias) throws Exception {
        if (ObjectUtil.isNull(flinkJobId) || ObjectUtil.isNull(alias)) {
            throw new Exception("flink job id or alias is null");
        }
        CompletableFuture<FlinkJob> flinkJobFuture = CompletableFuture.supplyAsync(() -> infoService.flinkJobDetail(flinkJobId), ExecutorProvider.EXECUTORS);
        CompletableFuture<TableMeta> tableMetaFuture = CompletableFuture.supplyAsync(() -> infoService.tableMetaDetail(flinkJobId, alias), ExecutorProvider.EXECUTORS);
        CompletableFuture.allOf(flinkJobFuture, tableMetaFuture).get();
        return AmisResponse.responseMapData()
                .setData("flinkJob", new FlinkJobVO(flinkJobFuture.get()))
                .setData("tableMeta", tableMetaFuture.get());
    }

    @GetMapping("list_compaction_metrics")
    public AmisCrudResponse listCompactionMetrics(
            @RequestParam(value = "page", defaultValue = "1") Integer page,
            @RequestParam(value = "count", defaultValue = "10") Integer count,
            @RequestParam(value = "order", required = false) String order,
            @RequestParam(value = "direction", required = false) String direction,
            @RequestParam(value = "search_flink_job_id") String searchFlinkJobId,
            @RequestParam(value = "search_alias") String searchAlias,
            @RequestParam(value = "filter_completes", required = false) List<Boolean> filterCompletes
    ) throws Exception {
        if (ObjectUtil.isNull(searchFlinkJobId) || ObjectUtil.isNull(searchAlias)) {
            throw new Exception("flink job id or alias is null");
        }
        MutableMap<String, Object> queryMap = buildQueryMap(page, count, order, direction, searchFlinkJobId, searchAlias);
        if (ObjectUtil.isNotEmpty(filterCompletes)) {
            queryMap.put("filter_completes", filterCompletes);
        }
        PageResponse<CompactionMetrics> pageResponse = infoService.compactionMetrics(queryMap);
        ImmutableList<CompactionMetricsVO> vos = Lists.immutable.ofAll(pageResponse.getData()).collect(CompactionMetricsVO::new);
        return AmisResponse.responseCrudData(vos, pageResponse.getTotal());
    }

    @SuppressWarnings("DataFlowIssue")
    @GetMapping("all_flink_job_id")
    public AmisResponse<ImmutableList<Item>> allFlinkJobId(@RequestParam(value = "alias", required = false) String alias) {
        if (StrUtil.isBlank(alias)) {
            return AmisResponse.responseSuccess(infoService.allFlinkJobId().collect(Item::new));
        } else {
            return AmisResponse.responseSuccess(infoService.allFlinkJobIdByAlias(alias).collect(Item::new));
        }
    }

    @SuppressWarnings("DataFlowIssue")
    @GetMapping("all_alias")
    public AmisResponse<ImmutableList<Item>> allAlias(@RequestParam(value = "flink_job_id", required = false) String flinkJobId) {
        if (StrUtil.isBlank(flinkJobId)) {
            return AmisResponse.responseSuccess(infoService.allAlias().collect(Item::new));
        } else {
            return AmisResponse.responseSuccess(infoService.allAliasByFlinkJobId(flinkJobId).collect(Item::new));
        }
    }

    @SuppressWarnings("DataFlowIssue")
    @GetMapping("all_hdfs")
    public AmisResponse<ImmutableList<Item>> allHdfs(@RequestParam(value = "key", required = false) String key) {
        if (StrUtil.isBlank(key)) {
            return AmisResponse.responseSuccess(infoService.allHdfs().collect(Item::new));
        }
        return AmisResponse.responseSuccess(infoService.allHdfs(key).collect(Item::new));
    }

    @SuppressWarnings("DataFlowIssue")
    @GetMapping("all_pulsar")
    public AmisResponse<ImmutableList<Item>> allPulsar(@RequestParam(value = "key", required = false) String key) {
        if (StrUtil.isBlank(key)) {
            return AmisResponse.responseSuccess(infoService.allPulsar().collect(Item::new));
        }
        return AmisResponse.responseSuccess(infoService.allPulsar(key).collect(Item::new));
    }

    @SuppressWarnings("DataFlowIssue")
    @GetMapping("all_pulsar_topic")
    public AmisResponse<ImmutableList<Item>> allPulsarTopic(@RequestParam(value = "key", required = false) String key) {
        if (StrUtil.isBlank(key)) {
            return AmisResponse.responseSuccess(infoService.allPulsarTopic().collect(Item::new));
        }
        return AmisResponse.responseSuccess(infoService.allPulsarTopic(key).collect(Item::new));
    }
}
