package com.njcn.gather.detection.sntp;

import org.springframework.stereotype.Service;

import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Arrays;

@Service
public class SntpPacketService {

    private static final int MIN_PACKET_LENGTH = 48;
    private static final int CLIENT_MODE = 3;
    private static final int SERVER_MODE = 4;
    // NTP纪元偏移量：2208988800秒（1900年到1970年的秒数差）
    private static final long NTP_EPOCH_OFFSET = 2208988800L;
    private static final ZoneId SHANGHAI_ZONE = ZoneId.of("Asia/Shanghai");
    private static final byte[] REFERENCE_ID = "LOCL".getBytes(StandardCharsets.US_ASCII);

    public SntpExchange parseRequest(byte[] request, InetSocketAddress clientAddress) {
        if (request == null || request.length < MIN_PACKET_LENGTH) {
            return null;
        }

        int mode = request[0] & 0x07;
        if (mode != CLIENT_MODE) {
            return null;
        }

        int version = (request[0] >> 3) & 0x07;
        byte[] clientTransmitTimestamp = Arrays.copyOfRange(request, 40, 48);
        Instant deviceInstant = fromNtpTimestamp(clientTransmitTimestamp);
        return new SntpExchange(clientAddress, version == 0 ? 3 : version, deviceInstant, clientTransmitTimestamp);
    }

    public byte[] buildResponse(SntpExchange exchange, Instant receiveInstant, Instant transmitInstant) {
        byte[] response = new byte[MIN_PACKET_LENGTH];
        int version = exchange.getVersion() == 0 ? 3 : exchange.getVersion();
        response[0] = (byte) ((version << 3) | SERVER_MODE);
        response[1] = 0x01;
        response[2] = 0x04;
        response[3] = (byte) 0xEC;
        System.arraycopy(REFERENCE_ID, 0, response, 12, REFERENCE_ID.length);

        byte[] receiveTimestamp = toNtpTimestamp(receiveInstant);
        byte[] transmitTimestamp = toNtpTimestamp(transmitInstant);

        System.arraycopy(receiveTimestamp, 0, response, 16, receiveTimestamp.length);
        System.arraycopy(exchange.getClientTransmitTimestamp(), 0, response, 24, exchange.getClientTransmitTimestamp().length);
        System.arraycopy(receiveTimestamp, 0, response, 32, receiveTimestamp.length);
        System.arraycopy(transmitTimestamp, 0, response, 40, transmitTimestamp.length);
        return response;
    }

    public SntpPushMessage toPushMessage(String deviceIp, Instant computerInstant, Instant deviceInstant) {
        long computerTimestampMs = computerInstant.toEpochMilli();
        long deviceTimestampMs = deviceInstant.toEpochMilli();
        return new SntpPushMessage(
                "sntp_time_update",
                deviceIp,
                formatShanghaiTime(computerInstant),
                formatShanghaiTime(deviceInstant),
                computerTimestampMs,
                deviceTimestampMs,
                computerTimestampMs - deviceTimestampMs
        );
    }

    public static byte[] toNtpTimestamp(Instant instant) {
        long ntpSeconds = instant.getEpochSecond() + NTP_EPOCH_OFFSET;
        long fraction = ((long) instant.getNano() << 32) / 1_000_000_000L;
        byte[] bytes = new byte[8];
        writeUnsignedInt(bytes, 0, ntpSeconds);
        writeUnsignedInt(bytes, 4, fraction);
        return bytes;
    }

    private static Instant fromNtpTimestamp(byte[] bytes) {
        long seconds = readUnsignedInt(bytes, 0);
        long fraction = readUnsignedInt(bytes, 4);
        long epochSeconds = seconds - NTP_EPOCH_OFFSET;
        long nanos = ((fraction * 1_000_000_000L) + 0x80000000L) >>> 32;
        if (nanos >= 1_000_000_000L) {
            epochSeconds += 1;
            nanos -= 1_000_000_000L;
        }
        return Instant.ofEpochSecond(epochSeconds, nanos);
    }

    private String formatShanghaiTime(Instant instant) {
        return LocalDateTime.ofInstant(instant, SHANGHAI_ZONE).toString().replace('T', ' ');
    }

    private static long readUnsignedInt(byte[] bytes, int offset) {
        return ((long) bytes[offset] & 0xFF) << 24
                | ((long) bytes[offset + 1] & 0xFF) << 16
                | ((long) bytes[offset + 2] & 0xFF) << 8
                | ((long) bytes[offset + 3] & 0xFF);
    }

    private static void writeUnsignedInt(byte[] target, int offset, long value) {
        target[offset] = (byte) ((value >>> 24) & 0xFF);
        target[offset + 1] = (byte) ((value >>> 16) & 0xFF);
        target[offset + 2] = (byte) ((value >>> 8) & 0xFF);
        target[offset + 3] = (byte) (value & 0xFF);
    }
}
