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.enricher.routing.HeuristicBeanResolutionEngine;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

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

class EnterpriseBugsTest {

    @Test
    void shouldResolveStaticImportAndAvoidMismatchedConstructorEnumArgs(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                import static com.example.EventUtils.assertMatchingCancelEventType;
                
                public class OrderController {
                    private OrderService service;
                    public void handleCancel(Cancellation.Sender sender) {
                        service.process(new OrderCancelledEvent(sender).getType());
                    }
                }
                
                class OrderService {
                    public void process(CommonOrderEvent event) {}
                }
                
                class OrderCancelledEvent {
                    private CommonOrderEvent type;
                    public OrderCancelledEvent(Cancellation.Sender sender) {
                        this.type = assertMatchingCancelEventType(sender);
                    }
                    public CommonOrderEvent getType() { return type; }
                }
                
                class EventUtils {
                    public static CommonOrderEvent assertMatchingCancelEventType(Cancellation.Sender sender) {
                        return switch(sender) {
                            case registry_corp -> CommonOrderEvent.CANCELLED_BY_registry_corp;
                            default -> null;
                        };
                    }
                }
                
                class Cancellation {
                    public enum Sender { registry_corp }
                }
                
                enum CommonOrderEvent { CANCELLED_BY_registry_corp }
                """;

        Files.writeString(tempDir.resolve("OrderConfig.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.scan(tempDir);
        
        HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.OrderController")
                .methodName("handleCancel")
                .build();
        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.OrderService")
                .methodName("process")
                .event("event")
                .build();

        List<CallChain> chains = engine.findChains(List.of(entryPoint), List.of(trigger));

        assertThat(chains).hasSize(1);
        TriggerPoint resolvedTp = chains.get(0).getTriggerPoint();
        
        // Assert Bug 3: assertMatchingCancelEventType method is resolved via static import
        // Assert Bug 2: Cancellation.Sender.registry_corp is NOT extracted as an event argument
        assertThat(resolvedTp.getPolymorphicEvents())
                .as("Polymorphic events found: %s", resolvedTp.getPolymorphicEvents())
                .contains("CommonOrderEvent.CANCELLED_BY_registry_corp")
                .doesNotContain("registry_corp", "Cancellation.Sender.registry_corp");
    }

    @Test
    void shouldAcceptConnectorCallChainUsingTriggerPointDomainMatch() {
        HeuristicBeanResolutionEngine engine = new HeuristicBeanResolutionEngine();

        CallChain chain = CallChain.builder()
                .triggerPoint(TriggerPoint.builder()
                        .className("project.shop.OrderService")
                        .build())
                .methodChain(List.of("project.enterprise.connector.org.OrgOrderControllerImpl.accept"))
                .build();

        // Assert Bug 4: Connector entry points targeting shop SM trigger point are accepted
        boolean isMatched = engine.isRoutedToCorrectMachine(chain, "project.shop.OrderStateMachine", null);
        assertThat(isMatched).isTrue();
    }

    @Test
    void shouldResolveClassConstantToSimpleNameWithoutFQNDuplicates(@TempDir Path tempDir) throws IOException {
        String source = """
                package com.example;
                public enum CommonOrderEvent { ACCEPTED_BY_corp }
                """;
        Files.writeString(tempDir.resolve("CommonOrderEvent.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);

        List<String> resolved = engine.resolveClassConstantReturns("com.example.CommonOrderEvent.ACCEPTED_BY_corp", context, null);
        
        // Assert Bug 6: resolves cleanly to simple name (since prefix is a known type)
        assertThat(resolved).containsExactly("CommonOrderEvent.ACCEPTED_BY_corp");
    }

    @Test
    void shouldMatchFqnsAndMismatchesOnRouting(@TempDir Path tempDir) throws IOException {
        String statesSrc = """
                package com.example;
                public enum OrderStates { S1, S2 }
                """;
        String eventsSrc = """
                package com.example;
                public enum OrderEvents { E1, E2 }
                """;
        String otherEventsSrc = """
                package com.example.other;
                public enum OtherEvents { E1, E2 }
                """;
        String configSrc = """
                package com.example;
                public class OrderStateMachineConfig extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<OrderStates, OrderEvents> {}
                """;
        
        Files.writeString(tempDir.resolve("OrderStates.java"), statesSrc);
        Files.writeString(tempDir.resolve("OrderEvents.java"), eventsSrc);
        Files.writeString(tempDir.resolve("OtherEvents.java"), otherEventsSrc);
        Files.writeString(tempDir.resolve("OrderStateMachineConfig.java"), configSrc);
        
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        
        HeuristicBeanResolutionEngine engine = new HeuristicBeanResolutionEngine();
        
        // Scenario 1: Exact Event type argument match
        CallChain chain1 = CallChain.builder()
                .triggerPoint(TriggerPoint.builder()
                        .eventTypeFqn("com.example.OrderEvents")
                        .className("com.example.Service")
                        .build())
                .build();
        assertThat(engine.isRoutedToCorrectMachine(chain1, "com.example.OrderStateMachineConfig", context)).isTrue();
        
        // Scenario 2: Event type argument mismatch
        CallChain chain2 = CallChain.builder()
                .triggerPoint(TriggerPoint.builder()
                        .eventTypeFqn("com.example.other.OtherEvents")
                        .className("com.example.Service")
                        .build())
                .build();
        assertThat(engine.isRoutedToCorrectMachine(chain2, "com.example.OrderStateMachineConfig", context)).isFalse();
    }

    @Test
    void shouldFindPathsUsingEagerEntryPointExpansion(@TempDir Path tempDir) throws IOException {
        String apiSrc = """
                package com.example;
                public interface OrderApi {
                    void submit();
                }
                """;
        String controllerSrc = """
                package com.example;
                public class OrderController implements OrderApi {
                    private OrderService service;
                    public void submit() {
                        service.trigger("someEvent");
                    }
                }
                """;
        String serviceSrc = """
                package com.example;
                public class OrderService {
                    public void trigger(String EVENT) {}
                }
                """;

        Files.writeString(tempDir.resolve("OrderApi.java"), apiSrc);
        Files.writeString(tempDir.resolve("OrderController.java"), controllerSrc);
        Files.writeString(tempDir.resolve("OrderService.java"), serviceSrc);

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);

        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.OrderApi")
                .methodName("submit")
                .build();
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .className("com.example.OrderService")
                .methodName("trigger")
                .event("EVENT")
                .build();

        List<CallChain> chains = engine.findChains(List.of(entryPoint), List.of(triggerPoint));

        assertThat(chains).hasSize(1);
        assertThat(chains.get(0).getMethodChain()).contains(
                "com.example.OrderController.submit"
        );
    }

    @Test
    void shouldNotPolymorphicallyExpandExternalInterfacesWithManyImpls(@TempDir Path tempDir) throws IOException {
        String impl1 = "package com.example; public class R1 implements java.lang.Runnable { public void run() {} }";
        String impl2 = "package com.example; public class R2 implements java.lang.Runnable { public void run() {} }";
        String impl3 = "package com.example; public class R3 implements java.lang.Runnable { public void run() {} }";
        String impl4 = "package com.example; public class R4 implements java.lang.Runnable { public void run() {} }";
        String executorSrc = """
                package com.example;
                public class ExecutorService {
                    public void execute(java.lang.Runnable task) {
                        task.run();
                    }
                }
                """;

        Files.writeString(tempDir.resolve("R1.java"), impl1);
        Files.writeString(tempDir.resolve("R2.java"), impl2);
        Files.writeString(tempDir.resolve("R3.java"), impl3);
        Files.writeString(tempDir.resolve("R4.java"), impl4);
        Files.writeString(tempDir.resolve("ExecutorService.java"), executorSrc);

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
        var graph = engine.buildCallGraph();

        // The execute method calls task.run(). Since java.lang.Runnable is external (td == null) and has > 3 impls,
        // it should NOT resolve task.run() to R1.run(), R2.run(), etc.
        var edges = graph.get("com.example.ExecutorService.execute");
        if (edges != null) {
            for (var edge : edges) {
                assertThat(edge.getTargetMethod()).isNotEqualTo("com.example.R1.run");
            }
            assertThat(edges).extracting("targetMethod").containsExactly("java.lang.Runnable.run");
        }
    }

    @Test
    void shouldNotTreatValueOfExpressionAsWildcardMatchingAllTransitions() {
        click.kamil.springstatemachineexporter.analysis.enricher.matching.StrictFqnMatchingEngine engine =
                new click.kamil.springstatemachineexporter.analysis.enricher.matching.StrictFqnMatchingEngine();
        
        click.kamil.springstatemachineexporter.model.Event smEvent =
                click.kamil.springstatemachineexporter.model.Event.of("PAY", "com.example.OrderEvents.PAY");
        
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("OrderEvents.valueOf(someVar)")
                .polymorphicEvents(List.of("OrderEvents.PAY"))
                .build();
        
        assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
    }
}
