package study.orderflow;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;

import org.junit.jupiter.api.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.MapPropertySource;

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

/// 配置失败启动（O01）与坏数据源探针（O02 另一半）：
/// 独立短命上下文，不监听 Web 端口（web=none），失败即退出本用例。
class StartupFailureTest {

    @Test
    void missingRequiredConfig_failsStartup() {
        // 缺 case.order.tenant：@Validated @NotBlank 绑定校验在启动阶段失败
        System.setProperty("case.hs256.secret", LabFixtures.randomHs256Secret());
        try {
            SpringApplication app = new SpringApplication(OrderFlowApp.class);
            app.setWebApplicationType(org.springframework.boot.WebApplicationType.NONE);
            // web=NONE 下没有 HttpSecurity，先排除安全自动配置，让"缺 case.order.tenant"成为首个失败面
            app.run("--spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mysql?useSSL=false&allowPublicKeyRetrieval=true",
                    "--spring.datasource.username=root",
                    "--spring.datasource.password=" + System.getenv().getOrDefault("CASE_DB_PASSWORD", ""),
                    "--spring.autoconfigure.exclude=org.springframework.boot.security.servlet.SecurityAutoConfiguration,"
                            + "org.springframework.boot.security.oauth2.server.servlet.OAuth2ResourceServerAutoConfiguration,"
                            + "org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration",
                    "--case.security.issuer=" + LabFixtures.ISSUER,
                    "--case.security.audience=" + LabFixtures.AUDIENCE,
                    "--case.order.max-items-per-order=3",
                    "--case.order.stock-warn-threshold=2",
                    // 包内 application.properties 有 tenant=t1；命令行显式置空模拟"必填项缺失/非法"
                    "--case.order.tenant="
            ).close();
            throw new AssertionError("blank tenant should fail startup");
        } catch (Exception expected) {
            // 失败链：BeanCreationException → BindException；根因包含绑定失败对象名
            StringBuilder chain = new StringBuilder();
            for (Throwable current = expected; current != null; current = current.getCause()) {
                chain.append(current.getClass().getSimpleName()).append(':').append(current.getMessage()).append('\n');
            }
            String message = chain.toString();
            assertThat(message).containsIgnoringCase("Could not bind properties");
            assertThat(message).containsIgnoringCase("case.order");
            System.out.println("U79 missingConfigFail=PASS bindsValidationAtStartup=true");
        } finally {
            System.clearProperty("case.hs256.secret");
        }
    }

    @Test
    void badDataSource_healthReportsDown() throws Exception {
        // 坏 URL 数据源：上下文可启动（懒连接池），readiness 判 DOWN —— 监听端口不冒充可服务
        System.setProperty("case.hs256.secret", LabFixtures.randomHs256Secret());
        Map<String, Object> props = new java.util.LinkedHashMap<>();
        props.put("server.port", "0");
        props.put("spring.datasource.url", "jdbc:mysql://127.0.0.1:3399/no_such_db?connectTimeout=300&socketTimeout=300");
        props.put("spring.datasource.username", "root");
        props.put("spring.datasource.password", System.getenv().getOrDefault("CASE_DB_PASSWORD", ""));
        props.put("spring.datasource.hikari.connection-timeout", "500");
        props.put("case.security.issuer", LabFixtures.ISSUER);
        props.put("case.security.audience", LabFixtures.AUDIENCE);
        props.put("case.security.hs256-secret", System.getProperty("case.hs256.secret"));
        props.put("case.order.tenant", "t1");
        props.put("case.order.max-items-per-order", "5");
        props.put("case.order.stock-warn-threshold", "2");
        SpringApplication app = new SpringApplication(OrderFlowApp.class);
        app.setWebApplicationType(org.springframework.boot.WebApplicationType.SERVLET);
        app.addInitializers(context -> context.getEnvironment()
                .getPropertySources().addFirst(new MapPropertySource("u79-bad-ds", props)));
        ConfigurableApplicationContext context = null;
        try {
            context = app.run();
            int port = Integer.parseInt(context.getEnvironment().getProperty("local.server.port"));
            // liveness UP、readiness DOWN：同一进程两个探针分开裁决
            java.net.http.HttpClient client = java.net.http.HttpClient.newHttpClient();
            java.util.function.Function<String, int[]> probe = path -> {
                try {
                    var response = client.send(java.net.http.HttpRequest.newBuilder()
                            .uri(java.net.URI.create("http://127.0.0.1:" + port + path)).GET().build(),
                            java.net.http.HttpResponse.BodyHandlers.ofString());
                    return new int[]{response.statusCode(), response.body().contains("DOWN") ? 1 : 0};
                } catch (Exception ex) {
                    throw new IllegalStateException(ex);
                }
            };
            int[] liveness = probe.apply("/actuator/health/liveness");
            int[] readiness = probe.apply("/actuator/health/readiness");
            assertThat(liveness[0]).isEqualTo(200);
            assertThat(readiness[0]).isEqualTo(503);
            assertThat(readiness[1]).isEqualTo(1);
            System.out.println("U79 badDbReadinessDown=PASS livenessUp=true portListensButNotServing=true");
        } finally {
            if (context != null) {
                context.close();
            }
            System.clearProperty("case.hs256.secret");
        }
    }
}
