package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.spring.InjectionPointAnalyzer;
import click.kamil.springstatemachineexporter.analysis.spring.SpringBeanRegistry;
import click.kamil.springstatemachineexporter.analysis.spring.SpringContextScanner;
import click.kamil.springstatemachineexporter.analysis.spring.SpringDependencyResolver;
import click.kamil.springstatemachineexporter.analysis.service.ToolingClasspath;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Set;

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

class JdtCallGraphEngineIntegrationTest {

    private CodebaseContext context;
    private SpringBeanRegistry registry;
    private JdtCallGraphEngine engine;

    @BeforeEach
    void setUp() throws IOException {
        Path projectRoot = Path.of("../state_machines/extended_analysis_sample").toAbsolutePath().normalize();
        
        context = new CodebaseContext();
        context.setProjectRoot(projectRoot);
        context.setSourcepath(List.of(projectRoot.resolve("src/main/java").toString()));
        context.setClasspath(ToolingClasspath.currentJvmJarEntries());
        context.setResolveBindings(true);
        
        context.scan(Set.of(projectRoot), Collections.emptySet());
        
        registry = new SpringBeanRegistry();
        SpringContextScanner scanner = new SpringContextScanner(registry);
        
        for (var cu : context.getCompilationUnits()) {
            cu.accept(scanner);
        }

        SpringDependencyResolver dependencyResolver = new SpringDependencyResolver(registry);
        InjectionPointAnalyzer injectionAnalyzer = new InjectionPointAnalyzer(dependencyResolver);
        
        engine = new JdtCallGraphEngine(context, injectionAnalyzer);
    }

    @Test
    void shouldResolvePrimaryBeanInjection() {
        EntryPoint ep = EntryPoint.builder()
                .className("click.kamil.examples.statemachine.extended.web.QuirkController")
                .methodName("testPrimary")
                .build();
        TriggerPoint tp = TriggerPoint.builder()
                .className("click.kamil.examples.statemachine.extended.service.PrimaryQuirkService")
                .methodName("doQuirk")
                .build();

        List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
        System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
        assertThat(chains.get(0).getMethodChain())
                .containsExactly(
                        "click.kamil.examples.statemachine.extended.web.QuirkController.testPrimary",
                        "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk"
                );
    }

    @Test
    void shouldResolveQualifierBeanInjection() {
        EntryPoint ep = EntryPoint.builder()
                .className("click.kamil.examples.statemachine.extended.web.QuirkController")
                .methodName("testQualifier")
                .build();
        TriggerPoint tp = TriggerPoint.builder()
                .className("click.kamil.examples.statemachine.extended.service.QualifierQuirkService")
                .methodName("doQuirk")
                .build();

        List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
        System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
        assertThat(chains.get(0).getMethodChain())
                .containsExactly(
                        "click.kamil.examples.statemachine.extended.web.QuirkController.testQualifier",
                        "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk"
                );
    }

    @Test
    void shouldResolveNamedBeanInjection() {
        EntryPoint ep = EntryPoint.builder()
                .className("click.kamil.examples.statemachine.extended.web.QuirkController")
                .methodName("testNamed")
                .build();
        TriggerPoint tp = TriggerPoint.builder()
                .className("click.kamil.examples.statemachine.extended.service.NamedQuirkService")
                .methodName("doQuirk")
                .build();

        List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
        System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
        assertThat(chains.get(0).getMethodChain())
                .containsExactly(
                        "click.kamil.examples.statemachine.extended.web.QuirkController.testNamed",
                        "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk"
                );
    }

    @Test
    void shouldResolveFallbackBeanInjection() {
        EntryPoint ep = EntryPoint.builder()
                .className("click.kamil.examples.statemachine.extended.web.QuirkController")
                .methodName("testFallback")
                .build();
        TriggerPoint tp = TriggerPoint.builder()
                .className("click.kamil.examples.statemachine.extended.service.PrimaryQuirkService")
                .methodName("doQuirk")
                .build();

        List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
        System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
        assertThat(chains.get(0).getMethodChain())
                .containsExactly(
                        "click.kamil.examples.statemachine.extended.web.QuirkController.testFallback",
                        "click.kamil.examples.statemachine.extended.service.PrimaryQuirkService.doQuirk"
                );
    }

    @Test
    void shouldResolveBeanParameterInjection() {
        EntryPoint ep = EntryPoint.builder()
                .className("click.kamil.examples.statemachine.extended.config.BeanParameterTestConfig")
                .methodName("myBeanParamTester")
                .build();
        TriggerPoint tp = TriggerPoint.builder()
                .className("click.kamil.examples.statemachine.extended.service.NamedQuirkService")
                .methodName("doQuirk")
                .build();

        List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
        System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
        assertThat(chains.get(0).getMethodChain())
                .containsExactly(
                        "click.kamil.examples.statemachine.extended.config.BeanParameterTestConfig.myBeanParamTester",
                        "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk"
                );
    }

    @Test
    void shouldResolveOrderedBeanInjection() {
        EntryPoint ep = EntryPoint.builder()
                .className("click.kamil.examples.statemachine.extended.web.OrderedTestController")
                .methodName("testOrder")
                .build();
        TriggerPoint tp = TriggerPoint.builder()
                .className("click.kamil.examples.statemachine.extended.service.HighPriorityOrderedService")
                .methodName("doAction")
                .build();

        List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
        System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
        assertThat(chains.get(0).getMethodChain())
                .containsExactly(
                        "click.kamil.examples.statemachine.extended.web.OrderedTestController.testOrder",
                        "click.kamil.examples.statemachine.extended.service.HighPriorityOrderedService.doAction"
                );
    }

    @Test
    void shouldResolveFieldInjection() {
        EntryPoint ep = EntryPoint.builder()
                .className("click.kamil.examples.statemachine.extended.web.FieldInjectionController")
                .methodName("testField")
                .build();
        TriggerPoint tp = TriggerPoint.builder()
                .className("click.kamil.examples.statemachine.extended.service.NamedQuirkService")
                .methodName("doQuirk")
                .build();

        List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
        System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
        assertThat(chains.get(0).getMethodChain()).contains(
                "click.kamil.examples.statemachine.extended.web.FieldInjectionController.testField",
                "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk"
        );
    }

    @Test
    void shouldResolveSetterInjection() {
        EntryPoint ep = EntryPoint.builder()
                .className("click.kamil.examples.statemachine.extended.web.SetterInjectionController")
                .methodName("testSetter")
                .build();
        TriggerPoint tp = TriggerPoint.builder()
                .className("click.kamil.examples.statemachine.extended.service.NamedQuirkService")
                .methodName("doQuirk")
                .build();

        List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
        System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
        assertThat(chains.get(0).getMethodChain()).contains(
                "click.kamil.examples.statemachine.extended.web.SetterInjectionController.testSetter",
                "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk"
        );
    }

    @Test
    void shouldResolveAbstractInheritedBean() {
        EntryPoint ep = EntryPoint.builder()
                .className("click.kamil.examples.statemachine.extended.config.AbstractBaseConfig")
                .methodName("inheritedBeanTester")
                .build();
        TriggerPoint tp = TriggerPoint.builder()
                .className("click.kamil.examples.statemachine.extended.service.NamedQuirkService")
                .methodName("doQuirk")
                .build();

        List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
        System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
        assertThat(chains.get(0).getMethodChain()).contains(
                "click.kamil.examples.statemachine.extended.config.AbstractBaseConfig.inheritedBeanTester",
                "click.kamil.examples.statemachine.extended.service.NamedQuirkService.doQuirk"
        );
    }

    @Test
    void shouldResolveAmbiguousListFallback() {
        EntryPoint ep = EntryPoint.builder()
                .className("click.kamil.examples.statemachine.extended.web.ListInjectionController")
                .methodName("testList")
                .build();
        TriggerPoint tp1 = TriggerPoint.builder()
                .className("click.kamil.examples.statemachine.extended.service.AmbiguousA")
                .methodName("doAmbig")
                .build();
        TriggerPoint tp2 = TriggerPoint.builder()
                .className("click.kamil.examples.statemachine.extended.service.AmbiguousB")
                .methodName("doAmbig")
                .build();

        List<CallChain> chains1 = engine.findChains(List.of(ep), List.of(tp1));
        List<CallChain> chains2 = engine.findChains(List.of(ep), List.of(tp2));
        
        assertThat(chains1).isNotEmpty();
        assertThat(chains1.get(0).getMethodChain()).contains(
                "click.kamil.examples.statemachine.extended.web.ListInjectionController.testList",
                "click.kamil.examples.statemachine.extended.service.AmbiguousA.doAmbig"
        );
        
        assertThat(chains2).isNotEmpty();
        assertThat(chains2.get(0).getMethodChain()).contains(
                "click.kamil.examples.statemachine.extended.web.ListInjectionController.testList",
                "click.kamil.examples.statemachine.extended.service.AmbiguousB.doAmbig"
        );
    }

    @Test
    void shouldResolveConcreteReturnType() {
        EntryPoint ep = EntryPoint.builder()
                .className("click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController")
                .methodName("testConcrete")
                .build();
        TriggerPoint tp = TriggerPoint.builder()
                .className("click.kamil.examples.statemachine.extended.service.QualifierQuirkService")
                .methodName("doQuirk")
                .build();

        List<CallChain> chains = engine.findChains(List.of(ep), List.of(tp));
        System.out.println("CHAINS FOUND: " + chains.size()); assertThat(chains).hasSize(1);
        assertThat(chains.get(0).getMethodChain()).contains(
                "click.kamil.examples.statemachine.extended.web.ConcreteReturnTypeController.testConcrete",
                "click.kamil.examples.statemachine.extended.service.QualifierQuirkService.doQuirk"
        );
    }

    @Test
    void testEnterpriseDispatcherResolutionJdt() throws IOException {
        Path projectRoot = Path.of("../state_machines/state_machine_enterprise").toAbsolutePath().normalize();
        
        CodebaseContext ctx = new CodebaseContext();
        ctx.setProjectRoot(projectRoot);
        ctx.setSourcepath(List.of(projectRoot.resolve("src/main/java").toString()));
        ctx.setClasspath(ToolingClasspath.currentJvmJarEntries());
        ctx.setResolveBindings(true);
        ctx.scan(Set.of(projectRoot), Collections.emptySet());
        
        SpringBeanRegistry reg = new SpringBeanRegistry();
        SpringContextScanner scanner = new SpringContextScanner(reg);
        
        for (var cu : ctx.getCompilationUnits()) {
            cu.accept(scanner);
        }

        SpringDependencyResolver dependencyResolver = new SpringDependencyResolver(reg);
        InjectionPointAnalyzer injectionAnalyzer = new InjectionPointAnalyzer(dependencyResolver);
        
        JdtCallGraphEngine jdtEngine = new JdtCallGraphEngine(ctx, injectionAnalyzer);
        
        EntryPoint entry = EntryPoint.builder()
                .className("click.kamil.enterprise.web.StateMachineController")
                .methodName("transition")
                .name("POST /api/machine/{machineType}/transition/{event}")
                .parameters(java.util.List.of(
                        EntryPoint.Parameter.builder()
                                .name("machineType")
                                .type("String")
                                .annotations(java.util.List.of("PathVariable"))
                                .build(),
                        EntryPoint.Parameter.builder()
                                .name("event")
                                .type("String")
                                .annotations(java.util.List.of("PathVariable"))
                                .build()))
                .build();
        TriggerPoint trigger = TriggerPoint.builder()
                .className("click.kamil.enterprise.web.StateMachineDispatcher")
                .methodName("dispatch")
                .event("event")
                .build();
                
        List<CallChain> chains = jdtEngine.findChains(List.of(entry), List.of(trigger));
        assertThat(chains).isNotEmpty();
        java.util.Set<String> polymorphicEvents = new java.util.LinkedHashSet<>();
        for (CallChain chain : chains) {
            if (chain.getTriggerPoint().getPolymorphicEvents() != null) {
                polymorphicEvents.addAll(chain.getTriggerPoint().getPolymorphicEvents());
            }
            assertThat(chain.getTriggerPoint().getEvent())
                    .doesNotContain("true ?");
        }
        assertThat(polymorphicEvents)
                .contains("<SYMBOLIC: OrderEvent.*>", "<SYMBOLIC: DocumentEvent.*>", "<SYMBOLIC: UserEvent.*>");
    }
}
