package com.agent.management.engine;

import org.junit.jupiter.api.Test;

import java.util.LinkedHashMap;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

class NodeOutputEnvelopeTest {

    @Test
    void successBuildsWithStatus200AndProvidedData() {
        Map<String, Object> data = new LinkedHashMap<>();
        data.put("output", "hello");
        data.put("elapsed_time", 15.83);

        NodeOutputEnvelope env = NodeOutputEnvelope.success("LLM 调用成功", data);

        assertThat(env.getStatus()).isEqualTo(200);
        assertThat(env.getMessage()).isEqualTo("LLM 调用成功");
        assertThat(env.getData()).containsEntry("output", "hello").containsEntry("elapsed_time", 15.83);
        assertThat(env.isSuccess()).isTrue();
        assertThat(env.isFailure()).isFalse();
    }

    @Test
    void failureBuildsWithStatus400AndEmptyData() {
        NodeOutputEnvelope env = NodeOutputEnvelope.failure("LLM 调用失败：超时");

        assertThat(env.getStatus()).isEqualTo(400);
        assertThat(env.getMessage()).isEqualTo("LLM 调用失败：超时");
        assertThat(env.getData()).isEmpty();
        assertThat(env.isFailure()).isTrue();
        assertThat(env.isSuccess()).isFalse();
    }

    @Test
    void toMapProducesEnvelopeStructure() {
        NodeOutputEnvelope env = NodeOutputEnvelope.success("ok", Map.of("k", "v"));

        Map<String, Object> map = env.toMap();

        assertThat(map).containsOnlyKeys("status", "message", "data");
        assertThat(map.get("status")).isEqualTo(200);
        assertThat(map.get("message")).isEqualTo("ok");
        assertThat(map.get("data")).isInstanceOf(Map.class);
        @SuppressWarnings("unchecked")
        Map<String, Object> dataMap = (Map<String, Object>) map.get("data");
        assertThat(dataMap).containsEntry("k", "v");
    }

    @Test
    void toMapDataCopyIsMutableWithoutAffectingEnvelope() {
        NodeOutputEnvelope env = NodeOutputEnvelope.success("ok", Map.of("k", "v"));
        Map<String, Object> map = env.toMap();
        @SuppressWarnings("unchecked")
        Map<String, Object> data = (Map<String, Object>) map.get("data");
        data.put("new", "value");

        assertThat(env.getData()).doesNotContainKey("new");
    }

    @Test
    void fromObjectParsesEnvelopeMap() {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("status", 400);
        map.put("message", "失败");
        map.put("data", Map.of("reason", "parse error"));

        NodeOutputEnvelope env = NodeOutputEnvelope.fromObject(map);

        assertThat(env).isNotNull();
        assertThat(env.getStatus()).isEqualTo(400);
        assertThat(env.getMessage()).isEqualTo("失败");
        assertThat(env.getData()).containsEntry("reason", "parse error");
    }

    @Test
    void fromObjectAcceptsStringStatusField() {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("status", "200");
        map.put("data", Map.of("k", "v"));

        NodeOutputEnvelope env = NodeOutputEnvelope.fromObject(map);

        assertThat(env).isNotNull();
        assertThat(env.getStatus()).isEqualTo(200);
    }

    @Test
    void fromObjectReturnsNullForNonEnvelopeMap() {
        Map<String, Object> flatOutput = Map.of("output", "hello", "elapsed_time", 15.83);

        assertThat(NodeOutputEnvelope.fromObject(flatOutput)).isNull();
    }

    @Test
    void fromObjectReturnsNullForNullAndPrimitives() {
        assertThat(NodeOutputEnvelope.fromObject(null)).isNull();
        assertThat(NodeOutputEnvelope.fromObject("string")).isNull();
        assertThat(NodeOutputEnvelope.fromObject(42)).isNull();
    }

    @Test
    void fromObjectHandlesNullDataField() {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("status", 200);
        map.put("message", "ok");
        map.put("data", null);

        NodeOutputEnvelope env = NodeOutputEnvelope.fromObject(map);

        assertThat(env).isNotNull();
        assertThat(env.getData()).isEmpty();
    }

    @Test
    void fromObjectHandlesNonMapDataField() {
        Map<String, Object> map = new LinkedHashMap<>();
        map.put("status", 200);
        map.put("data", "not a map");

        NodeOutputEnvelope env = NodeOutputEnvelope.fromObject(map);

        assertThat(env).isNotNull();
        assertThat(env.getData()).isEmpty();
    }

    @Test
    void isEnvelopeDetectsMapWithStatusAndData() {
        assertThat(NodeOutputEnvelope.isEnvelope(Map.of("status", 200, "data", Map.of()))).isTrue();
        assertThat(NodeOutputEnvelope.isEnvelope(Map.of("status", 400, "data", Map.of(), "extra", 1))).isTrue();
    }

    @Test
    void isEnvelopeRejectsFlatMapAndPrimitives() {
        assertThat(NodeOutputEnvelope.isEnvelope(null)).isFalse();
        assertThat(NodeOutputEnvelope.isEnvelope("string")).isFalse();
        assertThat(NodeOutputEnvelope.isEnvelope(42)).isFalse();
        assertThat(NodeOutputEnvelope.isEnvelope(Map.of("output", "hello"))).isFalse();
        assertThat(NodeOutputEnvelope.isEnvelope(Map.of("status", 200))).isFalse();   // 缺 data
        assertThat(NodeOutputEnvelope.isEnvelope(Map.of("data", Map.of()))).isFalse(); // 缺 status
    }

    @Test
    void dataIsUnmodifiableAfterConstruction() {
        NodeOutputEnvelope env = NodeOutputEnvelope.success("ok", new LinkedHashMap<>(Map.of("k", "v")));

        org.junit.jupiter.api.Assertions.assertThrows(
                UnsupportedOperationException.class,
                () -> env.getData().put("new", "value")
        );
    }

    @Test
    void successWithNullDataDefaultsToEmptyMap() {
        NodeOutputEnvelope env = NodeOutputEnvelope.success("ok", null);
        assertThat(env.getData()).isEmpty();
    }

    @Test
    void ofAllowsCustomStatus() {
        NodeOutputEnvelope env = NodeOutputEnvelope.of(500, "partial", Map.of("k", "v"));
        assertThat(env.getStatus()).isEqualTo(500);
        assertThat(env.getMessage()).isEqualTo("partial");
    }
}
