package study.springcaps;

import org.junit.jupiter.api.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jmx.export.MBeanExporter;
import org.springframework.jmx.export.annotation.AnnotationMBeanExporter;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.jmx.export.naming.MetadataNamingStrategy;
import org.springframework.jmx.support.MBeanServerFactoryBean;
import org.springframework.jmx.support.RegistrationPolicy;

import javax.management.Attribute;
import javax.management.JMException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.ObjectInstance;
import javax.management.ObjectName;
import javax.management.openmbean.OpenDataException;
import java.util.List;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * U49 JMX：真实平台 MBeanServer 上的注册、属性/操作调用与失败边界。
 */
class JmxPlatformTest {

    private static final ObjectName ON = quiet("study.springcaps:name=cacheStats,type=CacheStats");

    static ObjectName quiet(String name) {
        try {
            return new ObjectName(name);
        } catch (javax.management.MalformedObjectNameException e) {
            throw new IllegalStateException(e);
        }
    }

    @Configuration
    static class JmxCfg {
        @Bean
        MBeanServerFactoryBean mbeanServer() {
            MBeanServerFactoryBean factory = new MBeanServerFactoryBean();
            factory.setLocateExistingServerIfPossible(true);
            return factory;
        }

        @Bean
        CacheStats cacheStats() {
            return new CacheStats();
        }

        @Bean
        AnnotationMBeanExporter exporter(MBeanServer server) {
            AnnotationMBeanExporter exporter = new AnnotationMBeanExporter();
            exporter.setServer(server);
            exporter.setRegistrationPolicy(RegistrationPolicy.FAIL_ON_EXISTING);
            return exporter;
        }
    }

    @Test
    void jmxRegisterAttributesAndOperation() throws Exception {
        MBeanServer platform = java.lang.management.ManagementFactory.getPlatformMBeanServer();
        int before = platform.queryNames(ON, null).size();
        assertEquals(0, before, "测试前同名 MBean 不应存在（自证环境干净）");

        try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JmxCfg.class)) {
            MBeanServer server = ctx.getBean(MBeanServer.class);
            CacheStats stats = ctx.getBean(CacheStats.class);

            assertTrue(server.queryNames(ON, null).size() == 1, "注解 Bean 应被自动注册为 MBean");

            MBeanInfo info = server.getMBeanInfo(ON);
            List<String> attrNames = java.util.Arrays.stream(info.getAttributes())
                    .map(MBeanAttributeInfo::getName).toList();
            assertTrue(attrNames.contains("Hits") && attrNames.contains("Misses"),
                    "带 @ManagedAttribute 的 getter 才暴露，实际=" + attrNames);
            assertFalse(attrNames.contains("HiddenField"), "无注解 getter 不应出现在 MBeanInfo");

            stats.recordHit();
            stats.recordMiss();
            assertEquals(1, server.getAttribute(ON, "Hits"));

            // 只有 getter 的 @ManagedAttribute 默认只读：setAttribute 抛 AttributeNotFoundException
            javax.management.AttributeNotFoundException readOnly = null;
            try {
                server.setAttribute(ON, new Attribute("Hits", 5));
            } catch (javax.management.AttributeNotFoundException ex) {
                readOnly = ex;
            }
            assertTrue(readOnly != null, "无 setter 的属性应不可写，实际=" + readOnly);
            assertEquals(1, stats.getHits(), "写失败后真实值不变");

            Object sum = server.invoke(ON, "resetCounters", new Object[0], new String[0]);
            assertEquals(2, ((Number) sum).intValue(), "操作返回 Hits(1)+Misses(1)=2，并清零");
            assertEquals(0, stats.getHits(), "操作后计数清零");
            System.out.println("U49 jmxRegister=PASS attrs=" + attrNames + " readOnly=true reset=" + sum);
        } finally {
            // FAIL_ON_EXISTING + ctx.close()：exporter destroy 注销 MBean；若残留则本测试失败
            assertEquals(0, platform.queryNames(ON, null).size(), "上下文关闭后 MBean 应被注销");
            System.out.println("U49 jmxLifecycle=PASS unregisteredOnClose=true");
        }
    }

    @Test
    void jmxRegistrationPolicyConflict() throws Exception {
        MBeanServer server = java.lang.management.ManagementFactory.getPlatformMBeanServer();
        // 预注册一个同名 MBean，模拟重复注册
        javax.management.StandardMBean placeholder = new javax.management.StandardMBean(new Runnable() {
            @Override
            public void run() {
            }
        }, Runnable.class);
        server.registerMBean(placeholder, ON);
        try {
            try (AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JmxCfg.class)) {
                org.junit.jupiter.api.Assertions.fail("FAIL_ON_EXISTING 必须让启动失败");
            } catch (Exception expected) {
                // ApplicationContext refresh 失败
                assertTrue(String.valueOf(expected).contains("JMException")
                                || String.valueOf(expected).contains("InstanceAlreadyExistsException")
                                || String.valueOf(expected.getCause()) != null,
                        "失败应来自注册冲突");
            }
            System.out.println("U49 jmxConflictFail=PASS policy=FAIL_ON_EXISTING");
        } finally {
            if (server.isRegistered(ON)) {
                server.unregisterMBean(ON);
            }
        }
    }

    @Test
    void jmxBootEndpointMBeans() throws Exception {
        // Boot 端点 JMX 暴露：spring.jmx.enabled=true + management.endpoints.jmx.exposure.include=health
        // 仅装配（无 Web 上下文），通过 JmxEndpointExporter 检查端点 MBean 注册
        org.springframework.boot.SpringApplication app =
                new org.springframework.boot.SpringApplication(BootJmxApp.class);
        app.setWebApplicationType(org.springframework.boot.WebApplicationType.NONE);
        try (var ctx = app.run("--spring.jmx.enabled=true",
                "--management.endpoints.jmx.exposure.include=health",
                "--spring.main.banner-mode=off")) {
            MBeanServer server = java.lang.management.ManagementFactory.getPlatformMBeanServer();
            ObjectName health = quiet("org.springframework.boot:type=Endpoint,name=Health");
            assertTrue(server.queryNames(health, null).size() == 1,
                    "health 端点应以 org.springframework.boot domain 注册为 MBean");
            Object healthState = server.invoke(health, "health", new Object[0], new String[0]);
            assertTrue(String.valueOf(healthState).contains("UP"), "JMX 调用 health 应返回 UP，实际=" + healthState);
            System.out.println("U49 jmxBootEndpoint=PASS domain=org.springframework.boot health=UP");
        }
    }

    @org.springframework.boot.autoconfigure.SpringBootApplication
    static class BootJmxApp {
    }
}
