package com.purui.prims.headless.server.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.purui.prims.auth.api.authentication.pojo.User;
import com.purui.prims.common.pojo.QueryColumn;
import com.purui.prims.headless.api.pojo.request.DatabaseReq;
import com.purui.prims.headless.api.pojo.request.SqlExecuteReq;
import com.purui.prims.headless.api.pojo.response.DatabaseResp;
import com.purui.prims.headless.api.pojo.response.ModelResp;
import com.purui.prims.headless.api.pojo.response.SemanticQueryResp;
import com.purui.prims.headless.core.adaptor.db.DbAdaptor;
import com.purui.prims.headless.core.adaptor.db.DbAdaptorFactory;
import com.purui.prims.headless.core.pojo.DBColumn;
import com.purui.prims.headless.core.pojo.Database;
import com.purui.prims.headless.core.utils.JdbcDataSourceUtils;
import com.purui.prims.headless.core.utils.SqlUtils;
import com.purui.prims.headless.core.utils.SqlVariableParseUtils;
import com.purui.prims.headless.server.persistence.dataobject.DatabaseDO;
import com.purui.prims.headless.server.persistence.mapper.DatabaseDOMapper;
import com.purui.prims.headless.server.pojo.DatabaseParameter;
import com.purui.prims.headless.server.pojo.DbParameterFactory;
import com.purui.prims.headless.server.pojo.ModelFilter;
import com.purui.prims.headless.server.service.DatabaseService;
import com.purui.prims.headless.server.service.ModelService;
import com.purui.prims.headless.server.utils.DatabaseConverter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.sql.SQLException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


@Slf4j
@Service
public class DatabaseServiceImpl extends ServiceImpl<DatabaseDOMapper, DatabaseDO>
    implements DatabaseService {

    @Autowired
    private SqlUtils sqlUtils;

    @Lazy
    @Autowired
    private ModelService datasourceService;

    @Override
    public boolean testConnect(DatabaseReq databaseReq, User user) {
        Database database = DatabaseConverter.convert(databaseReq);
        return JdbcDataSourceUtils.testDatabase(database);
    }

    @Override
    public DatabaseResp createOrUpdateDatabase(DatabaseReq databaseReq, User user) {
        DatabaseDO databaseDO = getDatabaseDO(databaseReq.getId());
        if (databaseDO != null) {
            databaseReq.updatedBy(user.getUserName());
            DatabaseConverter.convert(databaseReq, databaseDO);
            updateById(databaseDO);
            return DatabaseConverter.convertWithPassword(databaseDO);
        }
        databaseReq.createdBy(user.getUserName());
        databaseDO = DatabaseConverter.convertDO(databaseReq);
        save(databaseDO);
        return DatabaseConverter.convertWithPassword(databaseDO);
    }

    @Override
    public List<DatabaseResp> getDatabaseList(User user) {
        List<DatabaseResp> databaseResps = list().stream().map(DatabaseConverter::convert)
                                                 .collect(Collectors.toList());
        fillPermission(databaseResps, user);
        return databaseResps;
    }

    private void fillPermission(List<DatabaseResp> databaseResps, User user) {
        databaseResps.forEach(databaseResp -> {
            if (user.getUserName().equalsIgnoreCase(databaseResp.getCreatedBy())
                || user.isSuperAdmin()) {
                databaseResp.setHasEditPermission(true);
                databaseResp.setHasUsePermission(true);
            }
        });
    }

    @Override
    public void deleteDatabase(Long databaseId) {
        ModelFilter modelFilter = new ModelFilter();
        modelFilter.setDatabaseId(databaseId);
        modelFilter.setIncludesDetail(false);
        List<ModelResp> modelResps = datasourceService.getModelList(modelFilter);
        if (!CollectionUtils.isEmpty(modelResps)) {
            List<String> datasourceNames = modelResps.stream()
                                                     .map(ModelResp::getName).collect(Collectors.toList());
            String message = String.format("该数据库被模型%s使用，无法删除", datasourceNames);
            throw new RuntimeException(message);
        }
        removeById(databaseId);
    }

    @Override
    public DatabaseResp getDatabase(Long id) {
        DatabaseDO databaseDO = getById(id);
        return DatabaseConverter.convertWithPassword(databaseDO);
    }

    @Override
    public DatabaseResp getDatabase(Long id, User user) {
        DatabaseResp databaseResp = getDatabase(id);
        checkPermission(databaseResp, user);
        return databaseResp;
    }

    @Override
    public SemanticQueryResp executeSql(SqlExecuteReq sqlExecuteReq, Long id, User user) {
        DatabaseResp databaseResp = getDatabase(id);
        if (databaseResp == null) {
            return new SemanticQueryResp();
        }
        checkPermission(databaseResp, user);
        String sql = sqlExecuteReq.getSql();
        sql = SqlVariableParseUtils.parse(sql, sqlExecuteReq.getSqlVariables(), Lists.newArrayList());
        return executeSql(sql, databaseResp);
    }

    @Override
    public SemanticQueryResp executeSql(String sql, DatabaseResp databaseResp) {
        return queryWithColumns(sql, DatabaseConverter.convert(databaseResp));
    }

    @Override
    public Map<String, List<DatabaseParameter>> getDatabaseParameters() {
        return DbParameterFactory.getMap().entrySet().stream().collect(LinkedHashMap::new,
            (map, entry) -> map.put(entry.getKey(), entry.getValue().build()),
            LinkedHashMap::putAll);
    }

    private SemanticQueryResp queryWithColumns(String sql, Database database) {
        SemanticQueryResp queryResultWithColumns = new SemanticQueryResp();
        SqlUtils sqlUtils = this.sqlUtils.init(database);
        log.info("query SQL: {}", sql);
        sqlUtils.queryInternal(sql, queryResultWithColumns);
        return queryResultWithColumns;
    }

    private DatabaseDO getDatabaseDO(Long id) {
        return getById(id);
    }

    @Override
    public List<String> getDbNames(Long id) throws SQLException {
        DatabaseResp databaseResp = getDatabase(id);
        DbAdaptor dbAdaptor = DbAdaptorFactory.getEngineAdaptor(databaseResp.getType());
        return dbAdaptor.getDBs(DatabaseConverter.getConnectInfo(databaseResp));
    }

    @Override
    public List<String> getTables(Long id, String db) throws SQLException {
        DatabaseResp databaseResp = getDatabase(id);
        DbAdaptor dbAdaptor = DbAdaptorFactory.getEngineAdaptor(databaseResp.getType());
        return dbAdaptor.getTables(DatabaseConverter.getConnectInfo(databaseResp), db);
    }

    @Override
    public List<DBColumn> getColumns(Long id, String db, String table) throws SQLException {
        DatabaseResp databaseResp = getDatabase(id);
        return getColumns(databaseResp, db, table);
    }

    public List<DBColumn> getColumns(DatabaseResp databaseResp, String db, String table) throws SQLException {
        DbAdaptor engineAdaptor = DbAdaptorFactory.getEngineAdaptor(databaseResp.getType());
        return engineAdaptor.getColumns(DatabaseConverter.getConnectInfo(databaseResp), db, table);
    }

    @Override
    public List<DBColumn> getColumns(Long id, String sql) throws SQLException {
        String wrapSql = String.format("select * from (%s) a limit 1", sql);
        DatabaseResp databaseResp = getDatabase(id);
        SemanticQueryResp semanticQueryResp = executeSql(wrapSql, databaseResp);
        List<DBColumn> dbColumns = Lists.newArrayList();
        for (QueryColumn queryColumn : semanticQueryResp.getColumns()) {
            DBColumn dbColumn = new DBColumn();
            dbColumn.setColumnName(queryColumn.getNameEn());
            dbColumn.setDataType(queryColumn.getType());
            dbColumns.add(dbColumn);
        }
        return dbColumns;
    }

    @Override
    public void runOnly(String sql, DatabaseResp databaseResp) {
        Database database = DatabaseConverter.convert(databaseResp);
        SqlUtils sqlUtils = this.sqlUtils.init(database);
        log.info("query SQL: {}", sql);
        sqlUtils.runOnly(sql);
    }

    private void checkPermission(DatabaseResp databaseResp, User user) {
        // 权限控制
    }

}
