package study.readiness;

import java.lang.reflect.Proxy;
import java.nio.file.*;
import java.util.*;
import jakarta.jms.ConnectionFactory;
import org.junit.jupiter.api.*;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.boot.kafka.autoconfigure.KafkaAutoConfiguration;
import org.springframework.boot.amqp.autoconfigure.RabbitAutoConfiguration;
import org.springframework.boot.jms.autoconfigure.JmsAutoConfiguration;
import org.springframework.boot.pulsar.autoconfigure.PulsarAutoConfiguration;
import org.springframework.boot.restclient.autoconfigure.RestClientAutoConfiguration;
import org.springframework.context.annotation.*;
import org.springframework.kafka.core.*;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.pulsar.core.*;
import org.springframework.web.client.RestClient;
import tools.jackson.databind.json.JsonMapper;
import static org.junit.jupiter.api.Assertions.*;

/** Actual framework construction/conditions only. No broker delivery is attempted. */
class MessagingProbeTest {
  static final Map<String,Object> results=new LinkedHashMap<>();
  @AfterAll static void save()throws Exception{Files.writeString(Path.of("target/messaging-results.json"),new JsonMapper().writerWithDefaultPrettyPrinter().writeValueAsString(results));}
  @Test void kafka(){
    var base=new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(KafkaAutoConfiguration.class)).withPropertyValues("spring.kafka.bootstrap-servers=127.0.0.1:1","spring.kafka.admin.auto-create=false");var data=new LinkedHashMap<String,Object>();
    base.run(c->{assertNull(c.getStartupFailure());assertNotNull(c.getBean(KafkaTemplate.class));assertNotNull(c.getBean(ProducerFactory.class));data.put("defaultTemplate",true);data.put("conditions",ConditionProbeTest.conditions(c,"Kafka"));});
    base.withUserConfiguration(UserKafka.class).run(c->{assertNull(c.getStartupFailure());assertSame(c.getBean("userProducer"),c.getBean(KafkaTemplate.class).getProducerFactory());assertEquals(1,c.getBeanNamesForType(ProducerFactory.class).length);data.put("producerFactoryBackoff",true);});
    base.withClassLoader(new FilteredClassLoader(KafkaTemplate.class)).run(c->{assertNull(c.getStartupFailure());assertEquals(0,c.getBeanNamesForType(ProducerFactory.class).length);data.put("missingClassSkipped",true);});data.put("brokerMessages",0);results.put("kafka",data);
  }
  @Configuration(proxyBeanMethods=false) static class UserKafka{@Bean ProducerFactory<Object,Object> userProducer(){return new DefaultKafkaProducerFactory<>(Map.of("bootstrap.servers","127.0.0.1:1"));}}
  @Test void rabbit(){
    var base=new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(RabbitAutoConfiguration.class)).withPropertyValues("spring.rabbitmq.dynamic=false","spring.rabbitmq.listener.simple.auto-startup=false");var data=new LinkedHashMap<String,Object>();
    base.run(c->{assertNull(c.getStartupFailure());assertNotNull(c.getBean(RabbitTemplate.class));data.put("defaultTemplate",true);data.put("conditions",ConditionProbeTest.conditions(c,"Rabbit"));});
    base.withUserConfiguration(UserRabbit.class).run(c->{assertNull(c.getStartupFailure());assertSame(c.getBean("userRabbit"),c.getBean(RabbitTemplate.class).getConnectionFactory());data.put("factoryBackoff",true);});
    base.withUserConfiguration(TwoRabbit.class).run(c->{assertNull(c.getStartupFailure());assertEquals(0,c.getBeanNamesForType(RabbitTemplate.class).length);data.put("twoFactoriesNoTemplate",true);});
    base.withClassLoader(new FilteredClassLoader(RabbitTemplate.class)).run(c->{assertNull(c.getStartupFailure());assertEquals(0,c.getBeanNamesForType(CachingConnectionFactory.class).length);data.put("missingClassSkipped",true);});data.put("brokerMessages",0);results.put("amqp",data);
  }
  @Configuration(proxyBeanMethods=false) static class UserRabbit{@Bean CachingConnectionFactory userRabbit(){return new CachingConnectionFactory("127.0.0.1",1);}}
  @Configuration(proxyBeanMethods=false) static class TwoRabbit{
    @Bean CachingConnectionFactory rabbitOne(){return new CachingConnectionFactory("127.0.0.1",1);}
    @Bean CachingConnectionFactory rabbitTwo(){return new CachingConnectionFactory("127.0.0.1",1);}
    @Bean SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(){return new SimpleRabbitListenerContainerFactory();}
  }
  @Test void jms(){
    var base=new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(JmsAutoConfiguration.class));var data=new LinkedHashMap<String,Object>();
    base.run(c->{assertNull(c.getStartupFailure());assertEquals(0,c.getBeanNamesForType(JmsTemplate.class).length);data.put("noFactoryNoTemplate",true);data.put("conditions",ConditionProbeTest.conditions(c,"Jms"));});
    base.withUserConfiguration(NonConnectingJms.class).run(c->{assertNull(c.getStartupFailure());assertNotNull(c.getBean(JmsTemplate.class));data.put("fixtureFactoryEnablesTemplate",true);});
    base.withUserConfiguration(NonConnectingJms.class,UserJmsTemplate.class).run(c->{assertNull(c.getStartupFailure());assertSame(c.getBean("userJms"),c.getBean(JmsTemplate.class));data.put("userBackoff",true);});
    base.withClassLoader(new FilteredClassLoader(JmsTemplate.class)).run(c->{assertNull(c.getStartupFailure());assertEquals(0,c.getBeanNamesForType(JmsTemplate.class).length);data.put("missingClassSkipped",true);});data.put("brokerMessages",0);data.put("factoryFixture","non-connecting dynamic proxy; any connection attempt fails");results.put("jms",data);
  }
  @Configuration(proxyBeanMethods=false) static class NonConnectingJms{@Bean ConnectionFactory connectionFactory(){return (ConnectionFactory)Proxy.newProxyInstance(ConnectionFactory.class.getClassLoader(),new Class<?>[]{ConnectionFactory.class},(proxy,method,args)->{
    if(method.getName().equals("toString"))return "u24-nonconnecting-jms";if(method.getName().equals("hashCode"))return System.identityHashCode(proxy);if(method.getName().equals("equals"))return proxy==args[0];throw new AssertionError("JMS connection must not be attempted: "+method);
  });}}
  @Configuration(proxyBeanMethods=false) static class UserJmsTemplate{@Bean JmsTemplate userJms(ConnectionFactory factory){return new JmsTemplate(factory);}}
  @Test void pulsar(){
    var base=new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(PulsarAutoConfiguration.class)).withPropertyValues("spring.pulsar.client.service-url=pulsar://127.0.0.1:1","spring.pulsar.listener.auto-startup=false");var data=new LinkedHashMap<String,Object>();
    base.run(c->{assertNull(c.getStartupFailure());assertNotNull(c.getBean(PulsarTemplate.class));data.put("defaultTemplate",true);data.put("conditions",ConditionProbeTest.conditions(c,"Pulsar"));});
    base.withUserConfiguration(UserPulsar.class).run(c->{assertNull(c.getStartupFailure());assertSame(c.getBean("userPulsarFactory"),c.getBean(PulsarClientFactory.class));assertEquals(1,c.getBeanNamesForType(PulsarClientFactory.class).length);data.put("clientFactoryBackoff",true);});
    base.withClassLoader(new FilteredClassLoader(PulsarTemplate.class)).run(c->{assertNull(c.getStartupFailure());assertEquals(0,c.getBeanNamesForType(PulsarClientFactory.class).length);data.put("missingClassSkipped",true);});data.put("brokerMessages",0);results.put("pulsar",data);
  }
  @Configuration(proxyBeanMethods=false) static class UserPulsar{@Bean PulsarClientFactory userPulsarFactory(){return new DefaultPulsarClientFactory(builder->builder.serviceUrl("pulsar://127.0.0.1:1"));}}
  @Test void restClient(){
    var base=new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(RestClientAutoConfiguration.class));var data=new LinkedHashMap<String,Object>();
    base.run(c->{assertNull(c.getStartupFailure());assertNotSame(c.getBean(RestClient.Builder.class),c.getBean(RestClient.Builder.class));data.put("prototypeBuilder",true);});
    base.withUserConfiguration(UserBuilder.class).run(c->{assertNull(c.getStartupFailure());assertSame(c.getBean("userBuilder"),c.getBean(RestClient.Builder.class));assertEquals(1,c.getBeanNamesForType(RestClient.Builder.class).length);data.put("userBuilderBackoff",true);});
    base.withClassLoader(new FilteredClassLoader(RestClient.class)).run(c->{assertNull(c.getStartupFailure());assertEquals(0,c.getBeanNamesForType(RestClient.Builder.class).length);data.put("missingClassSkipped",true);});results.put("restClient",data);
  }
  @Configuration(proxyBeanMethods=false) static class UserBuilder{@Bean RestClient.Builder userBuilder(){return RestClient.builder();}}
}
