package com.zcloud.personnel.positioning.websocket;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.zcloud.personnel.positioning.dto.clientobject.BiPersonLocationCO;
import com.zcloud.personnel.positioning.integration.realtime.RealtimeLocationPublisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import javax.annotation.PostConstruct;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicLongArray;

/**
 * 前端联调用实时点位模拟器，仅在配置明确开启时生效。
 */
@Configuration(proxyBeanMethods = false)
@EnableScheduling
@ConditionalOnProperty(
        prefix = "personnel-positioning.websocket.location.mock",
        name = "enabled",
        havingValue = "true"
)
public class MockPersonLocationPublisher {
    private static final Logger LOGGER = LoggerFactory.getLogger(MockPersonLocationPublisher.class);
    private static final BigDecimal STEP = new BigDecimal("0.000030");
    private static final int TRACK_POINT_COUNT = 40;

    private final ObjectMapper objectMapper;
    private final ObjectProvider<RealtimeLocationPublisher> publishers;
    private final AtomicLong sequence = new AtomicLong();
    private final AtomicLongArray trackSequences = new AtomicLongArray(3);
    private final AtomicBoolean thirdPersonActivated = new AtomicBoolean();
    private final long newPersonDelayMs;
    private final long startedAtMs;

    public MockPersonLocationPublisher(ObjectMapper objectMapper,
                                       ObjectProvider<RealtimeLocationPublisher> publishers,
                                       @Value("${personnel-positioning.websocket.location.mock.new-person-delay-ms:8000}")
                                               long newPersonDelayMs) {
        this.objectMapper = objectMapper;
        this.publishers = publishers;
        this.newPersonDelayMs = Math.max(0, newPersonDelayMs);
        this.startedAtMs = System.currentTimeMillis();
    }

    @PostConstruct
    public void logEnabled() {
        LOGGER.warn("人员实时点位WebSocket模拟数据已开启，仅用于前端联调，新增人员延迟={}ms",
                newPersonDelayMs);
    }

    @Scheduled(
            initialDelayString = "${personnel-positioning.websocket.location.mock.initial-delay-ms:1000}",
            fixedRateString = "${personnel-positioning.websocket.location.mock.interval-ms:1000}"
    )
    public void publishNextLocation() {
        int personIndex = nextPersonIndex();
        long trackIndex = trackSequences.getAndIncrement(personIndex) % TRACK_POINT_COUNT;
        BiPersonLocationCO location = createPerson(personIndex, trackIndex);

        String payload = serialize(location);
        int deliveries = 0;
        for (RealtimeLocationPublisher publisher : publishers) {
            deliveries += publisher.publish(payload);
        }
        LOGGER.debug("模拟人员点位已推送，staffNo={}, terminalNo={}, websocketDeliveries={}",
                location.getStaffNo(), location.getTerminalNo(), deliveries);
    }

    private int nextPersonIndex() {
        if (!thirdPersonActivated.get()
                && System.currentTimeMillis() - startedAtMs >= newPersonDelayMs
                && thirdPersonActivated.compareAndSet(false, true)) {
            LOGGER.info("模拟新增人员开始推送，staffNo=CODEX-STAFF-003, terminalNo=CODEX-TERMINAL-003");
            return 2;
        }
        int personCount = thirdPersonActivated.get() ? 3 : 2;
        return (int) (sequence.getAndIncrement() % personCount);
    }

    private BiPersonLocationCO createPerson(int personIndex, long trackIndex) {
        if (personIndex == 0) {
            return createEastPortPerson(trackIndex);
        }
        if (personIndex == 1) {
            return createWestPortPerson(trackIndex);
        }
        return createHarborPerson(trackIndex);
    }

    private BiPersonLocationCO createEastPortPerson(long trackIndex) {
        BigDecimal offset = STEP.multiply(BigDecimal.valueOf(trackIndex));
        BiPersonLocationCO location = baseLocation(
                "Codex测试人员A", "CODEX-STAFF-001", "CODEX-TERMINAL-001");
        location.setCompanyName("Codex测试东港公司");
        location.setCorpinfoId("CODEX-CORP-EAST");
        location.setCorpinfoName("Codex测试东港公司");
        location.setOrgCode("CODEX-ORG-EAST");
        location.setOrgName("Codex测试东港组织");
        location.setPortArea(2);
        location.setCurrentLocation("东港区模拟路线");
        location.setLon(new BigDecimal("119.604000").add(offset));
        location.setLat(new BigDecimal("39.925000").add(offset.divide(BigDecimal.valueOf(2))));
        location.setDirection(new BigDecimal("45"));
        return location;
    }

    private BiPersonLocationCO createWestPortPerson(long trackIndex) {
        BigDecimal offset = STEP.multiply(BigDecimal.valueOf(trackIndex));
        BiPersonLocationCO location = baseLocation(
                "Codex测试人员B", "CODEX-STAFF-002", "CODEX-TERMINAL-002");
        location.setCompanyName("Codex测试西港公司");
        location.setCorpinfoId("CODEX-CORP-WEST");
        location.setCorpinfoName("Codex测试西港公司");
        location.setOrgCode("CODEX-ORG-WEST");
        location.setOrgName("Codex测试西港组织");
        location.setPortArea(3);
        location.setCurrentLocation("西港区模拟路线");
        location.setLon(new BigDecimal("119.552000").subtract(offset));
        location.setLat(new BigDecimal("39.913000").add(offset.divide(BigDecimal.valueOf(3))));
        location.setDirection(new BigDecimal("315"));
        return location;
    }

    private BiPersonLocationCO createHarborPerson(long trackIndex) {
        BigDecimal offset = STEP.multiply(BigDecimal.valueOf(trackIndex));
        BiPersonLocationCO location = baseLocation(
                "Codex测试新增人员C", "CODEX-STAFF-003", "CODEX-TERMINAL-003");
        location.setCompanyName("Codex测试港区公司");
        location.setCorpinfoId("CODEX-CORP-HARBOR");
        location.setCorpinfoName("Codex测试港区公司");
        location.setOrgCode("CODEX-ORG-HARBOR");
        location.setOrgName("Codex测试港区组织");
        location.setPortArea(1);
        location.setCurrentLocation("秦皇岛港区模拟路线");
        location.setLon(new BigDecimal("119.580000").add(offset));
        location.setLat(new BigDecimal("39.920000").subtract(offset.divide(BigDecimal.valueOf(2))));
        location.setDirection(new BigDecimal("135"));
        return location;
    }

    private BiPersonLocationCO baseLocation(String staffName, String staffNo, String terminalNo) {
        BiPersonLocationCO location = new BiPersonLocationCO();
        location.setStaffName(staffName);
        location.setLocalStaffNo(staffNo);
        location.setStaffNo(staffNo);
        location.setTerminalNo(terminalNo);
        location.setPositionMode("模拟定位卡");
        location.setLastLocationTime(System.currentTimeMillis());
        location.setAlt(new BigDecimal("5.0"));
        location.setSpeed(new BigDecimal("1.2"));
        location.setOnline(true);
        location.setAlarmStatus(false);
        location.setAlarmCount(0);
        return location;
    }

    private String serialize(BiPersonLocationCO location) {
        try {
            ObjectNode payload = objectMapper.valueToTree(location);
            List<String> nullFields = new ArrayList<>();
            Iterator<Map.Entry<String, JsonNode>> fields = payload.fields();
            while (fields.hasNext()) {
                Map.Entry<String, JsonNode> field = fields.next();
                if (field.getValue().isNull()) {
                    nullFields.add(field.getKey());
                }
            }
            payload.remove(nullFields);
            return objectMapper.writeValueAsString(payload);
        } catch (JsonProcessingException e) {
            throw new IllegalStateException("模拟人员点位序列化失败", e);
        }
    }
}
