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

import com.mybatisflex.core.query.QueryWrapper;
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.adapter.DbHandleManager;
import tech.easyflow.datacenter.entity.DatacenterTable;
import tech.easyflow.datacenter.entity.DatacenterTableField;
import tech.easyflow.datacenter.execution.model.DatasetRef;
import tech.easyflow.datacenter.mapper.DatacenterCatalogMapper;
import tech.easyflow.datacenter.mapper.DatacenterDatasetVersionMapper;
import tech.easyflow.datacenter.mapper.DatacenterDerivedTableMapper;
import tech.easyflow.datacenter.mapper.DatacenterImportJobMapper;
import tech.easyflow.datacenter.mapper.DatacenterSourceMapper;
import tech.easyflow.datacenter.mapper.DatacenterTableFieldMapper;
import tech.easyflow.datacenter.mapper.DatacenterTableMapper;
import tech.easyflow.datacenter.meta.entity.DatacenterCatalog;
import tech.easyflow.datacenter.meta.entity.DatacenterSource;
import tech.easyflow.datacenter.meta.model.DatacenterFieldDescriptionUpdate;
import tech.easyflow.datacenter.meta.enums.DatacenterSourceType;
import tech.easyflow.datacenter.meta.service.DatacenterDatasetRegistryService;
import tech.easyflow.datacenter.meta.service.DatacenterMetaConstants;
import tech.easyflow.datacenter.meta.model.DatacenterTableDetailMeta;
import tech.easyflow.datacenter.meta.enums.DatacenterTableKind;

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

@Service
public class DatacenterDatasetRegistryServiceImpl implements DatacenterDatasetRegistryService {

    @Resource
    private DatacenterSourceMapper sourceMapper;
    @Resource
    private DatacenterCatalogMapper catalogMapper;
    @Resource
    private DatacenterTableMapper tableMapper;
    @Resource
    private DatacenterTableFieldMapper tableFieldMapper;
    @Resource
    private DatacenterDatasetVersionMapper datasetVersionMapper;
    @Resource
    private DatacenterDerivedTableMapper derivedTableMapper;
    @Resource
    private DatacenterImportJobMapper importJobMapper;
    @Resource
    private DbHandleManager dbHandleManager;

    @Override
    public DatacenterSource ensureBuiltinSource(DatacenterSourceType sourceType, LoginAccount account) {
        BigInteger tenantId = account == null || account.getTenantId() == null ? BigInteger.ZERO : account.getTenantId();
        BigInteger deptId = account == null || account.getDeptId() == null ? BigInteger.ZERO : account.getDeptId();
        BigInteger id = builtinSourceId(sourceType, tenantId);
        DatacenterSource source = sourceMapper.selectOneById(id);
        if (source != null) {
            return source;
        }
        source = new DatacenterSource();
        source.setId(id);
        source.setTenantId(tenantId);
        source.setDeptId(deptId);
        source.setSourceType(sourceType.name());
        source.setBuiltinFlag(1);
        source.setStatus(0);
        source.setCreated(new Date());
        source.setModified(new Date());
        source.setCreatedBy(account == null ? BigInteger.ZERO : account.getId());
        source.setModifiedBy(account == null ? BigInteger.ZERO : account.getId());
        source.setCapabilitiesJson(Map.of("capabilities", builtinCapabilities(sourceType)));
        if (sourceType == DatacenterSourceType.PROJECT_MYSQL) {
            source.setSourceName("项目 MySQL");
            source.setSourceCode("PROJECT_MYSQL_" + tenantId);
            source.setAccessMode("READ_WRITE");
        } else {
            throw new BusinessException("不支持的内置数据源类型: " + sourceType);
        }
        sourceMapper.insert(source);
        ensureCatalog(source, defaultCatalogName(sourceType), account);
        return source;
    }

    @Override
    public DatacenterCatalog ensureCatalog(DatacenterSource source, String catalogName, LoginAccount account) {
        QueryWrapper wrapper = QueryWrapper.create();
        wrapper.eq(DatacenterCatalog::getSourceId, source.getId());
        wrapper.eq(DatacenterCatalog::getCatalogName, catalogName);
        DatacenterCatalog catalog = catalogMapper.selectOneByQuery(wrapper);
        if (catalog != null) {
            return catalog;
        }
        catalog = new DatacenterCatalog();
        BigInteger catalogId = builtinCatalogId(source, catalogName);
        if (catalogId != null) {
            catalog.setId(catalogId);
        }
        catalog.setSourceId(source.getId());
        catalog.setTenantId(source.getTenantId());
        catalog.setDeptId(source.getDeptId());
        catalog.setCatalogName(catalogName);
        catalog.setCatalogDesc(catalogName);
        catalog.setCatalogType("DATABASE");
        catalog.setStatus(0);
        catalog.setCreated(new Date());
        catalog.setModified(new Date());
        catalog.setCreatedBy(account == null ? BigInteger.ZERO : account.getId());
        catalog.setModifiedBy(account == null ? BigInteger.ZERO : account.getId());
        catalogMapper.insert(catalog);
        return catalog;
    }

    @Override
    public DatacenterTable registerTable(DatacenterSource source, DatacenterCatalog catalog, DatacenterTableDetailMeta detail, LoginAccount account) {
        DatacenterTable table = detail.getTable();
        applyTableDefaults(table, source, detail);
        QueryWrapper wrapper = QueryWrapper.create();
        wrapper.eq(DatacenterTable::getSourceId, source.getId());
        wrapper.eq(DatacenterTable::getCatalogId, catalog.getId());
        wrapper.eq(DatacenterTable::getTableName, table.getTableName());
        DatacenterTable existing = tableMapper.selectOneByQuery(wrapper);
        Date now = new Date();
        if (existing == null) {
            table.setSourceId(source.getId());
            table.setCatalogId(catalog.getId());
            table.setTenantId(source.getTenantId());
            table.setDeptId(source.getDeptId());
            table.setStatus(0);
            table.setCreated(now);
            table.setModified(now);
            table.setCreatedBy(account == null ? BigInteger.ZERO : account.getId());
            table.setModifiedBy(account == null ? BigInteger.ZERO : account.getId());
            tableMapper.insert(table);
        } else {
            if (!hasText(existing.getTableDesc())) {
                existing.setTableDesc(normalizeDescription(table.getTableDesc()));
            }
            existing.setActualTable(table.getActualTable());
            existing.setMaterializedTable(table.getMaterializedTable());
            existing.setTableKind(table.getTableKind());
            existing.setAccessMode(table.getAccessMode());
            existing.setVersioningEnabled(table.getVersioningEnabled());
            existing.setCapabilitiesJson(table.getCapabilitiesJson());
            existing.setModified(now);
            existing.setModifiedBy(account == null ? BigInteger.ZERO : account.getId());
            tableMapper.update(existing);
            table = existing;
        }
        Map<String, DatacenterTableField> existingFieldMap = getFields(table.getId()).stream()
                .collect(LinkedHashMap::new, (map, field) -> map.put(field.getFieldName(), field), Map::putAll);
        QueryWrapper deleteWrapper = QueryWrapper.create();
        deleteWrapper.eq(DatacenterTableField::getTableId, table.getId());
        tableFieldMapper.deleteByQuery(deleteWrapper);
        for (DatacenterTableField field : detail.getFields()) {
            DatacenterTableField existingField = existingFieldMap.get(field.getFieldName());
            if (existingField != null && hasText(existingField.getFieldDesc())) {
                field.setFieldDesc(existingField.getFieldDesc());
            } else {
                field.setFieldDesc(normalizeDescription(field.getFieldDesc()));
            }
            field.setId(null);
            field.setTableId(table.getId());
            field.setCreated(now);
            field.setModified(now);
            field.setCreatedBy(account == null ? BigInteger.ZERO : account.getId());
            field.setModifiedBy(account == null ? BigInteger.ZERO : account.getId());
            tableFieldMapper.insert(field);
        }
        table.setFields(getFields(table.getId()));
        return table;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public DatacenterTable saveDescriptions(BigInteger tableId, String tableDesc, List<DatacenterFieldDescriptionUpdate> fields, LoginAccount account) {
        if (tableId == null) {
            throw new BusinessException("缺少表 ID");
        }
        DatacenterTable table = getTableWithFields(tableId);
        Date now = new Date();
        table.setTableDesc(normalizeDescription(tableDesc));
        table.setModified(now);
        table.setModifiedBy(account == null ? BigInteger.ZERO : account.getId());
        tableMapper.update(table);

        Map<BigInteger, DatacenterFieldDescriptionUpdate> fieldUpdateMap = new LinkedHashMap<>();
        if (fields != null) {
            for (DatacenterFieldDescriptionUpdate field : fields) {
                if (field == null || field.getFieldId() == null) {
                    continue;
                }
                fieldUpdateMap.put(field.getFieldId(), field);
            }
        }
        for (DatacenterTableField field : table.getFields()) {
            DatacenterFieldDescriptionUpdate update = fieldUpdateMap.get(field.getId());
            if (update == null) {
                continue;
            }
            field.setFieldDesc(normalizeDescription(update.getFieldDesc()));
            field.setModified(now);
            field.setModifiedBy(account == null ? BigInteger.ZERO : account.getId());
            tableFieldMapper.update(field);
        }
        return getTableWithFields(tableId);
    }

    @Override
    public DatacenterTable getTableWithFields(BigInteger tableId) {
        DatacenterTable table = tableMapper.selectOneById(tableId);
        if (table == null) {
            throw new BusinessException("数据集不存在: " + tableId);
        }
        table.setFields(getFields(tableId));
        return table;
    }

    @Override
    public List<DatacenterTableField> getFields(BigInteger tableId) {
        QueryWrapper wrapper = QueryWrapper.create();
        wrapper.eq(DatacenterTableField::getTableId, tableId);
        wrapper.orderBy("id");
        return tableFieldMapper.selectListByQuery(wrapper);
    }

    @Override
    public DatasetRef resolveDatasetRef(BigInteger tableId) {
        DatacenterTable table = getTableWithFields(tableId);
        DatasetRef ref = new DatasetRef();
        ref.setTableId(tableId);
        ref.setSourceId(table.getSourceId());
        ref.setCatalogId(table.getCatalogId());
        ref.setTableName(table.getTableName());
        DatacenterCatalog catalog = getCatalogById(table.getCatalogId());
        if (catalog != null) {
            ref.setCatalogName(catalog.getCatalogName());
        }
        return ref;
    }

    @Override
    public DatacenterSource getSourceRequired(BigInteger sourceId) {
        DatacenterSource source = sourceMapper.selectOneById(sourceId);
        if (source == null) {
            throw new BusinessException("连接不存在");
        }
        return source;
    }

    @Override
    public DatacenterCatalog getCatalogById(BigInteger catalogId) {
        if (catalogId == null) {
            return null;
        }
        return catalogMapper.selectOneById(catalogId);
    }

    @Override
    public List<DatacenterTable> listManagedTables(BigInteger sourceId, BigInteger catalogId) {
        QueryWrapper wrapper = QueryWrapper.create();
        wrapper.eq(DatacenterTable::getSourceId, sourceId);
        if (catalogId != null) {
            wrapper.eq(DatacenterTable::getCatalogId, catalogId);
        }
        wrapper.orderBy("created desc");
        return tableMapper.selectListByQuery(wrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int removeTables(List<BigInteger> tableIds) {
        List<BigInteger> ids = tableIds == null ? List.of() : tableIds.stream().filter(id -> id != null).distinct().toList();
        if (ids.isEmpty()) {
            return 0;
        }
        QueryWrapper physicalTableWrapper = QueryWrapper.create();
        physicalTableWrapper.in(DatacenterTable::getId, ids);
        List<DatacenterTable> tables = tableMapper.selectListByQuery(physicalTableWrapper);
        tables.forEach(this::deletePhysicalTableIfNecessary);

        QueryWrapper fieldWrapper = QueryWrapper.create();
        fieldWrapper.in(DatacenterTableField::getTableId, ids);
        tableFieldMapper.deleteByQuery(fieldWrapper);

        QueryWrapper versionWrapper = QueryWrapper.create();
        versionWrapper.in(tech.easyflow.datacenter.meta.entity.DatacenterDatasetVersion::getTableId, ids);
        datasetVersionMapper.deleteByQuery(versionWrapper);

        QueryWrapper importJobWrapper = QueryWrapper.create();
        importJobWrapper.in(tech.easyflow.datacenter.meta.entity.DatacenterImportJob::getTableId, ids);
        importJobMapper.deleteByQuery(importJobWrapper);

        QueryWrapper upstreamWrapper = QueryWrapper.create();
        upstreamWrapper.in(tech.easyflow.datacenter.meta.entity.DatacenterDerivedTable::getSourceTableId, ids);
        derivedTableMapper.deleteByQuery(upstreamWrapper);

        QueryWrapper downstreamWrapper = QueryWrapper.create();
        downstreamWrapper.in(tech.easyflow.datacenter.meta.entity.DatacenterDerivedTable::getDerivedTableId, ids);
        derivedTableMapper.deleteByQuery(downstreamWrapper);

        QueryWrapper tableWrapper = QueryWrapper.create();
        tableWrapper.in(DatacenterTable::getId, ids);
        return tableMapper.deleteByQuery(tableWrapper);
    }

    private void deletePhysicalTableIfNecessary(DatacenterTable table) {
        if (table == null || !shouldDropPhysicalTable(table)) {
            return;
        }
        String physicalTableName = table.getActualTable();
        if (physicalTableName == null || physicalTableName.isBlank()) {
            physicalTableName = table.getMaterializedTable();
        }
        if (physicalTableName == null || physicalTableName.isBlank()) {
            return;
        }
        DatacenterTable physicalTable = new DatacenterTable();
        physicalTable.setActualTable(physicalTableName);
        dbHandleManager.getDbHandler().deleteTable(physicalTable);
    }

    private boolean shouldDropPhysicalTable(DatacenterTable table) {
        String tableKind = table.getTableKind();
        if (tableKind == null || tableKind.isBlank()) {
            return false;
        }
        try {
            DatacenterTableKind kind = DatacenterTableKind.valueOf(tableKind);
            return kind == DatacenterTableKind.PROJECT_MANAGED
                    || kind == DatacenterTableKind.EXCEL_SHEET
                    || kind == DatacenterTableKind.EXCEL_MATERIALIZED
                    || kind == DatacenterTableKind.DERIVED_TABLE;
        } catch (IllegalArgumentException ignored) {
            return false;
        }
    }

    private void applyTableDefaults(DatacenterTable table, DatacenterSource source, DatacenterTableDetailMeta detail) {
        if (table.getTableKind() == null || table.getTableKind().isBlank()) {
            table.setTableKind(DatacenterTableKind.EXTERNAL_TABLE.name());
        }
        if (table.getAccessMode() == null || table.getAccessMode().isBlank()) {
            table.setAccessMode("READ_ONLY");
        }
        if (table.getVersioningEnabled() == null) {
            table.setVersioningEnabled(0);
        }
        if (table.getCapabilitiesJson() == null || table.getCapabilitiesJson().isEmpty()) {
            table.setCapabilitiesJson(Map.of(
                    "capabilities",
                    source.getCapabilitiesJson() == null
                            ? List.of()
                            : source.getCapabilitiesJson().getOrDefault("capabilities", List.of())
            ));
        }
        if ((table.getActualTable() == null || table.getActualTable().isBlank()) && table.getTableName() != null) {
            table.setActualTable(table.getTableName());
        }
        table.setTableDesc(normalizeDescription(table.getTableDesc()));
        if (detail.getFields() == null) {
            detail.setFields(List.of());
        }
    }

    private String normalizeDescription(String description) {
        if (description == null) {
            return "";
        }
        return description.trim();
    }

    private boolean hasText(String value) {
        return value != null && !value.trim().isEmpty();
    }

    private BigInteger builtinSourceId(DatacenterSourceType sourceType, BigInteger tenantId) {
        if (sourceType == DatacenterSourceType.PROJECT_MYSQL) {
            return DatacenterMetaConstants.PROJECT_SOURCE_BASE.add(tenantId);
        }
        throw new BusinessException("不支持的内置源 ID 计算");
    }

    private BigInteger builtinCatalogId(DatacenterSource source, String catalogName) {
        if (source.getId().compareTo(DatacenterMetaConstants.PROJECT_SOURCE_BASE) >= 0
                && defaultCatalogName(DatacenterSourceType.PROJECT_MYSQL).equals(catalogName)) {
            return DatacenterMetaConstants.PROJECT_CATALOG_BASE.add(source.getTenantId());
        }
        return null;
    }

    private String defaultCatalogName(DatacenterSourceType sourceType) {
        if (sourceType == DatacenterSourceType.PROJECT_MYSQL) {
            return "project_mysql";
        }
        return sourceType.name().toLowerCase();
    }

    private List<String> builtinCapabilities(DatacenterSourceType sourceType) {
        return List.of("TEST_CONNECTION", "BROWSE_METADATA", "READ_QUERY", "WRITE_MUTATION");
    }
}
