package com.zcloud.personnel.positioning.command;

import com.alibaba.cola.exception.BizException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
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.OpenapiConfigDO;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleFenceRelRepository;
import com.zcloud.personnel.positioning.persistence.repository.AlarmRuleItemRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Synchronizes local alarm rule configuration to FindS.
 *
 * @Author fangjiakai
 * @Date 2026-07-08
 */
@Component
@AllArgsConstructor
public class AlarmRuleFindsSyncService {
    private static final String CREATE_API = "finds.alarm.rule.create";
    private static final String UPDATE_API = "finds.alarm.rule.update";
    private static final String DELETE_API = "finds.alarm.rule.delete";
    private static final String REL_IGNORE = "IGNORE";

    private final FindsOpenApiClient findsOpenApiClient;
    private final OpenapiConfigCache openapiConfigCache;
    private final FindsSyncStateService findsSyncStateService;
    private final AlarmRuleFenceRelRepository alarmRuleFenceRelRepository;
    private final AlarmRuleItemRepository alarmRuleItemRepository;
    private final ObjectMapper objectMapper;
    private final FindsEntityLookupService findsEntityLookupService;

    public void create(AlarmRuleDO rule) {
        JsonNode response = createWithRecovery(enabledConfig(rule), rule);
        markSyncSuccess(rule, response);
    }

    public void createOrUpdate(AlarmRuleDO rule) {
        OpenapiConfigDO config = enabledConfig(rule);
        if (rule.getFindsRuleId() == null) {
            Long existingId = findsEntityLookupService.findAlarmRuleId(config, rule);
            if (existingId != null) {
                rule.setFindsRuleId(existingId);
                findsSyncStateService.recordAlarmRuleMapping(rule.getId(), existingId);
            }
        }
        assertCurrent(rule);
        JsonNode response = rule.getFindsRuleId() == null
                ? createWithRecovery(config, rule)
                : updateOrRecreate(config, rule);
        markSyncSuccess(rule, response);
    }

    public void delete(AlarmRuleDO rule) {
        if (rule == null || rule.getFindsRuleId() == null) {
            return;
        }
        Map<String, Object> request = new LinkedHashMap<>();
        request.put("id", String.valueOf(rule.getFindsRuleId()));
        try {
            JsonNode response = findsOpenApiClient.post(enabledConfig(rule), DELETE_API, request);
            if (!findsEntityLookupService.isNotFound(response)) {
                assertSuccess(response, DELETE_API);
            }
        } catch (RuntimeException e) {
            if (!findsEntityLookupService.isNotFound(e)) {
                throw e;
            }
        }
    }

    private OpenapiConfigDO enabledConfig(AlarmRuleDO rule) {
        OpenapiConfigDO config = openapiConfigCache.getEnabledConfig(rule.getCorpinfoId());
        if (config == null) {
            throw new BizException("未配置启用的FindS OpenAPI配置");
        }
        return config;
    }

    private JsonNode createWithRecovery(OpenapiConfigDO config, AlarmRuleDO rule) {
        RuntimeException createError = null;
        try {
            JsonNode response = findsOpenApiClient.post(config, CREATE_API, buildSaveRequest(rule, true));
            if (isSuccess(response)) {
                applyCreatedRuleId(rule, response);
                findsSyncStateService.recordAlarmRuleMapping(rule.getId(), rule.getFindsRuleId());
                return response;
            }
            createError = apiError(response, CREATE_API);
        } catch (RuntimeException e) {
            createError = e;
        }
        Long recoveredId = tryFindAlarmRuleId(config, rule);
        if (recoveredId == null) {
            throw createError;
        }
        rule.setFindsRuleId(recoveredId);
        findsSyncStateService.recordAlarmRuleMapping(rule.getId(), recoveredId);
        assertCurrent(rule);
        return updateOrRecreate(config, rule, false);
    }

    private JsonNode updateOrRecreate(OpenapiConfigDO config, AlarmRuleDO rule) {
        return updateOrRecreate(config, rule, true);
    }

    private JsonNode updateOrRecreate(OpenapiConfigDO config, AlarmRuleDO rule, boolean recreateOnNotFound) {
        Long oldFindsId = rule.getFindsRuleId();
        Map<String, Object> request = buildSaveRequest(rule, false);
        request.put("id", oldFindsId);
        try {
            JsonNode response = findsOpenApiClient.post(config, UPDATE_API, request);
            if (!findsEntityLookupService.isNotFound(response)) {
                assertSuccess(response, UPDATE_API);
                Long responseRuleId = responseId(response.path("data"));
                if (responseRuleId != null) {
                    rule.setFindsRuleId(responseRuleId);
                }
                return response;
            }
        } catch (RuntimeException e) {
            if (!findsEntityLookupService.isNotFound(e)) {
                throw e;
            }
        }
        if (!findsSyncStateService.clearAlarmRuleMapping(rule, oldFindsId)) {
            throw new BizException("FindS alarm rule synchronization snapshot is stale");
        }
        rule.setFindsRuleId(null);
        if (!recreateOnNotFound) {
            throw new BizException("FindS alarm rule recovery failed: recovered id does not exist");
        }
        return createWithRecovery(config, rule);
    }

    private void assertCurrent(AlarmRuleDO rule) {
        if (!findsSyncStateService.isCurrentAlarmRule(rule)) {
            throw new BizException("FindS alarm rule synchronization snapshot is stale");
        }
    }

    private Long tryFindAlarmRuleId(OpenapiConfigDO config, AlarmRuleDO rule) {
        try {
            return findsEntityLookupService.findAlarmRuleId(config, rule);
        } catch (RuntimeException ignored) {
            return null;
        }
    }

    private void applyCreatedRuleId(AlarmRuleDO rule, JsonNode response) {
        Long findsRuleId = responseId(response.path("data"));
        if (findsRuleId == null) {
            throw new BizException("FindS告警规则创建成功但未返回规则ID");
        }
        rule.setFindsRuleId(findsRuleId);
    }

    private Map<String, Object> buildSaveRequest(AlarmRuleDO rule, boolean includeAlarmCode) {
        if (!StringUtils.hasText(rule.getRuleName())) {
            throw new BizException("同步FindS失败：告警规则名称不能为空");
        }
        Map<String, Object> request = new LinkedHashMap<>();
        request.put("name", rule.getRuleName());
        request.put("enable", enabled(rule.getEnable()));
        if (includeAlarmCode) {
            if (!StringUtils.hasText(rule.getAlarmCode())) {
                throw new BizException("同步FindS失败：告警类型不能为空");
            }
            request.put("alarmCode", rule.getAlarmCode());
        }
        request.put("trackTypes", parseTrackTypes(rule.getTrackTypes()));
        request.put("fenceEffectScope", buildFenceEffectScope(rule));
        request.put("ruleItems", buildRuleItems(rule));
        if (StringUtils.hasText(rule.getCrashType())) {
            request.put("crashType", rule.getCrashType());
        }
        return request;
    }

    private Map<String, Object> buildFenceEffectScope(AlarmRuleDO rule) {
        String strategy = StringUtils.hasText(rule.getFenceEffectStrategy())
                ? rule.getFenceEffectStrategy()
                : AlarmRuleConfigPersistence.STRATEGY_SPECIFIED;
        Map<String, Object> scope = new LinkedHashMap<>();
        scope.put("strategy", strategy);
        if (!AlarmRuleConfigPersistence.STRATEGY_SPECIFIED.equals(strategy)) {
            return scope;
        }
        List<AlarmRuleFenceRelDO> rels = listFenceRelations(rule.getId());
        List<Long> effectiveFence = new ArrayList<>();
        List<Long> ignoreFence = new ArrayList<>();
        for (AlarmRuleFenceRelDO rel : rels) {
            if (rel.getFindsFenceId() == null) {
                throw new BizException("同步FindS失败：关联围栏未同步到FindS，围栏：" + rel.getFenceName());
            }
            if (REL_IGNORE.equals(rel.getRelType())) {
                ignoreFence.add(rel.getFindsFenceId());
            } else {
                effectiveFence.add(rel.getFindsFenceId());
            }
        }
        if (effectiveFence.isEmpty() && ignoreFence.isEmpty()) {
            throw new BizException("同步FindS失败：未找到可同步的关联围栏");
        }
        if (!effectiveFence.isEmpty()) {
            scope.put("effectiveFence", effectiveFence);
        }
        if (!ignoreFence.isEmpty()) {
            scope.put("ignoreFence", ignoreFence);
        }
        return scope;
    }

    private List<Map<String, Object>> buildRuleItems(AlarmRuleDO rule) {
        List<AlarmRuleItemDO> items = listRuleItems(rule.getId());
        List<Map<String, Object>> result = new ArrayList<>();
        if (items.isEmpty()) {
            Map<String, Object> item = new LinkedHashMap<>();
            item.put("level", 1);
            item.put("enable", enabled(rule.getEnable()));
            item.put("alarmParam", new LinkedHashMap<String, Object>());
            result.add(item);
            return result;
        }
        for (AlarmRuleItemDO itemDO : items) {
            Map<String, Object> item = new LinkedHashMap<>();
            item.put("level", itemDO.getLevel() == null ? 1 : itemDO.getLevel());
            item.put("enable", itemDO.getEnable() == null ? enabled(rule.getEnable()) : enabled(itemDO.getEnable()));
            item.put("alarmParam", buildAlarmParam(itemDO));
            Map<String, Object> alarmAction = readJsonMap(itemDO.getAlarmActionJson(), "alarmActionJson");
            if (!alarmAction.isEmpty()) {
                item.put("alarmAction", alarmAction);
            }
            result.add(item);
        }
        return result;
    }

    private Map<String, Object> buildAlarmParam(AlarmRuleItemDO item) {
        Map<String, Object> alarmParam = readJsonMap(item.getAlarmParamJson(), "alarmParamJson");
        putIfNotNull(alarmParam, "crossDuration", item.getCrossDuration());
        putIfNotNull(alarmParam, "retentionMin", item.getRetentionMin());
        putIfNotNull(alarmParam, "stayMin", item.getStayMin());
        putIfNotNull(alarmParam, "staticDuration", item.getStaticDuration());
        putIfNotNull(alarmParam, "overSpeed", item.getOverSpeed());
        putIfNotNull(alarmParam, "globalOverSpeed", item.getGlobalOverSpeed());
        putIfNotNull(alarmParam, "lowBattery", item.getLowBattery());
        putIfNotNull(alarmParam, "crowdedNum", item.getCrowdedNum());
        putIfNotNull(alarmParam, "lackedNum", item.getLackedNum());
        putIfNotNull(alarmParam, "clusterDuration", item.getClusterDuration());
        putIfNotNull(alarmParam, "clusterQuantity", item.getClusterQuantity());
        putIfNotNull(alarmParam, "clusterRadius", item.getClusterRadius());
        putIfNotNull(alarmParam, "crashDistance", item.getCrashDistance());
        putIfNotNull(alarmParam, "crashSpeed", item.getCrashSpeed());
        return alarmParam;
    }

    private List<AlarmRuleFenceRelDO> listFenceRelations(Long ruleId) {
        QueryWrapper<AlarmRuleFenceRelDO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("rule_id", ruleId);
        queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
        queryWrapper.orderByAsc("id");
        return alarmRuleFenceRelRepository.list(queryWrapper);
    }

    private List<AlarmRuleItemDO> listRuleItems(Long ruleId) {
        QueryWrapper<AlarmRuleItemDO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("rule_id", ruleId);
        queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
        queryWrapper.orderByAsc("level", "id");
        return alarmRuleItemRepository.list(queryWrapper);
    }

    private Map<String, Object> readJsonMap(String json, String fieldName) {
        if (!StringUtils.hasText(json)) {
            return new LinkedHashMap<>();
        }
        try {
            return objectMapper.readValue(json, new TypeReference<LinkedHashMap<String, Object>>() {
            });
        } catch (IOException e) {
            throw new BizException("同步FindS失败：" + fieldName + "不是合法JSON对象");
        }
    }

    private List<String> parseTrackTypes(String trackTypes) {
        if (!StringUtils.hasText(trackTypes)) {
            return new ArrayList<>(Arrays.asList("STAFF"));
        }
        List<String> result = new ArrayList<>();
        for (String item : trackTypes.split(",")) {
            String value = item.trim();
            if (StringUtils.hasText(value)) {
                result.add(value);
            }
        }
        if (result.isEmpty()) {
            result.add("STAFF");
        }
        return result;
    }

    private void assertSuccess(JsonNode response, String apiName) {
        if (!isSuccess(response)) {
            throw apiError(response, apiName);
        }
    }

    private boolean isSuccess(JsonNode response) {
        String code = response == null ? null : response.path("code").asText();
        return "200".equals(code) || "0".equals(code);
    }

    private BizException apiError(JsonNode response, String apiName) {
        String msg = response == null ? "" : response.path("msg").asText();
        String detail = response == null ? "" : response.path("detail").asText();
        return new BizException("FindS接口调用失败：" + apiName + "，" + msg + " " + detail);
    }

    private void markSyncSuccess(AlarmRuleDO rule, JsonNode response) {
        findsSyncStateService.completeAlarmRule(rule, rule.getFindsRuleId(), response.toString());
    }

    private void putIfNotNull(Map<String, Object> target, String key, Object value) {
        if (value != null) {
            target.put(key, value);
        }
    }

    private boolean enabled(Integer enable) {
        return enable == null || enable != 0;
    }

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

    private Long responseId(JsonNode data) {
        Long direct = longValue(data);
        if (direct != null || data == null || !data.isObject()) {
            return direct;
        }
        for (String field : new String[]{"id", "ruleId", "findsRuleId"}) {
            Long value = longValue(data.get(field));
            if (value != null) {
                return value;
            }
        }
        return null;
    }
}
