package com.zcloud.personnel.positioning.command;

import com.alibaba.cola.exception.BizException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleDO;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleFenceRelDO;
import com.zcloud.personnel.positioning.persistence.dataobject.AlarmRuleItemDO;
import com.zcloud.personnel.positioning.persistence.dataobject.FenceDO;
import com.zcloud.personnel.positioning.persistence.dataobject.FencePointDO;
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalBindDO;
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalDO;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleFenceRelRepository;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleItemRepository;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleRepository;
import com.zcloud.personnel.positioning.persistence.repository.FencePointRepository;
import com.zcloud.personnel.positioning.persistence.repository.FenceRepository;
import com.zcloud.personnel.positioning.persistence.repository.TerminalBindRepository;
import com.zcloud.personnel.positioning.persistence.repository.TerminalRepository;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionTemplate;

import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

@Component
@AllArgsConstructor
public class FindsPendingSyncService {
    public static final String STATUS_WAIT = "WAIT";
    public static final String STATUS_FAIL = "FAIL";
    public static final String STATUS_DELETE_WAIT = "DELETE_WAIT";
    public static final String STATUS_DELETE_FAIL = "DELETE_FAIL";

    private static final Logger LOGGER = LoggerFactory.getLogger(FindsPendingSyncService.class);
    private static final int RETRY_BATCH_SIZE = 100;

    private final FenceRepository fenceRepository;
    private final FencePointRepository fencePointRepository;
    private final AlarmRuleRepository alarmRuleRepository;
    private final AlarmRuleFenceRelRepository alarmRuleFenceRelRepository;
    private final AlarmRuleItemRepository alarmRuleItemRepository;
    private final TerminalRepository terminalRepository;
    private final TerminalBindRepository terminalBindRepository;
    private final FenceFindsSyncService fenceFindsSyncService;
    private final AlarmRuleFindsSyncService alarmRuleFindsSyncService;
    private final TerminalFindsSyncService terminalFindsSyncService;
    private final FindsSyncStateService findsSyncStateService;
    private final AfterCommitExecutor afterCommitExecutor;
    private final TransactionTemplate transactionTemplate;
    private final ObjectMapper objectMapper;
    private final Set<String> inFlight = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());

    public void scheduleFence(Long id) {
        afterCommitExecutor.execute(() -> syncFence(id));
    }

    public void scheduleRule(Long id) {
        afterCommitExecutor.execute(() -> syncRule(id));
    }

    public void scheduleTerminal(Long id) {
        afterCommitExecutor.execute(() -> syncTerminal(id));
    }

    public void retryPending() {
        retryFences();
        retryTerminals();
        retryRules();
        int failureCount = countFailures();
        if (failureCount > 0) {
            throw new BizException("FindS待同步数据仍有 " + failureCount + " 条失败记录");
        }
    }

    public void syncFence(Long id) {
        withLock("FENCE:" + id, () -> {
            FenceDO fence = fenceRepository.getById(id);
            if (fence == null) {
                return;
            }
            try {
                if (isDeleteStatus(fence.getSyncStatus())) {
                    fenceFindsSyncService.delete(fence);
                    transactionTemplate.execute(status -> {
                        if (findsSyncStateService.deleteFenceIfCurrent(fence)) {
                            QueryWrapper<FencePointDO> pointWrapper = new QueryWrapper<>();
                            pointWrapper.eq("fence_id", id);
                            fencePointRepository.remove(pointWrapper);
                        }
                        return null;
                    });
                } else {
                    fenceFindsSyncService.createOrUpdate(fence);
                }
            } catch (Exception e) {
                findsSyncStateService.failFence(fence, isDeleteStatus(fence.getSyncStatus()), errorJson(e));
                LOGGER.warn("Synchronize fence to FindS failed, id={}", id, e);
            }
        });
    }

    public void syncRule(Long id) {
        withLock("RULE:" + id, () -> {
            AlarmRuleDO rule = alarmRuleRepository.getById(id);
            if (rule == null) {
                return;
            }
            try {
                if (isDeleteStatus(rule.getSyncStatus())) {
                    alarmRuleFindsSyncService.delete(rule);
                    transactionTemplate.execute(status -> {
                        if (findsSyncStateService.deleteAlarmRuleIfCurrent(rule)) {
                            QueryWrapper<AlarmRuleFenceRelDO> relWrapper = new QueryWrapper<>();
                            relWrapper.eq("rule_id", id);
                            alarmRuleFenceRelRepository.remove(relWrapper);
                            QueryWrapper<AlarmRuleItemDO> itemWrapper = new QueryWrapper<>();
                            itemWrapper.eq("rule_id", id);
                            alarmRuleItemRepository.remove(itemWrapper);
                        }
                        return null;
                    });
                } else {
                    alarmRuleFindsSyncService.createOrUpdate(rule);
                }
            } catch (Exception e) {
                findsSyncStateService.failAlarmRule(rule, isDeleteStatus(rule.getSyncStatus()), errorJson(e));
                LOGGER.warn("Synchronize alarm rule to FindS failed, id={}", id, e);
            }
        });
    }

    public void syncTerminal(Long id) {
        withLock("TERMINAL:" + id, () -> {
            TerminalDO terminal = terminalRepository.getById(id);
            if (terminal == null) {
                return;
            }
            try {
                if (isDeleteStatus(terminal.getSyncStatus())) {
                    terminalFindsSyncService.delete(terminal);
                    transactionTemplate.execute(status -> {
                        if (findsSyncStateService.deleteTerminalIfCurrent(terminal)) {
                            QueryWrapper<TerminalBindDO> bindWrapper = new QueryWrapper<>();
                            bindWrapper.eq("terminal_id", id);
                            terminalBindRepository.remove(bindWrapper);
                        }
                        return null;
                    });
                } else if (STATUS_WAIT.equals(terminal.getSyncStatus()) || STATUS_FAIL.equals(terminal.getSyncStatus())) {
                    terminalFindsSyncService.createOrUpdate(terminal);
                } else {
                    terminalFindsSyncService.syncPendingBindings(terminal);
                }
            } catch (Exception e) {
                findsSyncStateService.failTerminal(terminal, isDeleteStatus(terminal.getSyncStatus()), errorJson(e));
                LOGGER.warn("Synchronize terminal to FindS failed, id={}", id, e);
            }
        });
    }

    private void retryFences() {
        QueryWrapper<FenceDO> wrapper = pendingWrapper();
        List<FenceDO> rows = fenceRepository.list(wrapper);
        for (FenceDO row : rows) {
            syncFence(row.getId());
        }
    }

    private void retryRules() {
        QueryWrapper<AlarmRuleDO> wrapper = pendingWrapper();
        List<AlarmRuleDO> rows = alarmRuleRepository.list(wrapper);
        for (AlarmRuleDO row : rows) {
            syncRule(row.getId());
        }
    }

    private void retryTerminals() {
        QueryWrapper<TerminalDO> wrapper = pendingWrapper();
        List<TerminalDO> rows = terminalRepository.list(wrapper);
        Set<Long> terminalIds = Collections.newSetFromMap(new ConcurrentHashMap<Long, Boolean>());
        for (TerminalDO row : rows) {
            terminalIds.add(row.getId());
        }
        QueryWrapper<TerminalBindDO> bindWrapper = new QueryWrapper<>();
        bindWrapper.in("sync_status", STATUS_WAIT, STATUS_FAIL);
        bindWrapper.and(value -> value.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
        bindWrapper.last("LIMIT " + RETRY_BATCH_SIZE);
        for (TerminalBindDO binding : terminalBindRepository.list(bindWrapper)) {
            if (binding.getTerminalId() != null) {
                terminalIds.add(binding.getTerminalId());
            }
        }
        for (Long terminalId : terminalIds) {
            syncTerminal(terminalId);
        }
    }

    private <T> QueryWrapper<T> pendingWrapper() {
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        wrapper.in("sync_status", STATUS_WAIT, STATUS_FAIL, STATUS_DELETE_WAIT, STATUS_DELETE_FAIL);
        wrapper.and(value -> value.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
        wrapper.orderByAsc("update_time", "id");
        wrapper.last("LIMIT " + RETRY_BATCH_SIZE);
        return wrapper;
    }

    private int countFailures() {
        return safeCount(fenceRepository.count(failureWrapper()))
                + safeCount(alarmRuleRepository.count(failureWrapper()))
                + safeCount(terminalRepository.count(failureWrapper()))
                + safeCount(terminalBindRepository.count(failureWrapper()));
    }

    private <T> QueryWrapper<T> failureWrapper() {
        QueryWrapper<T> wrapper = new QueryWrapper<>();
        wrapper.in("sync_status", STATUS_FAIL, STATUS_DELETE_FAIL);
        wrapper.and(value -> value.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
        return wrapper;
    }

    private int safeCount(long count) {
        return count > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) count;
    }

    private boolean isDeleteStatus(String status) {
        return STATUS_DELETE_WAIT.equals(status) || STATUS_DELETE_FAIL.equals(status);
    }

    private String errorJson(Exception e) {
        try {
            return objectMapper.writeValueAsString(Collections.singletonMap("lastError", safeMessage(e)));
        } catch (Exception ignored) {
            return "{\"lastError\":\"FindS synchronization failed\"}";
        }
    }

    private String safeMessage(Exception e) {
        String message = e == null ? null : e.getMessage();
        if (message == null) {
            return "FindS synchronization failed";
        }
        return message.length() > 1000 ? message.substring(0, 1000) : message;
    }

    private void withLock(String key, Runnable task) {
        if (!inFlight.add(key)) {
            return;
        }
        try {
            task.run();
        } finally {
            inFlight.remove(key);
        }
    }
}
