package study.saga;

import java.util.Map;
import java.util.HashMap;
import java.util.UUID;
import org.apache.seata.saga.engine.StateMachineEngine;
import org.apache.seata.saga.statelang.domain.StateMachineInstance;
import org.apache.seata.saga.statelang.domain.ExecutionStatus;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import static org.junit.jupiter.api.Assertions.*;

/** 条件、预期与原始观察须由公开 runner 实跑后才能形成证据。 */
public class SagaLabTest {
    private static SagaFixture fixture;
    private static ClassPathXmlApplicationContext context;
    private static StateMachineEngine engine;
    private static JdbcTemplate jdbc;
    private static InventoryService inventory;

    @BeforeAll
    static void setup() throws Exception {
        fixture = SagaFixture.start();
        System.setProperty("u38.saga.jdbc.url", fixture.bizUrl());
        System.setProperty("u38.saga.jdbc.user", fixture.user());
        System.setProperty("u38.saga.jdbc.password", fixture.password());
        context = new ClassPathXmlApplicationContext("saga-context.xml");
        engine = context.getBean("stateMachineEngine", StateMachineEngine.class);
        jdbc = context.getBean(JdbcTemplate.class);
        inventory = context.getBean(InventoryService.class);
    }

    @AfterAll
    static void cleanup() throws Exception {
        try { if (context != null) context.close(); }
        finally { if (fixture != null) fixture.close(); }
    }

    private static Map<String, Object> inputs(String id, boolean chargeFails, boolean releaseFails) {
        // Seata 2.5.0 inserts its business key into the supplied start parameters.
        return new HashMap<>(Map.of("orderKey", id, "chargeFail", Boolean.toString(chargeFails),
                "compensationFail", Boolean.toString(releaseFails)));
    }
    private static String id() { return UUID.randomUUID().toString().replace("-", ""); }
    private static String state(String id) {
        return jdbc.queryForObject("SELECT state FROM u38_saga_order WHERE id=?", String.class, id);
    }
    private static StateMachineInstance reload(String id) {
        return engine.getStateMachineConfig().getStateLogStore().getStateMachineInstance(id);
    }

    @Test
    void normalPathPersistsBusinessAndState() {
        String key = id();
        StateMachineInstance instance = engine.startWithBusinessKey("u38SagaObservation", null, key,
                inputs(key, false, false));
        assertEquals(ExecutionStatus.SU, instance.getStatus());
        assertEquals("CHARGED", state(key));
        assertEquals(ExecutionStatus.SU, reload(instance.getId()).getStatus());
        System.out.println("U38_SAGA normal=PASS");
    }

    @Test
    void failedCompensationRemainsVisibleThenManualRetryRestores() {
        String key = id();
        StateMachineInstance initial = engine.startWithBusinessKey("u38SagaObservation", null, key,
                inputs(key, true, true));
        assertEquals(ExecutionStatus.UN, initial.getStatus());
        assertEquals(ExecutionStatus.UN, initial.getCompensationStatus());
        assertEquals("RESERVED", state(key), "补偿失败应保留待恢复的业务副作用");
        inventory.reserve(Map.of("key", key));
        assertEquals("RESERVED", state(key), "重复预留必须保持同一状态");
        assertEquals(1, jdbc.queryForObject("SELECT COUNT(*) FROM u38_saga_order WHERE id=?", Integer.class, key));
        assertEquals(ExecutionStatus.UN, reload(initial.getId()).getCompensationStatus());
        System.out.println("U38_SAGA compensationFailure=PASS");

        StateMachineInstance recovered = engine.compensate(initial.getId(), inputs(key, true, false));
        assertEquals(ExecutionStatus.SU, recovered.getCompensationStatus());
        assertEquals("RELEASED", state(key));
        assertEquals(ExecutionStatus.SU, reload(initial.getId()).getCompensationStatus());
        inventory.release(Map.of("key", key, "fail", "false"));
        assertEquals("RELEASED", state(key), "重复补偿不得改变结果");
        assertEquals(ExecutionStatus.SU, engine.compensate(initial.getId(), inputs(key, true, false)).getCompensationStatus());
        assertThrows(IllegalStateException.class, () -> inventory.reserve(Map.of("key", key)),
                "已释放身份不能复用为新的预留");
        assertThrows(IllegalStateException.class, () -> inventory.charge(Map.of("key", key, "fail", "false")),
                "已释放预留不能被扣款");
        assertEquals("RELEASED", state(key));
        System.out.println("U38_SAGA manualRecovery=PASS");
    }
}
