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.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import com.zcloud.personnel.positioning.dto.AlarmRuleCompanyPageQry;
import com.zcloud.personnel.positioning.dto.AlarmRulePageQry;
import com.zcloud.personnel.positioning.integration.finds.FindsOpenApiClient;
import com.zcloud.personnel.positioning.integration.finds.OpenapiConfigCache;
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.OpenapiConfigDO;
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.FenceRepository;
import com.zcloud.personnel.positioning.persistence.repository.OpenapiConfigRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Pulls FindS alarm-rule snapshots into local tables.
 *
 * @Author fangjiakai
 * @Date 2026-07-08
 */
@Component
@AllArgsConstructor
public class AlarmRuleFindsPullService {
    private static final String LIST_API = "finds.alarm.rule.list";
    private static final String QUERY_API = "finds.alarm.rule.query";
    private static final String STATUS_SUCCESS = "SUCCESS";
    private static final int PULL_PAGE_SIZE = 100;
    private static final int MAX_PULL_PAGE_COUNT = 20;

    private final FindsOpenApiClient findsOpenApiClient;
    private final OpenapiConfigCache openapiConfigCache;
    private final OpenapiConfigRepository openapiConfigRepository;
    private final AlarmRuleRepository alarmRuleRepository;
    private final AlarmRuleFenceRelRepository alarmRuleFenceRelRepository;
    private final AlarmRuleItemRepository alarmRuleItemRepository;
    private final FenceRepository fenceRepository;
    private final ObjectMapper objectMapper;

    public void refreshForCompanyStats(AlarmRuleCompanyPageQry qry) {
        String companyId = qry == null ? null : qry.getEqCorpinfoId();
        refreshConfigs(resolveConfigs(companyId), null);
    }

    public void refreshForList(AlarmRulePageQry qry) {
        String companyId = qry == null ? null : qry.getEqCorpinfoId();
        String alarmCode = qry == null ? null : qry.getEqAlarmCode();
        refreshConfigs(resolveConfigs(companyId), alarmCode);
    }

    public void refreshForListAll() {
        refreshConfigs(resolveConfigs(null), null);
    }

    public int refreshAllConfigured() {
        return refreshConfigs(resolveConfigs(null), null);
    }

    public void refreshForDetail(Long localRuleId) {
        AlarmRuleDO local = alarmRuleRepository.getById(localRuleId);
        if (local == null || local.getFindsRuleId() == null) {
            return;
        }
        OpenapiConfigDO config = resolveConfigForRule(local);
        if (config == null) {
            return;
        }
        Map<String, Object> request = new LinkedHashMap<>();
        request.put("id", local.getFindsRuleId());
        JsonNode root = findsOpenApiClient.post(config, QUERY_API, request);
        JsonNode data = assertSuccess(root, QUERY_API).path("data");
        if (!data.isMissingNode() && !data.isNull()) {
            upsertRule(config, data, true);
        }
    }

    private int refreshConfigs(List<OpenapiConfigDO> configs, String alarmCode) {
        int loaded = 0;
        Set<String> refreshedConfigs = new HashSet<>();
        for (OpenapiConfigDO config : configs) {
            if (!refreshedConfigs.add(configIdentity(config))) {
                continue;
            }
            loaded += refreshConfig(config, alarmCode);
        }
        return loaded;
    }

    private String configIdentity(OpenapiConfigDO config) {
        return defaultIfBlank(config.getHostUrl(), "") + "|" + defaultIfBlank(config.getSik(), "");
    }

    private int refreshConfig(OpenapiConfigDO config, String alarmCode) {
        int pageNo = 1;
        long loaded = 0;
        long total = Long.MAX_VALUE;
        while (loaded < total && pageNo <= MAX_PULL_PAGE_COUNT) {
            Map<String, Object> request = new LinkedHashMap<>();
            request.put("pageNo", pageNo);
            request.put("pageSize", PULL_PAGE_SIZE);
            if (StringUtils.hasText(alarmCode)) {
                request.put("alarmCode", alarmCode);
            }
            JsonNode root = findsOpenApiClient.post(config, LIST_API, request);
            JsonNode data = assertSuccess(root, LIST_API).path("data");
            JsonNode rows = extractRows(data);
            if (!rows.isArray() || rows.size() == 0) {
                total = loaded;
                break;
            }
            for (JsonNode row : rows) {
                upsertRule(config, row, false);
            }
            loaded += rows.size();
            total = totalValue(data, loaded);
            pageNo++;
        }
        if (loaded < total) {
            throw new BizException("FindS alarm-rule pull exceeded max page count");
        }
        return loaded > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) loaded;
    }

    private AlarmRuleDO upsertRule(OpenapiConfigDO config, JsonNode row, boolean refreshItems) {
        Long findsRuleId = longValue(row.path("id"));
        if (findsRuleId == null) {
            return null;
        }
        AlarmRuleDO rule = queryByFindsRuleId(config, findsRuleId);
        if (rule != null && isPendingLocalSync(rule.getSyncStatus())) {
            return rule;
        }
        boolean isNew = rule == null;
        if (isNew) {
            rule = new AlarmRuleDO();
            rule.setRuleCode("ARF" + findsRuleId);
        }
        rule.setFindsRuleId(findsRuleId);
        rule.setRuleName(firstText(row.path("name"), rule.getRuleName()));
        rule.setAlarmCode(firstText(row.path("alarmCode"), rule.getAlarmCode()));
        rule.setAlarmName(defaultIfBlank(rule.getAlarmName(), alarmNameOf(rule.getAlarmCode())));
        rule.setEnable(booleanValue(row.path("enable")) ? 1 : 0);
        rule.setRuleStatus(rule.getEnable() == 1
                ? AlarmRuleConfigPersistence.STATUS_ENABLED
                : AlarmRuleConfigPersistence.STATUS_DISABLED);
        JsonNode scope = row.path("fenceEffectScope");
        String strategy = firstText(scope.path("strategy"), rule.getFenceEffectStrategy());
        rule.setFenceEffectStrategy(defaultIfBlank(strategy, AlarmRuleConfigPersistence.STRATEGY_SPECIFIED));
        rule.setAlarmAttr(defaultIfBlank(rule.getAlarmAttr(), inferAlarmAttr(scope)));
        rule.setTrackTypes(defaultIfBlank(joinTextArray(row.path("trackTypes")), rule.getTrackTypes()));
        rule.setTrackTypes(defaultIfBlank(rule.getTrackTypes(), "STAFF"));
        rule.setCrashType(firstText(row.path("crashType"), rule.getCrashType()));
        FenceDO scopeFence = findFirstScopeFence(scope);
        String corpinfoId = config.getCorpinfoId() != null
                ? config.getCorpinfoId()
                : (scopeFence == null ? rule.getCorpinfoId() : scopeFence.getCorpinfoId());
        String corpinfoName = defaultIfBlank(config.getCorpinfoName(),
                scopeFence == null ? rule.getCorpinfoName() : scopeFence.getCorpinfoName());
        rule.setCorpinfoId(corpinfoId);
        rule.setCorpinfoName(corpinfoName);
        rule.setOrgCode(defaultIfBlank(rule.getOrgCode(), scopeFence == null ? null : scopeFence.getOrgCode()));
        rule.setOrgName(defaultIfBlank(rule.getOrgName(),
                scopeFence == null ? corpinfoName : defaultIfBlank(scopeFence.getOrgName(), corpinfoName)));
        rule.setSyncStatus(STATUS_SUCCESS);
        rule.setLastSyncTime(LocalDateTime.now());
        rule.setRawJson(row.toString());
        if (isNew) {
            alarmRuleRepository.save(rule);
        } else {
            alarmRuleRepository.updateById(rule);
        }
        rebuildFenceRelations(rule, scope);
        if (refreshItems && row.has("ruleItems")) {
            rebuildRuleItems(rule, row.path("ruleItems"));
        }
        return rule;
    }

    private AlarmRuleDO queryByFindsRuleId(OpenapiConfigDO config, Long findsRuleId) {
        QueryWrapper<AlarmRuleDO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("finds_rule_id", findsRuleId);
        queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
        if (config.getCorpinfoId() != null) {
            queryWrapper.eq("corpinfo_id", config.getCorpinfoId());
        }
        queryWrapper.last("LIMIT 1");
        return alarmRuleRepository.getOne(queryWrapper, false);
    }

    private void rebuildFenceRelations(AlarmRuleDO rule, JsonNode scope) {
        if (scope == null || scope.isMissingNode() || scope.isNull()) {
            return;
        }
        QueryWrapper<AlarmRuleFenceRelDO> removeWrapper = new QueryWrapper<>();
        removeWrapper.eq("rule_id", rule.getId());
        alarmRuleFenceRelRepository.remove(removeWrapper);
        saveFenceRelations(rule, scope.path("effectiveFence"), AlarmRuleConfigPersistence.REL_EFFECTIVE);
        saveFenceRelations(rule, scope.path("ignoreFence"), "IGNORE");
    }

    private void saveFenceRelations(AlarmRuleDO rule, JsonNode ids, String relType) {
        if (!ids.isArray()) {
            return;
        }
        for (JsonNode idNode : ids) {
            Long findsFenceId = longValue(idNode);
            if (findsFenceId == null) {
                continue;
            }
            AlarmRuleFenceRelDO rel = new AlarmRuleFenceRelDO();
            rel.setRuleId(rule.getId());
            rel.setFindsFenceId(findsFenceId);
            rel.setRelType(relType);
            rel.setCorpinfoId(rule.getCorpinfoId());
            rel.setCorpinfoName(rule.getCorpinfoName());
            fillLocalFence(rel);
            alarmRuleFenceRelRepository.save(rel);
        }
    }

    private void fillLocalFence(AlarmRuleFenceRelDO rel) {
        QueryWrapper<FenceDO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("finds_fence_id", rel.getFindsFenceId());
        queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
        queryWrapper.last("LIMIT 1");
        FenceDO fence = fenceRepository.getOne(queryWrapper, false);
        if (fence != null) {
            rel.setFenceId(fence.getId());
            rel.setFenceName(fence.getFenceName());
        }
    }

    private FenceDO findFirstScopeFence(JsonNode scope) {
        if (scope == null || scope.isMissingNode() || scope.isNull()) {
            return null;
        }
        for (String field : new String[]{"effectiveFence", "ignoreFence"}) {
            JsonNode ids = scope.path(field);
            if (!ids.isArray()) {
                continue;
            }
            for (JsonNode idNode : ids) {
                Long findsFenceId = longValue(idNode);
                if (findsFenceId == null) {
                    continue;
                }
                QueryWrapper<FenceDO> queryWrapper = new QueryWrapper<>();
                queryWrapper.eq("finds_fence_id", findsFenceId);
                queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
                queryWrapper.last("LIMIT 1");
                FenceDO fence = fenceRepository.getOne(queryWrapper, false);
                if (fence != null) {
                    return fence;
                }
            }
        }
        return null;
    }

    private void rebuildRuleItems(AlarmRuleDO rule, JsonNode items) {
        QueryWrapper<AlarmRuleItemDO> removeWrapper = new QueryWrapper<>();
        removeWrapper.eq("rule_id", rule.getId());
        alarmRuleItemRepository.remove(removeWrapper);
        if (!items.isArray()) {
            return;
        }
        for (JsonNode itemNode : items) {
            AlarmRuleItemDO item = new AlarmRuleItemDO();
            item.setRuleId(rule.getId());
            item.setCorpinfoId(rule.getCorpinfoId());
            item.setCorpinfoName(rule.getCorpinfoName());
            item.setLevel(integerValue(itemNode.path("level")));
            item.setEnable(booleanValue(itemNode.path("enable")) ? 1 : 0);
            JsonNode alarmParam = itemNode.path("alarmParam");
            item.setCrossDuration(longValue(alarmParam.path("crossDuration")));
            item.setRetentionMin(decimalValue(alarmParam.path("retentionMin")));
            item.setStayMin(integerValue(alarmParam.path("stayMin")));
            item.setStaticDuration(integerValue(alarmParam.path("staticDuration")));
            item.setOverSpeed(decimalValue(alarmParam.path("overSpeed")));
            item.setGlobalOverSpeed(decimalValue(alarmParam.path("globalOverSpeed")));
            item.setLowBattery(decimalValue(alarmParam.path("lowBattery")));
            item.setCrowdedNum(integerValue(alarmParam.path("crowdedNum")));
            item.setLackedNum(integerValue(alarmParam.path("lackedNum")));
            item.setClusterDuration(integerValue(alarmParam.path("clusterDuration")));
            item.setClusterQuantity(integerValue(alarmParam.path("clusterQuantity")));
            item.setClusterRadius(decimalValue(alarmParam.path("clusterRadius")));
            item.setCrashDistance(decimalValue(alarmParam.path("crashDistance")));
            item.setCrashSpeed(decimalValue(alarmParam.path("crashSpeed")));
            item.setAlarmParamJson(jsonValue(alarmParam));
            item.setAlarmActionJson(jsonValue(itemNode.path("alarmAction")));
            alarmRuleItemRepository.save(item);
        }
    }

    private List<OpenapiConfigDO> resolveConfigs(String requestedCorpinfoId) {
        SSOUser ssoUser = AuthContext.getCurrentUser();
        String corpinfoId = requestedCorpinfoId != null
                ? requestedCorpinfoId
                : (ssoUser == null || ssoUser.getCompanyId() == null
                ? null : String.valueOf(ssoUser.getCompanyId()));
        QueryWrapper<OpenapiConfigDO> queryWrapper = baseConfigQuery();
        if (corpinfoId != null) {
            queryWrapper.eq("corpinfo_id", corpinfoId);
        }
        List<OpenapiConfigDO> configs = openapiConfigRepository.list(queryWrapper);
        if ((configs == null || configs.isEmpty()) && corpinfoId != null) {
            QueryWrapper<OpenapiConfigDO> defaultWrapper = baseConfigQuery();
            defaultWrapper.and(wrapper -> wrapper.isNull("corpinfo_id").or().eq("corpinfo_id", "0"));
            configs = openapiConfigRepository.list(defaultWrapper);
            if (configs != null) {
                for (OpenapiConfigDO config : configs) {
                    config.setCorpinfoId(corpinfoId);
                }
            }
        }
        if (configs == null || configs.isEmpty()) {
            OpenapiConfigDO fallback = openapiConfigCache.getEnabledConfig(corpinfoId);
            if (fallback == null) {
                return Collections.emptyList();
            }
            if (corpinfoId != null && fallback.getCorpinfoId() == null) {
                fallback.setCorpinfoId(corpinfoId);
            }
            configs = Collections.singletonList(fallback);
        }
        return configs;
    }

    private boolean isPendingLocalSync(String status) {
        return FindsPendingSyncService.STATUS_WAIT.equals(status)
                || FindsPendingSyncService.STATUS_FAIL.equals(status)
                || FindsPendingSyncService.STATUS_DELETE_WAIT.equals(status)
                || FindsPendingSyncService.STATUS_DELETE_FAIL.equals(status);
    }

    private OpenapiConfigDO resolveConfigForRule(AlarmRuleDO rule) {
        List<OpenapiConfigDO> configs = resolveConfigs(rule.getCorpinfoId());
        return configs.isEmpty() ? null : configs.get(0);
    }

    private QueryWrapper<OpenapiConfigDO> baseConfigQuery() {
        QueryWrapper<OpenapiConfigDO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("enabled", 1);
        queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
        queryWrapper.orderByDesc("update_time");
        return queryWrapper;
    }

    private JsonNode assertSuccess(JsonNode root, String apiName) {
        if (root == null || !"200".equals(root.path("code").asText())) {
            String msg = root == null ? "" : root.path("msg").asText();
            String detail = root == null ? "" : root.path("detail").asText();
            throw new BizException("FindS接口调用失败：" + apiName + "，" + msg + " " + detail);
        }
        return root;
    }

    private JsonNode extractRows(JsonNode data) {
        if (data == null || data.isMissingNode() || data.isNull()) {
            return objectMapper.createArrayNode();
        }
        if (data.isArray()) {
            return data;
        }
        JsonNode direct = firstArray(data.path("data"), data.path("rows"), data.path("records"), data.path("list"));
        if (direct != null) {
            return direct;
        }
        JsonNode nested = firstArray(data.path("data").path("data"), data.path("data").path("rows"));
        return nested == null ? objectMapper.createArrayNode() : nested;
    }

    private JsonNode firstArray(JsonNode... nodes) {
        for (JsonNode node : nodes) {
            if (node != null && node.isArray()) {
                return node;
            }
        }
        return null;
    }

    private long totalValue(JsonNode data, long defaultValue) {
        for (String field : new String[]{"total", "totalCount", "count"}) {
            Long value = longValue(data.path(field));
            if (value != null) {
                return value;
            }
        }
        return defaultValue;
    }

    private String inferAlarmAttr(JsonNode scope) {
        if (scope != null && scope.path("effectiveFence").isArray() && scope.path("effectiveFence").size() > 0) {
            return AlarmRuleConfigPersistence.ATTR_FENCE;
        }
        return AlarmRuleConfigPersistence.ATTR_COMPANY_AREA;
    }

    private String joinTextArray(JsonNode node) {
        if (!node.isArray()) {
            return null;
        }
        List<String> values = new ArrayList<>();
        for (JsonNode item : node) {
            if (StringUtils.hasText(item.asText())) {
                values.add(item.asText());
            }
        }
        return values.stream().collect(Collectors.joining(","));
    }

    private String alarmNameOf(String alarmCode) {
        if ("CROSS_IN".equals(alarmCode)) {
            return "进入围栏告警";
        }
        if ("CROSS_OUT".equals(alarmCode)) {
            return "离开围栏告警";
        }
        if ("RETENTION".equals(alarmCode)) {
            return "超时停留告警";
        }
        if ("LACKED".equals(alarmCode)) {
            return "脱岗告警";
        }
        if ("STAY".equals(alarmCode)) {
            return "停留告警";
        }
        if ("CRASH".equals(alarmCode)) {
            return "碰撞告警";
        }
        return alarmCode;
    }

    private boolean booleanValue(JsonNode node) {
        return node == null || node.isMissingNode() || node.isNull() || node.asBoolean();
    }

    private Long longValue(JsonNode node) {
        if (node == null || node.isMissingNode() || node.isNull()) {
            return null;
        }
        if (node.isNumber()) {
            return node.asLong();
        }
        if (StringUtils.hasText(node.asText())) {
            try {
                return Long.valueOf(node.asText());
            } catch (NumberFormatException ignored) {
                return null;
            }
        }
        return null;
    }

    private Integer integerValue(JsonNode node) {
        Long value = longValue(node);
        return value == null ? null : value.intValue();
    }

    private BigDecimal decimalValue(JsonNode node) {
        if (node == null || node.isMissingNode() || node.isNull() || !StringUtils.hasText(node.asText())) {
            return null;
        }
        try {
            return new BigDecimal(node.asText());
        } catch (NumberFormatException ignored) {
            return null;
        }
    }

    private String jsonValue(JsonNode node) {
        if (node == null || node.isMissingNode() || node.isNull()) {
            return null;
        }
        try {
            return objectMapper.writeValueAsString(node);
        } catch (IOException e) {
            return null;
        }
    }

    private String firstText(JsonNode node, String defaultValue) {
        return node != null && !node.isMissingNode() && !node.isNull() && StringUtils.hasText(node.asText())
                ? node.asText()
                : defaultValue;
    }

    private String defaultIfBlank(String value, String defaultValue) {
        return StringUtils.hasText(value) ? value : defaultValue;
    }
}
