package study.runtime;

import java.util.*;
import org.slf4j.LoggerFactory;
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.boot.context.event.*;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.source.*;
import org.springframework.context.*;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.core.Ordered;
import org.springframework.core.env.*;
import tools.jackson.databind.json.JsonMapper;

@SpringBootApplication
@EnableConfigurationProperties(NoticeProperties.class)
@org.springframework.scheduling.annotation.EnableScheduling
public class RuntimeLab {
  static final List<String> events=new ArrayList<>();
  static class Probe implements ApplicationListener<ApplicationEvent>,Ordered {
    public int getOrder(){return Ordered.HIGHEST_PRECEDENCE;}
    public void onApplicationEvent(ApplicationEvent event){
      String phase=null;
      if(event instanceof ApplicationEnvironmentPreparedEvent)phase="environment-event-before-postprocessors";
      if(event instanceof ApplicationPreparedEvent)phase="prepared";
      if(event instanceof ApplicationStartedEvent)phase="started";
      if(event instanceof ApplicationReadyEvent)phase="ready";
      if(event instanceof ApplicationFailedEvent)phase="failed";
      if(event instanceof ContextClosedEvent)phase="closed";
      if(phase!=null){events.add(phase);System.out.println("EVENT "+phase);}
    }
  }
  @org.springframework.context.annotation.Bean
  ApplicationRunner stageRunner(){return args -> {events.add("runner");System.out.println("EVENT runner");};}
  public static void main(String[] args) throws Exception{
    events.clear();var app=new SpringApplication(RuntimeLab.class);
    boolean server=Arrays.asList(args).contains("--lab.mode=server");
    app.setWebApplicationType(server?WebApplicationType.SERVLET:WebApplicationType.NONE);
    app.addListeners(new Probe());
    ConfigurableApplicationContext context=null;
    try{
      context=app.run(args);
      String mode=context.getEnvironment().getProperty("lab.mode","config");
      if(mode.equals("server"))return; // shutdown hook owns a live server; runner harness sends SIGTERM.
      Object result=switch(mode){
        case "config" -> snapshot(context);
        case "tasks" -> TaskProbe.run(context);
        case "db" -> DatabaseProbe.run(context);
        default -> throw new IllegalArgumentException("unknown mode: "+mode);
      };
      System.out.println("RESULT "+new JsonMapper().writeValueAsString(result));
    }catch(RuntimeException error){
      var causes=new ArrayList<String>();for(Throwable t=error;t!=null;t=t.getCause())causes.add(t.getClass().getName());
      System.out.println("CAUSES "+causes);System.out.println("EVENTS "+events);throw error;
    }finally{
      if(context!=null&&!server)context.close();
    }
    System.out.println("EVENTS "+events);
  }
  static Map<String,Object> snapshot(ConfigurableApplicationContext context){
    var p=context.getBean(NoticeProperties.class);var env=context.getEnvironment();
    var effective=new LinkedHashMap<String,Object>();
    for(String key:List.of("lab.notice.message","lab.notice.timeout","lab.notice.client-id")){
      var name=ConfigurationPropertyName.of(key);
      for(var source:ConfigurationPropertySources.get(env)){
        var property=source.getConfigurationProperty(name);if(property==null)continue;
        Object underlying=source.getUnderlyingSource();String sourceName=underlying instanceof PropertySource<?> ps?ps.getName():String.valueOf(underlying);
        effective.put(key,Map.of("value",String.valueOf(property.getValue()),"source",sourceName,"origin",String.valueOf(property.getOrigin())));break;
      }
    }
    var bound=new LinkedHashMap<String,Object>();bound.put("message",p.message());bound.put("timeoutMillis",p.timeout().toMillis());bound.put("workers",p.workers());bound.put("region",p.nested().region());bound.put("limitBytes",p.limit().toBytes());bound.put("labels",p.labels());bound.put("routes",p.routes());bound.put("clientId",p.clientId());
    LoggerFactory.getLogger(RuntimeLab.class).info("U23_LOG_PROBE synthetic public message={}",p.message());
    return Map.of("bound",bound,"effective",effective,"profiles",env.getActiveProfiles(),"mapper",context.getBean(JsonMapper.class).getClass().getName());
  }
}
