package study.iot.command;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.time.Instant;
import java.util.Map;

/**
 * 跨语言接口契约：命令与影子消息的 JSON 编解码。
 *
 * <p>契约版本 v1 的字段（Java 与 Python 必须逐字一致）：</p>
 * <pre>
 * command: { "schemaVersion": 1, "commandId": "...", "deviceId": "...", "type": "...",
 *            "payload": "...", "createdAt": "RFC3339", "expireAt": "RFC3339" }
 * ack:     { "schemaVersion": 1, "commandId": "...", "deviceId": "...", "success": true,
 *            "reported": { ... } }
 * </pre>
 *
 * <p>时间统一用 UTC 的 RFC 3339（`Instant.toString()` / Python `datetime.isoformat()`），
 * 不接受本地时区字符串；未知字段在解析时忽略，缺失必需字段直接拒绝。</p>
 */
public final class Contract {

    public static final int SCHEMA_VERSION = 1;

    private Contract() {
    }

    public static ObjectNode encodeCommand(Command command) {
        ObjectNode node = Shadow.MAPPER.createObjectNode();
        node.put("schemaVersion", SCHEMA_VERSION);
        node.put("commandId", command.commandId());
        node.put("deviceId", command.deviceId());
        node.put("type", command.type());
        node.put("payload", command.payload());
        node.put("createdAt", CommandService.contractTime(command.createdAt()));
        node.put("expireAt", CommandService.contractTime(command.expireAt()));
        return node;
    }

    public static Command decodeCommand(String json) {
        try {
            JsonNode node = Shadow.MAPPER.readTree(json);
            if (node.path("schemaVersion").asInt() != SCHEMA_VERSION) {
                throw new IllegalArgumentException("unsupported schemaVersion");
            }
            return new Command(
                    required(node, "commandId"),
                    required(node, "deviceId"),
                    required(node, "type"),
                    required(node, "payload"),
                    Instant.parse(required(node, "createdAt")),
                    Instant.parse(required(node, "expireAt")));
        } catch (com.fasterxml.jackson.core.JsonProcessingException e) {
            throw new IllegalArgumentException("malformed command JSON: " + e.getMessage(), e);
        }
    }

    public static ObjectNode encodeAck(String commandId, String deviceId, boolean success,
                                       Map<String, Object> reported) {
        ObjectNode node = Shadow.MAPPER.createObjectNode();
        node.put("schemaVersion", SCHEMA_VERSION);
        node.put("commandId", commandId);
        node.put("deviceId", deviceId);
        node.put("success", success);
        node.set("reported", Shadow.MAPPER.valueToTree(reported));
        return node;
    }

    public record Ack(String commandId, String deviceId, boolean success, JsonNode reported) {
    }

    public static Ack decodeAck(String json) {
        try {
            JsonNode node = Shadow.MAPPER.readTree(json);
            if (node.path("schemaVersion").asInt() != SCHEMA_VERSION) {
                throw new IllegalArgumentException("unsupported schemaVersion");
            }
            if (!node.path("success").isBoolean()) {
                throw new IllegalArgumentException("missing boolean success");
            }
            return new Ack(required(node, "commandId"), required(node, "deviceId"),
                    node.get("success").asBoolean(), node.path("reported"));
        } catch (com.fasterxml.jackson.core.JsonProcessingException e) {
            throw new IllegalArgumentException("malformed ack JSON: " + e.getMessage(), e);
        }
    }

    private static String required(JsonNode node, String field) {
        JsonNode value = node.get(field);
        if (value == null || value.isNull() || value.asText().isBlank()) {
            throw new IllegalArgumentException("missing required field " + field);
        }
        return value.asText();
    }
}
