package io.gisbi.datasource.manage;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import io.gisbi.commons.constants.TaskStatus;
import io.gisbi.dataset.utils.TableUtils;
import io.gisbi.datasource.dao.auto.entity.CoreDatasource;
import io.gisbi.datasource.dao.auto.entity.CoreDatasourceTask;
import io.gisbi.datasource.dao.auto.entity.CoreDatasourceTaskLog;
import io.gisbi.datasource.dao.auto.entity.CoreDeEngine;
import io.gisbi.datasource.dao.auto.mapper.CoreDatasourceMapper;
import io.gisbi.datasource.provider.*;
import io.gisbi.datasource.request.EngineRequest;
import io.gisbi.datasource.server.DatasourceServer;
import io.gisbi.datasource.server.DatasourceTaskServer;
import io.gisbi.exception.DEException;
import io.gisbi.extensions.datasource.dto.DatasetTableDTO;
import io.gisbi.extensions.datasource.dto.DatasourceDTO;
import io.gisbi.extensions.datasource.dto.DatasourceRequest;
import io.gisbi.extensions.datasource.dto.TableField;
import io.gisbi.extensions.datasource.vo.DatasourceConfiguration;
import io.gisbi.job.schedule.ExtractDataJob;
import io.gisbi.job.schedule.ScheduleManager;
import io.gisbi.utils.BeanUtils;
import io.gisbi.utils.LogUtil;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.quartz.JobExecutionContext;
import org.quartz.JobKey;
import org.quartz.TriggerKey;
import org.springframework.stereotype.Component;

import java.util.*;

import static io.gisbi.datasource.server.DatasourceTaskServer.ScheduleType.CRON;
import static io.gisbi.datasource.server.DatasourceTaskServer.ScheduleType.MANUAL;

@Component
public class DatasourceSyncManage {

    @Resource
    private CoreDatasourceMapper datasourceMapper;
    @Resource
    private EngineManage engineManage;
    @Resource
    private DatasourceTaskServer datasourceTaskServer;
    @Resource
    private ScheduleManager scheduleManager;
    @Resource
    private CalciteProvider calciteProvider;
    @Resource
    private DatasourceServer datasourceServer;


    public void extractExcelData(CoreDatasource coreDatasource, String type) {
        if (coreDatasource == null) {
            LogUtil.error("Can not find CoreDatasource: " + coreDatasource.getName());
            return;
        }
        DatasourceServer.UpdateType updateType = DatasourceServer.UpdateType.valueOf(type);
        DatasourceRequest datasourceRequest = new DatasourceRequest();
        datasourceRequest.setDatasource(transDTO(coreDatasource));
        List<DatasetTableDTO> tables = ExcelUtils.getTables(datasourceRequest);
        for (DatasetTableDTO tableDTO : tables) {
            CoreDatasourceTaskLog datasetTableTaskLog = datasourceTaskServer.initTaskLog(coreDatasource.getId(), null, tableDTO.getTableName(), CRON.toString());
            datasourceRequest.setTable(tableDTO.getTableName());
            List<TableField> tableFields = ExcelUtils.getTableFields(datasourceRequest);
            try {
                datasetTableTaskLog.setInfo(datasetTableTaskLog.getInfo() + "/n Begin to sync datatable: " + datasourceRequest.getTable());
                createEngineTable(datasourceRequest.getTable(), tableFields);
                if (updateType.equals(DatasourceServer.UpdateType.all_scope)) {
                    createEngineTable(TableUtils.tmpName(datasourceRequest.getTable()), tableFields);
                }
                extractExcelData(datasourceRequest, updateType, tableFields);
                if (updateType.equals(DatasourceServer.UpdateType.all_scope)) {
                    replaceTable(datasourceRequest.getTable());
                }
                datasetTableTaskLog.setInfo(datasetTableTaskLog.getInfo() + "/n End to sync datatable: " + datasourceRequest.getTable());
                datasetTableTaskLog.setTaskStatus(TaskStatus.Completed.toString());
            } catch (Exception e) {
                try {
                    if (updateType.equals(DatasourceServer.UpdateType.all_scope)) {
                        dropEngineTable(TableUtils.tmpName(datasourceRequest.getTable()));
                    }
                } catch (Exception ignore) {
                }
                datasetTableTaskLog.setTaskStatus(TaskStatus.Error.toString());
                datasetTableTaskLog.setInfo(datasetTableTaskLog.getInfo() + "/n Failed to sync datatable: " + datasourceRequest.getTable() + ", " + e.getMessage());
                if (e.getMessage().contains("Duplicate entry")) {
                    DEException.throwException("不能追加主键相同的数据, " + e.getMessage());
                } else {
                    DEException.throwException(e);
                }

            } finally {
                datasourceTaskServer.saveLog(datasetTableTaskLog);
            }
        }
    }


    public void extractData(Long datasourceId, Long taskId, JobExecutionContext context) {
        CoreDatasource coreDatasource = datasourceMapper.selectById(datasourceId);
        if (coreDatasource == null) {
            LogUtil.error("Can not find datasource: " + datasourceId);
            return;
        }
        CoreDatasourceTask coreDatasourceTask = datasourceTaskServer.selectById(taskId);
        if (coreDatasourceTask == null) {
            return;
        }
        datasourceTaskServer.checkTaskIsStopped(coreDatasourceTask);
        if (StringUtils.isNotEmpty(coreDatasourceTask.getTaskStatus()) && (coreDatasourceTask.getTaskStatus().equalsIgnoreCase(TaskStatus.Stopped.name()) || coreDatasourceTask.getTaskStatus().equalsIgnoreCase(TaskStatus.Suspend.name()))) {
            LogUtil.info("Skip synchronization task: {} ,due to task status is {}", coreDatasourceTask.getId(), coreDatasourceTask.getTaskStatus());
            return;
        }

        if (datasourceTaskServer.existUnderExecutionTask(datasourceId, coreDatasourceTask.getId())) {
            LogUtil.info("Skip synchronization task for datasource due to exist others, datasource ID : " + datasourceId);
            return;
        }
        try {
            DatasourceServer.UpdateType updateType = DatasourceServer.UpdateType.valueOf(coreDatasourceTask.getUpdateType());
            if (context != null) {
                UpdateWrapper<CoreDatasource> updateWrapper = new UpdateWrapper<>();
                updateWrapper.eq("id", datasourceId);
                CoreDatasource record = new CoreDatasource();
                record.setQrtzInstance(context.getFireInstanceId());
                datasourceMapper.update(record, updateWrapper);
            }
            if (coreDatasource.getType().equalsIgnoreCase("ExcelRemote")) {
                extractedExcelData(taskId, coreDatasource, updateType, coreDatasourceTask.getSyncRate());
            } else {
                extractedData(taskId, coreDatasource, updateType, coreDatasourceTask.getSyncRate());
            }
        } catch (Exception e) {
            LogUtil.error(e);
        } finally {
            datasourceTaskServer.updateTaskStatus(coreDatasourceTask);
            updateDsTaskStatus(datasourceId);
        }
    }

    public void extractedData(Long taskId, CoreDatasource coreDatasource, DatasourceServer.UpdateType updateType, String scheduleType) {
        DatasourceRequest datasourceRequest = new DatasourceRequest();
        datasourceRequest.setDatasource(transDTO(coreDatasource));
        List<DatasetTableDTO> tables = (List<DatasetTableDTO>) datasourceServer.invokeMethod(coreDatasource.getType(), "getApiTables", DatasourceRequest.class, datasourceRequest);
        for (DatasetTableDTO api : tables) {
            CoreDatasourceTaskLog datasetTableTaskLog = datasourceTaskServer.initTaskLog(coreDatasource.getId(), taskId, api.getTableName(), scheduleType);
            datasourceRequest.setTable(api.getTableName());
            List<TableField> tableFields = (List<TableField>) datasourceServer.invokeMethod(coreDatasource.getType(), "getTableFields", DatasourceRequest.class, datasourceRequest);
            try {
                datasetTableTaskLog.setInfo(datasetTableTaskLog.getInfo() + "/n Begin to sync datatable: " + datasourceRequest.getTable());
                createEngineTable(datasourceRequest.getTable(), tableFields);
                if (updateType.equals(DatasourceServer.UpdateType.all_scope)) {
                    createEngineTable(TableUtils.tmpName(datasourceRequest.getTable()), tableFields);
                }
                extractApiData(datasourceRequest, updateType, tableFields);
                if (updateType.equals(DatasourceServer.UpdateType.all_scope)) {
                    replaceTable(datasourceRequest.getTable());
                }
                datasetTableTaskLog.setInfo(datasetTableTaskLog.getInfo() + "/n End to sync datatable: " + datasourceRequest.getTable());
                datasetTableTaskLog.setTaskStatus(TaskStatus.Completed.toString());
                datasetTableTaskLog.setEndTime(System.currentTimeMillis());
            } catch (Exception e) {
                try {
                    if (updateType.equals(DatasourceServer.UpdateType.all_scope)) {
                        dropEngineTable(TableUtils.tmpName(datasourceRequest.getTable()));
                    }
                } catch (Exception ignore) {
                }
                datasetTableTaskLog.setInfo(datasetTableTaskLog.getInfo() + "/n Failed to sync datatable: " + datasourceRequest.getTable() + ", " + e.getMessage());
                datasetTableTaskLog.setTaskStatus(TaskStatus.Error.toString());
                datasetTableTaskLog.setEndTime(System.currentTimeMillis());
            } finally {
                datasourceTaskServer.saveLog(datasetTableTaskLog);
            }
        }
    }

    public void extractedExcelData(Long taskId, CoreDatasource coreDatasource, DatasourceServer.UpdateType updateType, String scheduleType) {
        DatasourceRequest datasourceRequest = new DatasourceRequest();
        datasourceRequest.setDatasource(transDTO(coreDatasource));
        List<DatasetTableDTO> tables = ExcelUtils.getTables(datasourceRequest);
        for (DatasetTableDTO tableDTO : tables) {
            CoreDatasourceTaskLog datasetTableTaskLog = datasourceTaskServer.initTaskLog(coreDatasource.getId(), taskId, tableDTO.getTableName(), scheduleType);
            datasourceRequest.setTable(tableDTO.getTableName());
            ExcelUtils.getTableFields(datasourceRequest);
            List<TableField> tableFields = ExcelUtils.getTableFields(datasourceRequest);
            try {
                datasetTableTaskLog.setInfo(datasetTableTaskLog.getInfo() + "/n Begin to sync datatable: " + datasourceRequest.getTable());
                createEngineTable(datasourceRequest.getTable(), tableFields);
                if (updateType.equals(DatasourceServer.UpdateType.all_scope)) {
                    createEngineTable(TableUtils.tmpName(datasourceRequest.getTable()), tableFields);
                }
                extractExcelData(datasourceRequest, updateType, tableFields);
                if (updateType.equals(DatasourceServer.UpdateType.all_scope)) {
                    replaceTable(datasourceRequest.getTable());
                }
                datasetTableTaskLog.setInfo(datasetTableTaskLog.getInfo() + "/n End to sync datatable: " + datasourceRequest.getTable());
                datasetTableTaskLog.setTaskStatus(TaskStatus.Completed.toString());
                datasetTableTaskLog.setEndTime(System.currentTimeMillis());
            } catch (Exception e) {
                try {
                    if (updateType.equals(DatasourceServer.UpdateType.all_scope)) {
                        dropEngineTable(TableUtils.tmpName(datasourceRequest.getTable()));
                    }
                } catch (Exception ignore) {
                }
                datasetTableTaskLog.setInfo(datasetTableTaskLog.getInfo() + "/n Failed to sync datatable: " + datasourceRequest.getTable() + ", " + e.getMessage());
                datasetTableTaskLog.setTaskStatus(TaskStatus.Error.toString());
                datasetTableTaskLog.setEndTime(System.currentTimeMillis());

                e.printStackTrace();
            } finally {
                datasourceTaskServer.saveLog(datasetTableTaskLog);
            }
        }
    }

    private void updateDsTaskStatus(Long datasourceId) {
        UpdateWrapper<CoreDatasource> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("id", datasourceId);
        CoreDatasource record = new CoreDatasource();
        record.setTaskStatus(TaskStatus.WaitingForExecution.name());
        datasourceMapper.update(record, updateWrapper);
    }

    public void extractDataForTable(Long datasourceId, String name, String tableName, String type) {
        DatasourceServer.UpdateType updateType = DatasourceServer.UpdateType.valueOf(type);
        CoreDatasource coreDatasource = datasourceMapper.selectById(datasourceId);
        if (coreDatasource == null) {
            LogUtil.error("Can not find datasource: " + datasourceId);
            return;
        }
        CoreDatasourceTaskLog datasetTableTaskLog = datasourceTaskServer.initTaskLog(datasourceId, null, tableName, MANUAL.toString());

        DatasourceRequest datasourceRequest = new DatasourceRequest();
        datasourceRequest.setDatasource(transDTO(coreDatasource));
        List<DatasetTableDTO> tables = (List<DatasetTableDTO>) datasourceServer.invokeMethod(coreDatasource.getType(), "getApiTables", DatasourceRequest.class, datasourceRequest);
        for (DatasetTableDTO api : tables) {
            if (api.getTableName().equalsIgnoreCase(tableName)) {
                datasourceRequest.setTable(api.getTableName());
                List<TableField> tableFields = (List<TableField>) datasourceServer.invokeMethod(coreDatasource.getType(), "getTableFields", DatasourceRequest.class, datasourceRequest);
                try {
                    datasetTableTaskLog.setInfo(datasetTableTaskLog.getInfo() + "/n Begin to sync datatable: " + datasourceRequest.getTable());
                    createEngineTable(datasourceRequest.getTable(), tableFields);
                    if (updateType.equals(DatasourceServer.UpdateType.all_scope)) {
                        createEngineTable(TableUtils.tmpName(datasourceRequest.getTable()), tableFields);
                    }
                    extractApiData(datasourceRequest, updateType, tableFields);
                    if (updateType.equals(DatasourceServer.UpdateType.all_scope)) {
                        replaceTable(datasourceRequest.getTable());
                    }
                    datasetTableTaskLog.setInfo(datasetTableTaskLog.getInfo() + "/n End to sync datatable: " + datasourceRequest.getTable());
                    datasetTableTaskLog.setTaskStatus(TaskStatus.Completed.name());
                    datasetTableTaskLog.setEndTime(System.currentTimeMillis());
                } catch (Exception e) {
                    try {
                        if (updateType.equals(DatasourceServer.UpdateType.all_scope)) {
                            dropEngineTable(TableUtils.tmpName(datasourceRequest.getTable()));
                        }
                    } catch (Exception ignore) {
                    }
                    datasetTableTaskLog.setInfo(datasetTableTaskLog.getInfo() + "/n Failed to sync datatable: " + datasourceRequest.getTable() + ", " + e.getMessage());
                    datasetTableTaskLog.setTaskStatus(TaskStatus.Error.name());
                    datasetTableTaskLog.setEndTime(System.currentTimeMillis());
                } finally {
                    datasourceTaskServer.saveLog(datasetTableTaskLog);
                }
            }
        }
    }

    private void extractApiData(DatasourceRequest datasourceRequest, DatasourceServer.UpdateType extractType, List<TableField> tableFields) throws Exception {
        Map<String, Object> result = (Map<String, Object>) datasourceServer.invokeMethod(datasourceRequest.getDatasource().getType(), "fetchApiResultField", DatasourceRequest.class, datasourceRequest);
        List<String[]> dataList = (List<String[]>) result.get("dataList");
        CoreDeEngine engine = engineManage.info();
        EngineRequest engineRequest = new EngineRequest();
        engineRequest.setEngine(engine);
        EngineProvider engineProvider = ProviderUtil.getEngineProvider(engine.getType());
        int pageNumber = 1000; //一次插入 1000条
        int totalPage;
        if (dataList.size() % pageNumber > 0) {
            totalPage = dataList.size() / pageNumber + 1;
        } else {
            totalPage = dataList.size() / pageNumber;
        }
        for (int page = 1; page <= totalPage; page++) {
            engineRequest.setQuery(engineProvider.insertSql(DatasourceConfiguration.DatasourceType.API.name(), datasourceRequest.getTable(), extractType, dataList, page, pageNumber, tableFields));
            calciteProvider.exec(engineRequest);
        }
    }

    private void extractExcelData(DatasourceRequest datasourceRequest, DatasourceServer.UpdateType extractType, List<TableField> tableFields) throws Exception {
        ExcelUtils excelUtils = new ExcelUtils();
        List<String[]> dataList = excelUtils.fetchDataList(datasourceRequest);
        CoreDeEngine engine = engineManage.info();
        EngineRequest engineRequest = new EngineRequest();
        engineRequest.setEngine(engine);
        EngineProvider engineProvider = ProviderUtil.getEngineProvider(engine.getType());
        int pageNumber = 1000; //一次插入 1000条
        int totalPage;
        if (dataList.size() % pageNumber > 0) {
            totalPage = dataList.size() / pageNumber + 1;
        } else {
            totalPage = dataList.size() / pageNumber;
        }
        for (int page = 1; page <= totalPage; page++) {
            engineRequest.setQuery(engineProvider.insertSql(DatasourceConfiguration.DatasourceType.Excel.name(), datasourceRequest.getTable(), extractType, dataList, page, pageNumber, tableFields));
            calciteProvider.exec(engineRequest);
        }
    }

    private void replaceTable(String tableName) throws Exception {
        CoreDeEngine engine = engineManage.info();
        EngineRequest engineRequest = new EngineRequest();
        engineRequest.setEngine(engine);
        EngineProvider engineProvider = ProviderUtil.getEngineProvider(engine.getType());
        String[] replaceTableSql = engineProvider.replaceTable(tableName).split(";");
        for (int i = 0; i < replaceTableSql.length; i++) {
            if (StringUtils.isNotEmpty(replaceTableSql[i])) {
                engineRequest.setQuery(replaceTableSql[i]);
                calciteProvider.exec(engineRequest);
            }
        }
    }

    public void createEngineTable(String tableName, List<TableField> tableFields) throws Exception {
        CoreDeEngine engine = engineManage.info();
        EngineRequest engineRequest = new EngineRequest();
        engineRequest.setEngine(engine);
        EngineProvider engineProvider = ProviderUtil.getEngineProvider(engine.getType());
        engineRequest.setQuery(engineProvider.createTableSql(tableName, tableFields, engine));
        calciteProvider.exec(engineRequest);
    }

    public void dropEngineTable(String tableName) throws Exception {
        CoreDeEngine engine = engineManage.info();
        EngineRequest engineRequest = new EngineRequest();
        engineRequest.setEngine(engine);
        EngineProvider engineProvider = ProviderUtil.getEngineProvider(engine.getType());
        engineRequest.setQuery(engineProvider.dropTable(tableName));
        calciteProvider.exec(engineRequest);
    }

    public void addSchedule(CoreDatasourceTask datasourceTask) throws DEException {
        if (StringUtils.equalsIgnoreCase(datasourceTask.getSyncRate(), DatasourceTaskServer.ScheduleType.RIGHTNOW.toString())) {
            scheduleManager.addOrUpdateSingleJob(new JobKey(datasourceTask.getId().toString(), datasourceTask.getDsId().toString()), new TriggerKey(datasourceTask.getId().toString(), datasourceTask.getDsId().toString()), ExtractDataJob.class, new Date(datasourceTask.getStartTime()), scheduleManager.getDefaultJobDataMap(datasourceTask.getDsId().toString(), datasourceTask.getCron(), datasourceTask.getId().toString(), datasourceTask.getUpdateType()));
        } else {
            Date endTime;
            if (datasourceTask.getEndTime() == null || datasourceTask.getEndTime() == 0) {
                endTime = null;
            } else {
                endTime = new Date(datasourceTask.getEndTime());
                if (endTime.before(new Date())) {
                    deleteSchedule(datasourceTask);
                    return;
                }
            }

            scheduleManager.addOrUpdateCronJob(new JobKey(datasourceTask.getId().toString(), datasourceTask.getDsId().toString()), new TriggerKey(datasourceTask.getId().toString(), datasourceTask.getDsId().toString()), ExtractDataJob.class, datasourceTask.getCron(), new Date(datasourceTask.getStartTime()), endTime, scheduleManager.getDefaultJobDataMap(datasourceTask.getDsId().toString(), datasourceTask.getCron(), datasourceTask.getId().toString(), datasourceTask.getUpdateType()));
        }
    }

    public void deleteSchedule(CoreDatasourceTask datasourceTask) {
        scheduleManager.removeJob(new JobKey(datasourceTask.getId().toString(), datasourceTask.getDsId().toString()), new TriggerKey(datasourceTask.getId().toString(), datasourceTask.getDsId().toString()));
    }

    public void fireNow(CoreDatasourceTask datasourceTask) throws Exception {
        scheduleManager.fireNow(datasourceTask.getId().toString(), datasourceTask.getDsId().toString());
    }

    private DatasourceDTO transDTO(CoreDatasource record) {
        DatasourceDTO datasourceDTO = new DatasourceDTO();
        BeanUtils.copyBean(datasourceDTO, record);
        return datasourceDTO;
    }
}
