package com.usky.issue.service.impl;

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.IssueCloudConfig;
import com.usky.issue.domain.IssueSyncCursor;
import com.usky.issue.domain.IssueSyncData;
import com.usky.issue.domain.IssueSyncDownQueue;
import com.usky.issue.mapper.IssueSyncCursorMapper;
import com.usky.issue.mapper.IssueSyncDataMapper;
import com.usky.issue.service.LocalPullService;
import com.usky.issue.service.client.CloudPlatformClient;
import com.usky.issue.service.enums.SyncDirection;
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.constant.CloudIntegrationConstants;
import com.usky.issue.service.vo.CloudPollResult;
import com.usky.issue.service.vo.SyncPacket;
import com.usky.issue.service.vo.SyncResponse;
import com.usky.issue.service.vo.SyncResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 本地拉取服务实现
 *
 * @author fyc
 * @date 2026-06-08
 */
@Slf4j
@Service
public class LocalPullServiceImpl implements LocalPullService {

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

    @Autowired
    private IssueSyncDataMapper syncDataMapper;
    @Autowired
    private IssueSyncCursorMapper cursorMapper;
    @Autowired
    private CloudPlatformClient cloudPlatformClient;
    @Autowired
    private SyncDataApplierRegistry syncDataApplierRegistry;

    @Override
    public SyncResult pullAndApply(String tableName, IssueCloudConfig config) {
        SyncResult result = SyncResult.empty();
        List<IssueSyncCursor> cursors = cursorMapper.selectList(new LambdaQueryWrapper<IssueSyncCursor>()
                .eq(IssueSyncCursor::getDirection, SyncDirection.DOWN.getCode())
                .eq(IssueSyncCursor::getIsDeleted, 0));
        if (cursors.isEmpty() && config.getTenantId() != null) {
            result.merge(pullByTenant(config.getTenantId(), tableName, config));
            return result;
        }
        for (IssueSyncCursor cursor : cursors) {
            try {
                result.merge(pullByTenant(cursor.getTenantId(), tableName, config));
            } catch (Exception e) {
                log.error("拉取租户{}数据失败", cursor.getTenantId(), e);
                result.setFailureCount(result.getFailureCount() + 1);
                result.setErrorMessage(e.getMessage());
            }
        }
        return result;
    }

    @Override
    public SyncResult pullByTenant(Integer tenantId, String tableName, IssueCloudConfig config, boolean forceBackfill) {
        SyncResult result = SyncResult.empty();
        IssueSyncCursor cursor = getOrInitCursor(tenantId, tableName, SyncDirection.DOWN.getCode());
        Long lastVersion = cursor.getLastSyncVersion() == null ? 0L : cursor.getLastSyncVersion();
        if (forceBackfill) {
            lastVersion = 0L;
            updateCursor(tenantId, tableName, SyncDirection.DOWN.getCode(), 0L);
            log.info("手动全量拉取，已重置 DOWN 游标 tenantId={}, tableName={}", tenantId, tableName);
        }

        while (true) {
            CloudPollResult pollResult = cloudPlatformClient.pollCloud(
                    tenantId, tableName, lastVersion, config, forceBackfill);
            if (pollResult.isAuthFailed()) {
                result.setFailureCount(1);
                result.setErrorMessage(CloudIntegrationConstants.MSG_POLL_AUTH_FAILED);
                log.error("[pull] poll 认证失败 tenantId={}, tableName={}", tenantId, tableName);
                break;
            }
            if (pollResult.isRequestFailed()) {
                result.setFailureCount(1);
                result.setErrorMessage(pollResult.getErrorMessage());
                break;
            }
            SyncPacket packet = pollResult.getPacket();
            if (packet == null || packet.getDataList() == null || packet.getDataList().isEmpty()) {
                if (result.getTotalCount() == 0) {
                    log.warn("云端未返回待同步数据 tenantId={}, tableName={}（type=USER 对应 sys_user，数据经 issue_sync_down_queue 下发）",
                            tenantId, tableName);
                }
                break;
            }
            forceBackfill = false;

            List<IssueSyncDownQueue> queueList = convertQueueList(packet.getDataList());
            List<Long> ackIds = new ArrayList<>();
            int success = 0;
            int failure = 0;

            for (IssueSyncDownQueue queue : queueList) {
                try {
                    applyToLocal(queue);
                    ackIds.add(queue.getId());
                    success++;
                } catch (Exception e) {
                    log.error("应用云端数据到本地失败, queueId={}", queue.getId(), e);
                    failure++;
                }
            }

            result.setTotalCount(result.getTotalCount() + queueList.size());
            result.setSuccessCount(result.getSuccessCount() + success);
            result.setFailureCount(result.getFailureCount() + failure);

            lastVersion = packet.getBatchVersion();
            updateCursor(tenantId, tableName, SyncDirection.DOWN.getCode(), lastVersion);
            ackCloud(ackIds, config);

            if (Boolean.FALSE.equals(packet.getHasMore())) {
                break;
            }
        }
        return result;
    }

    private void applyToLocal(IssueSyncDownQueue queue) throws Exception {
        String rawPayload = queue.getPayload();
        IssueSyncData data;
        try {
            data = OBJECT_MAPPER.readValue(rawPayload, IssueSyncData.class);
        } catch (Exception ex) {
            data = new IssueSyncData();
            data.setPayload(rawPayload);
        }
        if (!StringUtils.hasText(data.getDataKey())) {
            data.setDataKey(queue.getTableName() + "-" + queue.getId());
        }
        data.setTenantId(queue.getTenantId());
        data.setTableName(queue.getTableName());
        data.setSyncVersion(queue.getSyncVersion());
        data.setSyncDirection("CLOUD");
        data.setSyncStatus(1);

        IssueSyncData exist = syncDataMapper.selectOne(new LambdaQueryWrapper<IssueSyncData>()
                .eq(IssueSyncData::getTenantId, data.getTenantId())
                .eq(IssueSyncData::getTableName, data.getTableName())
                .eq(IssueSyncData::getDataKey, data.getDataKey())
                .eq(IssueSyncData::getIsDeleted, 0));
        if (exist == null) {
            CloudEntitySupport.fillOnInsert(data);
            syncDataMapper.insert(data);
        } else if (queue.getSyncVersion() >= exist.getSyncVersion()) {
            data.setId(exist.getId());
            CloudEntitySupport.fillOnUpdate(data);
            syncDataMapper.updateById(data);
        }
        applyToBusinessTable(queue, data);
    }

    private void applyToBusinessTable(IssueSyncDownQueue queue, IssueSyncData data) throws Exception {
        String rawPayload = StringUtils.hasText(data.getPayload()) ? data.getPayload() : queue.getPayload();
        if (!StringUtils.hasText(rawPayload)) {
            return;
        }
        String businessPayload = SyncPayloadSupport.resolveBusinessPayload(rawPayload);
        String operation = StringUtils.hasText(queue.getOperation()) ? queue.getOperation() : "UPDATE";
        syncDataApplierRegistry.apply(queue.getTenantId(), queue.getTableName(), operation, businessPayload);
    }

    private void ackCloud(List<Long> queueIds, IssueCloudConfig config) {
        if (queueIds == null || queueIds.isEmpty()) {
            return;
        }
        SyncResponse response = cloudPlatformClient.ackPoll(queueIds, config);
        if (response == null || !response.isSuccess()) {
            log.warn("ACK云端失败 queueIds={}", queueIds);
        }
    }

    @SuppressWarnings("unchecked")
    private List<IssueSyncDownQueue> convertQueueList(List<?> dataList) {
        return dataList.stream().map(item -> {
            if (item instanceof IssueSyncDownQueue) {
                return (IssueSyncDownQueue) item;
            }
            return OBJECT_MAPPER.convertValue(item, IssueSyncDownQueue.class);
        }).collect(Collectors.toList());
    }

    private IssueSyncCursor getOrInitCursor(Integer tenantId, String tableName, String direction) {
        IssueSyncCursor cursor = cursorMapper.selectOne(new LambdaQueryWrapper<IssueSyncCursor>()
                .eq(IssueSyncCursor::getTenantId, tenantId)
                .eq(IssueSyncCursor::getTableName, tableName)
                .eq(IssueSyncCursor::getDirection, direction)
                .eq(IssueSyncCursor::getIsDeleted, 0));
        if (cursor == null) {
            cursor = new IssueSyncCursor();
            cursor.setTenantId(tenantId);
            cursor.setTableName(tableName);
            cursor.setDirection(direction);
            cursor.setLastSyncVersion(0L);
            cursor.setLastSyncTime(LocalDateTime.now());
            CloudEntitySupport.fillOnInsert(cursor);
            cursorMapper.insert(cursor);
        }
        return cursor;
    }

    private void updateCursor(Integer tenantId, String tableName, String direction, Long version) {
        cursorMapper.update(null, new LambdaUpdateWrapper<IssueSyncCursor>()
                .eq(IssueSyncCursor::getTenantId, tenantId)
                .eq(IssueSyncCursor::getTableName, tableName)
                .eq(IssueSyncCursor::getDirection, direction)
                .set(IssueSyncCursor::getLastSyncVersion, version)
                .set(IssueSyncCursor::getLastSyncTime, LocalDateTime.now()));
    }

    @Override
    public void resetDownCursor(Integer tenantId, String tableName) {
        updateCursor(tenantId, tableName, SyncDirection.DOWN.getCode(), 0L);
    }
}
