package com.zcloud.personnel.positioning.command;

import com.alibaba.cola.exception.BizException;
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.FenceDO;
import com.zcloud.personnel.positioning.persistence.dataobject.OpenapiConfigDO;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.LinkedHashMap;
import java.util.Map;

@Component
@AllArgsConstructor
public class FenceFindsSyncService {
    private static final String CREATE_API = "finds.fence.create.v2";
    private static final String UPDATE_API = "finds.fence.update.v2";
    private static final String DELETE_API = "finds.fence.delete";

    private final FindsOpenApiClient findsOpenApiClient;
    private final OpenapiConfigCache openapiConfigCache;
    private final FindsSyncStateService findsSyncStateService;
    private final FindsEntityLookupService findsEntityLookupService;
    private final FencePointPersistenceService fencePointPersistenceService;
    private final ObjectMapper objectMapper;

    public void createOrUpdate(FenceDO fence) {
        OpenapiConfigDO config = enabledConfig(fence);
        if (fence.getFindsFenceId() == null) {
            Long existingId = findsEntityLookupService.findFenceId(config, fence);
            if (existingId != null) {
                fence.setFindsFenceId(existingId);
                findsSyncStateService.recordFenceMapping(fence.getId(), existingId, fence.getFindsFenceCode());
            }
        }
        assertCurrent(fence);
        JsonNode response = fence.getFindsFenceId() == null
                ? createWithRecovery(config, fence)
                : updateOrRecreate(config, fence);
        markSyncSuccess(fence, response);
    }

    public void create(FenceDO fence) {
        JsonNode response = createWithRecovery(enabledConfig(fence), fence);
        markSyncSuccess(fence, response);
    }

    public void update(FenceDO fence) {
        if (fence.getFindsFenceId() == null) {
            create(fence);
            return;
        }
        JsonNode response = updateOrRecreate(enabledConfig(fence), fence);
        markSyncSuccess(fence, response);
    }

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

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

    private Map<String, Object> buildSaveRequest(FenceDO fence, boolean create) {
        if (!StringUtils.hasText(fence.getFenceName())) {
            throw new BizException("Fence name is required");
        }
        Map<String, Object> request = new LinkedHashMap<>();
        request.put("name", fence.getFenceName());
        if (create) {
            request.put("bizName", StringUtils.hasText(fence.getFenceBizName())
                    ? fence.getFenceBizName()
                    : FencePersistenceSupport.DEFAULT_BIZ_NAME);
        }
        request.put("coor", StringUtils.hasText(fence.getCoor()) ? fence.getCoor() : FencePersistenceSupport.DEFAULT_COOR);
        request.put("silence", fence.getSilence() != null && fence.getSilence() == 1);
        request.put("polygon", fencePointPersistenceService.parsePolygonObject(fence.getPolygonJson()));
        putIfNotNull(request, "buildingId", fence.getBuildingId());
        putIfNotNull(request, "floor", fence.getFloor());
        if (StringUtils.hasText(fence.getSpaceType())) {
            request.put("spaceType", fence.getSpaceType());
        }
        Object effectPeriod = parseEffectPeriod(fence.getEffectPeriodJson());
        if (effectPeriod != null) {
            request.put("effectPeriod", effectPeriod);
        }
        putIfNotNull(request, "effectStart", toEpochMillis(fence.getEffectStart()));
        putIfNotNull(request, "effectEnd", toEpochMillis(fence.getEffectEnd()));
        return request;
    }

    private JsonNode createWithRecovery(OpenapiConfigDO config, FenceDO fence) {
        RuntimeException createError = null;
        try {
            JsonNode response = findsOpenApiClient.post(config, CREATE_API, buildSaveRequest(fence, true));
            if (isSuccess(response)) {
                applyResponseFence(fence, response);
                findsSyncStateService.recordFenceMapping(fence.getId(), fence.getFindsFenceId(), fence.getFindsFenceCode());
                return response;
            }
            createError = apiError(response, CREATE_API);
        } catch (RuntimeException e) {
            createError = e;
        }
        Long recoveredId = tryFindFenceId(config, fence);
        if (recoveredId == null) {
            throw createError;
        }
        fence.setFindsFenceId(recoveredId);
        findsSyncStateService.recordFenceMapping(fence.getId(), recoveredId, fence.getFindsFenceCode());
        assertCurrent(fence);
        return updateOrRecreate(config, fence, false);
    }

    private JsonNode updateOrRecreate(OpenapiConfigDO config, FenceDO fence) {
        return updateOrRecreate(config, fence, true);
    }

    private JsonNode updateOrRecreate(OpenapiConfigDO config, FenceDO fence, boolean recreateOnNotFound) {
        Long oldFindsId = fence.getFindsFenceId();
        Map<String, Object> request = buildSaveRequest(fence, false);
        request.put("id", oldFindsId);
        try {
            JsonNode response = findsOpenApiClient.post(config, UPDATE_API, request);
            if (!findsEntityLookupService.isNotFound(response)) {
                assertSuccess(response, UPDATE_API);
                applyResponseFence(fence, response);
                return response;
            }
        } catch (RuntimeException e) {
            if (!findsEntityLookupService.isNotFound(e)) {
                throw e;
            }
        }
        if (!findsSyncStateService.clearFenceMapping(fence, oldFindsId)) {
            throw new BizException("FindS fence synchronization snapshot is stale");
        }
        fence.setFindsFenceId(null);
        fence.setFindsFenceCode(null);
        if (!recreateOnNotFound) {
            throw new BizException("FindS fence recovery failed: recovered id does not exist");
        }
        return createWithRecovery(config, fence);
    }

    private void assertCurrent(FenceDO fence) {
        if (!findsSyncStateService.isCurrentFence(fence)) {
            throw new BizException("FindS fence synchronization snapshot is stale");
        }
    }

    private Long tryFindFenceId(OpenapiConfigDO config, FenceDO fence) {
        try {
            return findsEntityLookupService.findFenceId(config, fence);
        } catch (RuntimeException ignored) {
            return null;
        }
    }

    private Object parseEffectPeriod(String effectPeriodJson) {
        if (!StringUtils.hasText(effectPeriodJson)) {
            return null;
        }
        try {
            return objectMapper.readValue(effectPeriodJson, Object.class);
        } catch (IOException e) {
            throw new BizException("Fence effectPeriodJson is invalid");
        }
    }

    private Long toEpochMillis(LocalDateTime dateTime) {
        return dateTime == null ? null : dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
    }

    private void applyResponseFence(FenceDO fence, JsonNode response) {
        JsonNode data = response == null ? null : response.path("data");
        Long responseId = longValue(data);
        String responseCode = textValue(child(data, "code"));
        if (responseId == null && data != null && data.isObject()) {
            responseId = longValue(data.get("id"));
        }
        if (responseId != null) {
            fence.setFindsFenceId(responseId);
        }
        if (StringUtils.hasText(responseCode)) {
            fence.setFindsFenceCode(responseCode);
        }
        if (fence.getFindsFenceId() == null) {
            throw new BizException("FindS fence sync succeeded without fence id");
        }
    }

    private void markSyncSuccess(FenceDO fence, JsonNode response) {
        boolean current = findsSyncStateService.completeFence(fence, fence.getFindsFenceId(), fence.getFindsFenceCode(),
                response == null ? null : response.toString());
        if (current && fence.getAreaId() != null) {
            findsSyncStateService.recordAreaFenceMapping(fence.getAreaId(), fence.getId(), fence.getFindsFenceId());
        }
    }

    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 api failed: " + apiName + " " + msg + " " + detail);
    }

    private JsonNode child(JsonNode node, String fieldName) {
        return node == null || node.isMissingNode() || node.isNull() ? null : node.get(fieldName);
    }

    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 String textValue(JsonNode node) {
        if (node == null || node.isMissingNode() || node.isNull()) {
            return null;
        }
        String value = node.asText();
        return StringUtils.hasText(value) ? value : null;
    }

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