package tech.easyflow.datacenter.meta.service.impl;

import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.datacenter.connector.DatacenterConnector;
import tech.easyflow.datacenter.connector.DatacenterConnectorRegistry;
import tech.easyflow.datacenter.entity.DatacenterTable;
import tech.easyflow.datacenter.mapper.DatacenterCatalogMapper;
import tech.easyflow.datacenter.mapper.DatacenterSourceMapper;
import tech.easyflow.datacenter.meta.entity.DatacenterCatalog;
import tech.easyflow.datacenter.meta.entity.DatacenterSource;
import tech.easyflow.datacenter.meta.enums.DatacenterSourceType;
import tech.easyflow.datacenter.meta.model.DatacenterBatchRegisterRequest;
import tech.easyflow.datacenter.meta.model.DatacenterCatalogMeta;
import tech.easyflow.datacenter.meta.model.DatacenterTableDetailMeta;
import tech.easyflow.datacenter.meta.service.DatacenterDatasetRegistryService;
import tech.easyflow.datacenter.meta.service.DatacenterSourceService;
import tech.easyflow.datacenter.meta.support.DatacenterSourceConnectionDefaults;
import tech.easyflow.datacenter.execution.model.DatacenterConnectionTestResult;
import tech.easyflow.datacenter.security.DatacenterCredentialCipher;

import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class DatacenterSourceServiceImpl extends ServiceImpl<DatacenterSourceMapper, DatacenterSource> implements DatacenterSourceService {

    @Resource
    private DatacenterConnectorRegistry connectorRegistry;
    @Resource
    private DatacenterDatasetRegistryService registryService;
    @Resource
    private DatacenterCredentialCipher credentialCipher;
    @Resource
    private DatacenterCatalogMapper catalogMapper;
    @Resource
    private DatacenterSourceConnectionDefaults connectionDefaults;

    @Override
    public DatacenterSource saveSource(DatacenterSource source, LoginAccount account) {
        if (source == null || source.getSourceType() == null || source.getSourceType().isBlank()) {
            throw new BusinessException("数据源类型不能为空");
        }
        DatacenterSource existing = source.getId() == null ? null : getById(source.getId());
        DatacenterSource normalized = mergeWithExisting(existing, source);
        DatacenterConnector connector = connectorRegistry.getConnector(normalized.getSourceType());
        applyCredentialCipher(normalized, existing);
        normalized.setConfigJson(connectionDefaults.sanitizeConfig(normalized.getConfigJson()));
        connectionDefaults.normalize(normalized);
        normalized.setCapabilitiesJson(Map.of("capabilities", connector.getCapabilities().stream().map(Enum::name).toList()));
        Date now = new Date();
        if (normalized.getId() == null) {
            normalized.setCreated(now);
            normalized.setCreatedBy(account == null ? BigInteger.ZERO : account.getId());
            normalized.setTenantId(account == null ? BigInteger.ZERO : account.getTenantId());
            normalized.setDeptId(account == null ? BigInteger.ZERO : account.getDeptId());
            normalized.setStatus(normalized.getStatus() == null ? 0 : normalized.getStatus());
            normalized.setModified(now);
            normalized.setModifiedBy(account == null ? BigInteger.ZERO : account.getId());
            save(normalized);
        } else {
            if (existing == null) {
                throw new BusinessException("连接不存在");
            }
            normalized.setModified(now);
            normalized.setModifiedBy(account == null ? BigInteger.ZERO : account.getId());
            updateById(normalized);
        }
        return getById(normalized.getId());
    }

    @Override
    public Page<DatacenterSource> pageSources(Long pageNumber, Long pageSize, LoginAccount account) {
        registryService.ensureBuiltinSource(DatacenterSourceType.PROJECT_MYSQL, account);
        return page(new Page<>(pageNumber == null ? 1L : pageNumber, pageSize == null ? 10L : pageSize), QueryWrapper.create());
    }

    @Override
    public DatacenterConnectionTestResult testConnection(DatacenterSource source, LoginAccount account) {
        DatacenterSource existing = source != null && source.getId() != null ? getById(source.getId()) : null;
        if (source != null && source.getId() != null && existing == null) {
            throw new BusinessException("连接不存在");
        }
        DatacenterSource actual = mergeWithExisting(existing, source);
        if (DatacenterSourceType.PROJECT_MYSQL.name().equals(actual.getSourceType())) {
            actual = registryService.ensureBuiltinSource(DatacenterSourceType.PROJECT_MYSQL, account);
        }
        applyCredentialCipher(actual, existing);
        actual.setConfigJson(connectionDefaults.sanitizeConfig(actual.getConfigJson()));
        connectionDefaults.normalize(actual);
        DatacenterConnector connector = connectorRegistry.getConnector(actual.getSourceType());
        DatacenterConnectionTestResult result = connector.testConnection(actual);
        Map<String, Object> details = new LinkedHashMap<>();
        if (result.getDetails() != null) {
            details.putAll(result.getDetails());
        }
        details.put("effectiveDriverClassName", actual.getDriverClassName());
        details.put("effectiveJdbcUrl", actual.getJdbcUrl());
        details.put("effectivePort", actual.getPort());
        result.setDetails(details);
        if (actual.getId() != null) {
            DatacenterSource persisted = new DatacenterSource();
            persisted.setId(actual.getId());
            persisted.setLastTestStatus(result.isSuccess() ? "SUCCESS" : "FAILED");
            persisted.setLastTestMessage(result.getMessage());
            persisted.setLastTestedAt(new Date());
            updateById(persisted);
        }
        return result;
    }

    @Override
    public List<DatacenterCatalogMeta> listCatalogs(BigInteger sourceId, LoginAccount account) {
        DatacenterSource source = registryService.getSourceRequired(sourceId);
        if (isManagedOnly(source.getSourceType())) {
            QueryWrapper wrapper = QueryWrapper.create();
            wrapper.eq(DatacenterCatalog::getSourceId, sourceId);
            return catalogMapper.selectListByQuery(wrapper).stream().map(this::toCatalogMeta).collect(Collectors.toList());
        }
        return connectorRegistry.getConnector(source.getSourceType()).listCatalogs(source);
    }

    @Override
    public List<DatacenterTable> listTables(BigInteger sourceId, String catalogName, LoginAccount account) {
        DatacenterSource source = registryService.getSourceRequired(sourceId);
        if (isManagedOnly(source.getSourceType())) {
            BigInteger catalogId = resolveCatalogId(sourceId, catalogName);
            return registryService.listManagedTables(sourceId, catalogId);
        }
        return connectorRegistry.getConnector(source.getSourceType()).listTables(source, catalogName);
    }

    @Override
    public DatacenterTableDetailMeta getTableDetail(BigInteger sourceId, String catalogName, String tableName, boolean register, LoginAccount account) {
        DatacenterSource source = registryService.getSourceRequired(sourceId);
        if (isManagedOnly(source.getSourceType())) {
            BigInteger catalogId = resolveCatalogId(sourceId, catalogName);
            List<DatacenterTable> tables = registryService.listManagedTables(sourceId, catalogId);
            DatacenterTable target = tables.stream().filter(item -> tableName.equals(item.getTableName())).findFirst()
                    .orElseThrow(() -> new BusinessException("数据集不存在: " + tableName));
            DatacenterTableDetailMeta detail = new DatacenterTableDetailMeta();
            detail.setTable(registryService.getTableWithFields(target.getId()));
            detail.setFields(detail.getTable().getFields());
            return detail;
        }
        DatacenterConnector connector = connectorRegistry.getConnector(source.getSourceType());
        DatacenterTableDetailMeta detail = connector.getTableDetail(source, catalogName, tableName);
        if (register) {
            DatacenterCatalog catalog = registryService.ensureCatalog(source, catalogName, account);
            DatacenterTable table = registryService.registerTable(source, catalog, detail, account);
            detail.setTable(table);
            detail.setFields(table.getFields());
        }
        return detail;
    }

    @Override
    public List<DatacenterTable> batchRegisterTables(DatacenterBatchRegisterRequest request, LoginAccount account) {
        if (request == null || request.getSourceId() == null) {
            throw new BusinessException("数据连接不能为空");
        }
        if (request.getCatalogName() == null || request.getCatalogName().isBlank()) {
            throw new BusinessException("库不能为空");
        }
        List<String> tableNames = request.getTableNames() == null
                ? List.of()
                : request.getTableNames().stream().filter(name -> name != null && !name.isBlank()).distinct().toList();
        if (tableNames.isEmpty()) {
            throw new BusinessException("至少选择一张表");
        }
        DatacenterSource source = registryService.getSourceRequired(request.getSourceId());
        if (isManagedOnly(source.getSourceType())) {
            throw new BusinessException("当前数据连接不支持批量接入");
        }
        DatacenterConnector connector = connectorRegistry.getConnector(source.getSourceType());
        DatacenterCatalog catalog = registryService.ensureCatalog(source, request.getCatalogName(), account);
        return tableNames.stream().map(tableName -> {
            DatacenterTableDetailMeta detail = connector.getTableDetail(source, request.getCatalogName(), tableName);
            return registryService.registerTable(source, catalog, detail, account);
        }).collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void removeSource(BigInteger sourceId, LoginAccount account) {
        if (sourceId == null) {
            throw new BusinessException("数据连接不能为空");
        }
        DatacenterSource source = getById(sourceId);
        if (source == null) {
            throw new BusinessException("连接不存在");
        }
        if (Boolean.TRUE.equals(source.getBuiltinFlag())) {
            throw new BusinessException("内置数据连接不支持删除");
        }

        List<BigInteger> tableIds = registryService.listManagedTables(sourceId, null).stream()
                .map(DatacenterTable::getId)
                .filter(id -> id != null)
                .toList();
        registryService.removeTables(tableIds);

        QueryWrapper catalogWrapper = QueryWrapper.create();
        catalogWrapper.eq(DatacenterCatalog::getSourceId, sourceId);
        catalogMapper.deleteByQuery(catalogWrapper);

        removeById(sourceId);
    }

    private boolean isManagedOnly(String sourceType) {
        return DatacenterSourceType.EXCEL.name().equals(sourceType)
                || DatacenterSourceType.EXCEL_MATERIALIZED.name().equals(sourceType);
    }

    private DatacenterCatalogMeta toCatalogMeta(DatacenterCatalog catalog) {
        DatacenterCatalogMeta meta = new DatacenterCatalogMeta();
        meta.setId(catalog.getId());
        meta.setSourceId(catalog.getSourceId());
        meta.setCatalogName(catalog.getCatalogName());
        meta.setCatalogDesc(catalog.getCatalogDesc());
        meta.setCatalogType(catalog.getCatalogType());
        return meta;
    }

    private BigInteger resolveCatalogId(BigInteger sourceId, String catalogName) {
        if (catalogName == null || catalogName.isBlank()) {
            return null;
        }
        QueryWrapper wrapper = QueryWrapper.create();
        wrapper.eq(DatacenterCatalog::getSourceId, sourceId);
        wrapper.eq(DatacenterCatalog::getCatalogName, catalogName);
        DatacenterCatalog catalog = catalogMapper.selectOneByQuery(wrapper);
        if (catalog == null) {
            throw new BusinessException("目录不存在: " + catalogName);
        }
        return catalog.getId();
    }

    private void applyCredentialCipher(DatacenterSource target, DatacenterSource existing) {
        String password = extractPassword(target.getConfigJson());
        if (password != null) {
            target.setCredentialCipher(credentialCipher.encrypt(password));
            return;
        }
        if ((target.getCredentialCipher() == null || target.getCredentialCipher().isBlank()) && existing != null) {
            target.setCredentialCipher(existing.getCredentialCipher());
        }
    }

    private String extractPassword(Map<String, Object> configJson) {
        if (configJson == null) {
            return null;
        }
        Object password = configJson.get("password");
        if (password == null) {
            return null;
        }
        String value = String.valueOf(password);
        return value.isBlank() ? null : value;
    }

    private DatacenterSource mergeWithExisting(DatacenterSource existing, DatacenterSource incoming) {
        if (incoming == null) {
            return existing;
        }
        if (existing == null) {
            return incoming;
        }
        DatacenterSource merged = new DatacenterSource();
        merged.setId(existing.getId());
        merged.setCreated(existing.getCreated());
        merged.setCreatedBy(existing.getCreatedBy());
        merged.setTenantId(existing.getTenantId());
        merged.setDeptId(existing.getDeptId());
        merged.setBuiltinFlag(existing.getBuiltinFlag());
        merged.setStatus(existing.getStatus());
        merged.setLastTestStatus(existing.getLastTestStatus());
        merged.setLastTestMessage(existing.getLastTestMessage());
        merged.setLastTestedAt(existing.getLastTestedAt());
        merged.setOptions(existing.getOptions());
        merged.setSourceName(valueOrExisting(incoming.getSourceName(), existing.getSourceName()));
        merged.setSourceCode(valueOrExisting(incoming.getSourceCode(), existing.getSourceCode()));
        merged.setSourceType(valueOrExisting(incoming.getSourceType(), existing.getSourceType()));
        merged.setAccessMode(valueOrExisting(incoming.getAccessMode(), existing.getAccessMode()));
        merged.setDriverClassName(valueOrExisting(incoming.getDriverClassName(), existing.getDriverClassName()));
        merged.setJdbcUrl(valueOrExisting(incoming.getJdbcUrl(), existing.getJdbcUrl()));
        merged.setHost(valueOrExisting(incoming.getHost(), existing.getHost()));
        merged.setPort(incoming.getPort() != null ? incoming.getPort() : existing.getPort());
        merged.setDatabaseName(valueOrExisting(incoming.getDatabaseName(), existing.getDatabaseName()));
        merged.setSchemaName(valueOrExisting(incoming.getSchemaName(), existing.getSchemaName()));
        merged.setUsername(valueOrExisting(incoming.getUsername(), existing.getUsername()));
        merged.setCredentialCipher(valueOrExisting(incoming.getCredentialCipher(), existing.getCredentialCipher()));
        merged.setConfigJson(incoming.getConfigJson() != null ? incoming.getConfigJson() : existing.getConfigJson());
        merged.setCapabilitiesJson(existing.getCapabilitiesJson());
        return merged;
    }

    private String valueOrExisting(String incoming, String existing) {
        return incoming != null ? incoming : existing;
    }
}
