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.zcloud.personnel.positioning.integration.finds.FindsOpenApiClient;
import com.zcloud.personnel.positioning.integration.finds.OpenapiConfigCache;
import com.zcloud.personnel.positioning.persistence.dataobject.OpenapiConfigDO;
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalBindDO;
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalDO;
import com.zcloud.personnel.positioning.persistence.repository.TerminalBindRepository;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Component
@AllArgsConstructor
public class TerminalFindsSyncService {
    private static final String CREATE_API = "finds.terminal.create";
    private static final String UPDATE_API = "finds.terminal.update";
    private static final String DELETE_API = "finds.terminal.delete";
    private static final String BIND_API = "finds.track.terminal.bind";
    private static final String UNBIND_API = "finds.track.terminal.unbind";

    private final FindsOpenApiClient findsOpenApiClient;
    private final OpenapiConfigCache openapiConfigCache;
    private final TerminalBindRepository terminalBindRepository;
    private final FindsSyncStateService findsSyncStateService;
    private final FindsEntityLookupService findsEntityLookupService;

    public void createOrUpdate(TerminalDO terminal) {
        OpenapiConfigDO config = enabledConfig(terminal);
        if (terminal.getFindsTerminalId() == null) {
            Long existingId = findsEntityLookupService.findTerminalId(config, terminal);
            if (existingId != null) {
                terminal.setFindsTerminalId(existingId);
                findsSyncStateService.recordTerminalMapping(terminal.getId(), existingId);
            }
        }
        assertCurrent(terminal);
        JsonNode response = terminal.getFindsTerminalId() == null
                ? createWithRecovery(config, terminal)
                : updateOrRecreate(config, terminal);
        boolean current = findsSyncStateService.completeTerminal(terminal, terminal.getFindsTerminalId(), response.toString());
        if (current) {
            syncPendingBindings(terminal, config);
        }
    }

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

    public void syncPendingBindings(TerminalDO terminal) {
        syncPendingBindings(terminal, enabledConfig(terminal));
    }

    private void syncPendingBindings(TerminalDO terminal, OpenapiConfigDO config) {
        if (terminal.getFindsTerminalId() == null) {
            return;
        }
        QueryWrapper<TerminalBindDO> wrapper = new QueryWrapper<>();
        wrapper.eq("terminal_id", terminal.getId());
        wrapper.in("sync_status", FindsPendingSyncService.STATUS_WAIT, FindsPendingSyncService.STATUS_FAIL);
        wrapper.and(value -> value.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
        wrapper.orderByAsc("id");
        List<TerminalBindDO> bindings = terminalBindRepository.list(wrapper);
        for (TerminalBindDO binding : bindings) {
            try {
                Map<String, Object> request = buildBindingRequest(terminal, binding);
                String api = "UNBOUND".equalsIgnoreCase(binding.getBindStatus()) ? UNBIND_API : BIND_API;
                JsonNode response = findsOpenApiClient.post(config, api, request);
                assertSuccess(response, api);
                findsSyncStateService.completeTerminalBinding(binding, terminal.getFindsTerminalId());
            } catch (Exception e) {
                findsSyncStateService.failTerminalBinding(binding);
                break;
            }
        }
    }

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

    private JsonNode createWithRecovery(OpenapiConfigDO config, TerminalDO terminal) {
        RuntimeException createError = null;
        try {
            JsonNode response = findsOpenApiClient.post(config, CREATE_API, buildSaveRequest(terminal, false));
            if (isSuccess(response)) {
                applyCreatedTerminalId(terminal, response);
                findsSyncStateService.recordTerminalMapping(terminal.getId(), terminal.getFindsTerminalId());
                return response;
            }
            createError = apiError(response, CREATE_API);
        } catch (RuntimeException e) {
            createError = e;
        }
        Long recoveredId = tryFindTerminalId(config, terminal);
        if (recoveredId == null) {
            throw createError;
        }
        terminal.setFindsTerminalId(recoveredId);
        findsSyncStateService.recordTerminalMapping(terminal.getId(), recoveredId);
        assertCurrent(terminal);
        return updateOrRecreate(config, terminal, false);
    }

    private JsonNode updateOrRecreate(OpenapiConfigDO config, TerminalDO terminal) {
        return updateOrRecreate(config, terminal, true);
    }

    private JsonNode updateOrRecreate(OpenapiConfigDO config, TerminalDO terminal, boolean recreateOnNotFound) {
        Long oldFindsId = terminal.getFindsTerminalId();
        try {
            JsonNode response = findsOpenApiClient.post(config, UPDATE_API, buildSaveRequest(terminal, true));
            if (!findsEntityLookupService.isNotFound(response)) {
                assertSuccess(response, UPDATE_API);
                return response;
            }
        } catch (RuntimeException e) {
            if (!findsEntityLookupService.isNotFound(e)) {
                throw e;
            }
        }
        if (!findsSyncStateService.clearTerminalMapping(terminal, oldFindsId)) {
            throw new BizException("FindS terminal synchronization snapshot is stale");
        }
        terminal.setFindsTerminalId(null);
        if (!recreateOnNotFound) {
            throw new BizException("FindS terminal recovery failed: recovered id does not exist");
        }
        return createWithRecovery(config, terminal);
    }

    private void assertCurrent(TerminalDO terminal) {
        if (!findsSyncStateService.isCurrentTerminal(terminal)) {
            throw new BizException("FindS terminal synchronization snapshot is stale");
        }
    }

    private Long tryFindTerminalId(OpenapiConfigDO config, TerminalDO terminal) {
        try {
            return findsEntityLookupService.findTerminalId(config, terminal);
        } catch (RuntimeException ignored) {
            return null;
        }
    }

    private void applyCreatedTerminalId(TerminalDO terminal, JsonNode response) {
        Long findsTerminalId = responseId(response.path("data"));
        if (findsTerminalId == null) {
            throw new BizException("FindS终端创建成功但未返回终端ID");
        }
        terminal.setFindsTerminalId(findsTerminalId);
    }

    private Map<String, Object> buildSaveRequest(TerminalDO terminal, boolean update) {
        Map<String, Object> request = new LinkedHashMap<>();
        if (update) {
            request.put("id", terminal.getFindsTerminalId());
            request.put("terminalId", terminal.getFindsTerminalId());
        }
        putIfText(request, "terminalNo", terminal.getTerminalNo());
        putIfText(request, "name", terminal.getTerminalName());
        putIfText(request, "terminalName", terminal.getTerminalName());
        putIfText(request, "terminalModel", terminal.getTerminalModel());
        putIfText(request, "simCardNo", terminal.getSimCardNo());
        putIfText(request, "checkCode", terminal.getCheckCode());
        putIfText(request, "orgCode", terminal.getOrgCode());
        putIfText(request, "orgName", terminal.getOrgName());
        if ("APP".equalsIgnoreCase(terminal.getDeviceType())) {
            request.put("bizName", "手机APP定位");
        }
        request.put("enable", terminal.getEnable() == null || terminal.getEnable() != 0);
        return request;
    }

    private Map<String, Object> buildBindingRequest(TerminalDO terminal, TerminalBindDO binding) {
        Map<String, Object> request = new LinkedHashMap<>();
        request.put("trackType", StringUtils.hasText(binding.getTrackType()) ? binding.getTrackType() : "STAFF");
        putIfText(request, "terminalNo", terminal.getTerminalNo());
        request.put("terminalId", terminal.getFindsTerminalId());
        putIfText(request, "idCardNo", binding.getIdCardNo());
        putIfText(request, "staffNo", binding.getStaffNoSnapshot());
        putIfText(request, "staffName", binding.getStaffNameSnapshot());
        return request;
    }

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

    private boolean isSuccess(JsonNode response) {
        int code = response == null ? -1 : response.path("code").asInt(-1);
        return code == 200 || code == 0;
    }

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

    private Long responseId(JsonNode data) {
        if (data == null || data.isMissingNode() || data.isNull()) {
            return null;
        }
        if (data.isNumber()) {
            return data.asLong();
        }
        for (String field : new String[]{"id", "terminalId", "findsTerminalId"}) {
            JsonNode value = data.get(field);
            if (value != null && value.canConvertToLong()) {
                return value.asLong();
            }
            if (value != null && StringUtils.hasText(value.asText())) {
                try {
                    return Long.valueOf(value.asText());
                } catch (NumberFormatException ignored) {
                    // 当前字段不兼容时继续尝试下一个候选字段。
                }
            }
        }
        return null;
    }

    private void putIfText(Map<String, Object> request, String key, String value) {
        if (StringUtils.hasText(value)) {
            request.put(key, value);
        }
    }
}
