package com.usky.issue.service.impl;

import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.usky.issue.service.util.SyncJsonMapper;
import com.usky.issue.domain.IssueSyncData;
import com.usky.issue.domain.IssueSyncDownQueue;
import com.usky.issue.mapper.IssueSyncDataMapper;
import com.usky.issue.mapper.IssueSyncDownQueueMapper;
import com.usky.issue.service.CloudConfigService;
import com.usky.issue.service.CloudSyncPublishService;
import com.usky.issue.service.CloudSyncReceiveService;
import com.usky.issue.service.enums.SyncDirection;
import com.usky.issue.service.enums.SyncTypeEnum;
import com.usky.issue.service.support.CloudEntitySupport;
import com.usky.issue.service.sync.SyncDataApplierRegistry;
import com.usky.issue.service.sync.SyncPayloadSupport;
import com.usky.issue.service.vo.SyncPacket;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * 云端同步接收服务实现
 *
 * @author fyc
 * @date 2026-06-08
 */
@Slf4j
@Service
public class CloudSyncReceiveServiceImpl implements CloudSyncReceiveService {

    private static final int POLL_BATCH_SIZE = 100;
    private static final com.fasterxml.jackson.databind.ObjectMapper OBJECT_MAPPER = SyncJsonMapper.INSTANCE;

    @Autowired
    private IssueSyncDataMapper syncDataMapper;
    @Autowired
    private IssueSyncDownQueueMapper downQueueMapper;
    @Autowired
    private CloudConfigService cloudConfigService;
    @Autowired
    private CloudSyncPublishService cloudSyncPublishService;
    @Autowired
    private SyncDataApplierRegistry syncDataApplierRegistry;

    @Override
    public boolean validateTenant(String tenantId) {
        if (!StringUtils.hasText(tenantId)) {
            return false;
        }
        return cloudConfigService.validateTenantConnection(Integer.valueOf(tenantId)).isSuccess();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int batchUpsert(SyncPacket packet) {
        if (packet == null || packet.getDataList() == null || packet.getDataList().isEmpty()) {
            return 0;
        }
        int count = 0;
        for (Object item : packet.getDataList()) {
            try {
                IssueSyncData incoming = convertToSyncData(item, packet.getTableName());
                if (incoming == null) {
                    continue;
                }
                IssueSyncData exist = syncDataMapper.selectOne(new LambdaQueryWrapper<IssueSyncData>()
                        .eq(IssueSyncData::getTenantId, incoming.getTenantId())
                        .eq(IssueSyncData::getTableName, incoming.getTableName())
                        .eq(IssueSyncData::getDataKey, incoming.getDataKey())
                        .eq(IssueSyncData::getIsDeleted, 0));
                if (exist == null) {
                    incoming.setSyncStatus(1);
                    incoming.setSyncDirection("LOCAL");
                    CloudEntitySupport.fillOnInsert(incoming);
                    syncDataMapper.insert(incoming);
                    applyToBusinessTable(incoming, "UPDATE");
                    count++;
                } else if (incoming.getSyncVersion() >= exist.getSyncVersion()) {
                    incoming.setId(exist.getId());
                    incoming.setSyncStatus(1);
                    incoming.setSyncDirection("LOCAL");
                    CloudEntitySupport.fillOnUpdate(incoming);
                    syncDataMapper.updateById(incoming);
                    applyToBusinessTable(incoming, "UPDATE");
                    count++;
                }
            } catch (Exception e) {
                log.error("云端写入同步数据失败", e);
            }
        }
        return count;
    }

    @DS("usky-cloud")
    @Override
    public SyncPacket pollDownQueue(String tenantId, String tableName, Long lastVersion, boolean forceBackfill) {
        if (lastVersion == null) {
            lastVersion = 0L;
        }
        Integer tenant = Integer.valueOf(tenantId);
        if (SyncTypeEnum.USER.getCode().equalsIgnoreCase(tableName)) {
            int backfilled = cloudSyncPublishService.backfillUsersIfNeeded(tenant, lastVersion, forceBackfill);
            log.info("[poll] USER 类型从 sys_user 回填 {} 条, tenantId={}, lastVersion={}, force={}",
                    backfilled, tenantId, lastVersion, forceBackfill);
        }
        List<IssueSyncDownQueue> queueList = downQueueMapper.selectList(new LambdaQueryWrapper<IssueSyncDownQueue>()
                .eq(IssueSyncDownQueue::getTenantId, tenant)
                .eq(IssueSyncDownQueue::getTableName, tableName)
                .eq(IssueSyncDownQueue::getStatus, 0)
                .gt(IssueSyncDownQueue::getSyncVersion, lastVersion)
                .eq(IssueSyncDownQueue::getIsDeleted, 0)
                .orderByAsc(IssueSyncDownQueue::getSyncVersion)
                .last("LIMIT " + POLL_BATCH_SIZE));

        if (queueList.isEmpty()) {
            log.info("[poll] 下发队列为空 tenantId={}, tableName={}, lastVersion={}", tenantId, tableName, lastVersion);
            return null;
        }

        Long maxVersion = queueList.stream()
                .mapToLong(IssueSyncDownQueue::getSyncVersion)
                .max()
                .orElse(lastVersion);

        return SyncPacket.builder()
                .tenantId(tenantId)
                .tableName(tableName)
                .direction(SyncDirection.DOWN)
                .dataList(Collections.unmodifiableList(queueList))
                .batchVersion(maxVersion)
                .hasMore(queueList.size() >= POLL_BATCH_SIZE)
                .build();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void ackDownQueue(List<Long> queueIds) {
        if (queueIds == null || queueIds.isEmpty()) {
            return;
        }
        downQueueMapper.update(null, new LambdaUpdateWrapper<IssueSyncDownQueue>()
                .in(IssueSyncDownQueue::getId, queueIds)
                .set(IssueSyncDownQueue::getStatus, 1));
    }

    @Override
    public int countPendingDownQueue(Integer tenantId, String tableName) {
        Integer count = downQueueMapper.selectCount(new LambdaQueryWrapper<IssueSyncDownQueue>()
                .eq(IssueSyncDownQueue::getTenantId, tenantId)
                .eq(IssueSyncDownQueue::getTableName, tableName)
                .eq(IssueSyncDownQueue::getStatus, 0)
                .eq(IssueSyncDownQueue::getIsDeleted, 0));
        return count == null ? 0 : count;
    }

    @SuppressWarnings("unchecked")
    private IssueSyncData convertToSyncData(Object item, String tableName) throws Exception {
        if (item instanceof IssueSyncData) {
            return (IssueSyncData) item;
        }
        Map<String, Object> map = OBJECT_MAPPER.convertValue(item, Map.class);
        IssueSyncData data = new IssueSyncData();
        data.setTenantId(Integer.valueOf(String.valueOf(map.get("tenantId"))));
        data.setTableName(tableName);
        data.setDataKey(String.valueOf(map.get("dataKey")));
        data.setPayload(map.containsKey("payload") ? String.valueOf(map.get("payload")) : OBJECT_MAPPER.writeValueAsString(map));
        Object version = map.get("syncVersion");
        data.setSyncVersion(version == null ? System.currentTimeMillis() : Long.valueOf(String.valueOf(version)));
        return data;
    }

    private void applyToBusinessTable(IssueSyncData incoming, String operation) {
        if (!StringUtils.hasText(incoming.getPayload()) || incoming.getTenantId() == null) {
            return;
        }
        try {
            String businessPayload = SyncPayloadSupport.resolveBusinessPayload(incoming.getPayload());
            syncDataApplierRegistry.apply(incoming.getTenantId(), incoming.getTableName(), operation, businessPayload);
        } catch (Exception e) {
            log.error("云端应用同步数据到业务表失败 tableName={}, tenantId={}",
                    incoming.getTableName(), incoming.getTenantId(), e);
        }
    }
}
