package click.kamil.springstatemachineexporter.analysis.enricher.routing;

import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

@Tag("heuristic_bean_resolution")
public class HeuristicBeanResolutionEngineTest {

    private final HeuristicBeanResolutionEngine engine = new HeuristicBeanResolutionEngine();

    @Test
    public void testDeepPackageDivergenceMismatch() {
        // This is the bug case: sharing a deep root, but diverging into different domains
        CallChain chain = CallChain.builder()
                .methodChain(Arrays.asList("com.acme.corp.division.project.orders.OrderService.pay()"))
                .build();

        String machineName = "com.acme.corp.division.project.payments.PaymentStateMachine";

        boolean result = engine.isRoutedToCorrectMachine(chain, machineName, null);

        // They share com.acme.corp.division.project, but diverge into orders vs payments
        // This should be a strong mismatch, so it should return false
        assertFalse(result, "Deep package divergence into different domains should be rejected");
    }

    @Test
    public void testSameDomainMatch() {
        CallChain chain = CallChain.builder()
                .methodChain(Arrays.asList("com.acme.ecommerce.orders.OrderService.process()"))
                .build();

        String machineName = "com.acme.ecommerce.orders.OrderStateMachine";

        boolean result = engine.isRoutedToCorrectMachine(chain, machineName, null);

        // Same exact domain, should match
        assertTrue(result, "Same domain should be accepted");
    }

    @Test
    public void testSubPackageMatch() {
        CallChain chain = CallChain.builder()
                .methodChain(Arrays.asList("com.acme.ecommerce.orders.impl.OrderServiceImpl.process()"))
                .build();

        String machineName = "com.acme.ecommerce.orders.OrderStateMachine";

        boolean result = engine.isRoutedToCorrectMachine(chain, machineName, null);

        // Subpackage should match
        assertTrue(result, "Sub-packages of the same domain should be accepted");
    }

    @Test
    public void testSharedDomainTermMatch() {
        CallChain chain = CallChain.builder()
                .methodChain(Arrays.asList("com.acme.orders.web.OrderController.submit()"))
                .build();

        String machineName = "com.acme.orders.service.OrderStateMachine";

        boolean result = engine.isRoutedToCorrectMachine(chain, machineName, null);

        // Different subpackages, but they share the "order" domain term and common prefix
        assertTrue(result, "Divergent packages that share a domain term should be accepted");
    }

    @Test
    public void testExplicitTargetVariableMatch() {
        CallChain chain = CallChain.builder()
                .methodChain(Arrays.asList("com.acme.common.GlobalTrigger.fire()"))
                .contextMachineId("paymentStateMachine") // Explicitly targets payment
                .build();

        String machineName = "com.acme.corp.payments.PaymentStateMachine";

        boolean result = engine.isRoutedToCorrectMachine(chain, machineName, null);

        // Mismatched domains, but explicit variable targeting works
        assertTrue(result, "Explicit variable target matching the machine name should be accepted");
    }

    @Test
    public void testExplicitTargetVariableMismatch() {
        CallChain chain = CallChain.builder()
                .methodChain(Arrays.asList("com.acme.common.GlobalTrigger.fire()"))
                .contextMachineId("paymentStateMachine") // Explicitly targets payment
                .build();

        String machineName = "com.acme.corp.orders.OrderStateMachine"; // But this is Order

        boolean result = engine.isRoutedToCorrectMachine(chain, machineName, null);

        // Explicit variable targeting completely conflicts
        assertFalse(result, "Explicit variable target mismatching the machine name should be rejected");
    }

    @Test
    public void testGenericStateMachineConfigInGenericPackage() {
        CallChain chain = CallChain.builder()
                .methodChain(Arrays.asList("com.acme.corp.orders.OrderController.submit()"))
                .build();

        String machineName = "com.acme.corp.config.StateMachineConfig";

        boolean result = engine.isRoutedToCorrectMachine(chain, machineName, null);

        // StateMachineConfig has generic prefix and is in config package. OrderController is in orders.
        // There should be no domain mismatch because config/statemachine are generic.
        assertTrue(result, "Generic state machine config in generic config package should not trigger mismatch");
    }

    @Test
    public void testNoMismatchOnStateMachineConfigSuffix() {
        CallChain chain = CallChain.builder()
                .methodChain(Arrays.asList("com.acme.shipping.service.OrderServiceImpl.place()"))
                .build();

        String machineName = "com.acme.shipping.config.MyStateMachineConfig";

        boolean result = engine.isRoutedToCorrectMachine(chain, machineName, null);
        assertTrue(result, "MyStateMachineConfig should not trigger mismatch because it contains StateMachineConfig");
    }

    @Test
    void shouldRejectAmbiguousChainWhenMultipleStateMachinesExist(@TempDir Path tempDir) throws Exception {
        Files.writeString(tempDir.resolve("OrderConfig.java"), """
                package com.example;
                @org.springframework.statemachine.config.EnableStateMachine
                public class OrderStateMachineConfig
                        extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<OrderStates, OrderEvents> {}
                enum OrderStates { S1 }
                enum OrderEvents { E1 }
                """);
        Files.writeString(tempDir.resolve("DocumentConfig.java"), """
                package com.example;
                @org.springframework.statemachine.config.EnableStateMachine
                public class DocumentStateMachineConfig
                        extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<DocumentStates, DocumentEvents> {}
                enum DocumentStates { D1 }
                enum DocumentEvents { D1E }
                """);

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

        CallChain chain = CallChain.builder()
                .methodChain(Arrays.asList("com.example.shared.GlobalGateway.fire()"))
                .build();

        assertFalse(
                engine.isRoutedToCorrectMachine(chain, "com.example.OrderStateMachineConfig", context),
                "Ambiguous shared gateway should not attach to every machine");
        assertFalse(
                engine.isRoutedToCorrectMachine(chain, "com.example.DocumentStateMachineConfig", context),
                "Ambiguous shared gateway should not attach to every machine");
    }

    @Test
    void shouldResolveTriggerStateMachineIdAgainstEnableStateMachineName(@TempDir Path tempDir) throws Exception {
        Files.writeString(tempDir.resolve("PaymentConfig.java"), """
                package com.example;
                @org.springframework.statemachine.config.EnableStateMachine(name = "paymentStateMachine")
                public class PaymentStateMachineConfig
                        extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<String, String> {}
                """);
        Files.writeString(tempDir.resolve("OrderConfig.java"), """
                package com.example;
                @org.springframework.statemachine.config.EnableStateMachine(name = "orderStateMachine")
                public class OrderStateMachineConfig
                        extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<String, String> {}
                """);

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

        CallChain chain = CallChain.builder()
                .triggerPoint(TriggerPoint.builder()
                        .stateMachineId("paymentStateMachine")
                        .build())
                .methodChain(List.of("com.example.PaymentService.capture()"))
                .build();

        assertTrue(engine.isRoutedToCorrectMachine(chain, "com.example.PaymentStateMachineConfig", context));
        assertFalse(engine.isRoutedToCorrectMachine(chain, "com.example.OrderStateMachineConfig", context));
    }

    @Test
    void shouldAcceptTriggerWhenSpringErasureDiffersFromStringMachineTypes(@TempDir Path tempDir) throws Exception {
        Files.writeString(tempDir.resolve("InheritanceStateMachineConfig.java"), """
                package com.example;
                @org.springframework.statemachine.config.EnableStateMachine
                public class InheritanceStateMachineConfig
                        extends org.springframework.statemachine.config.StateMachineConfigurerAdapter<String, String> {}
                """);
        Files.writeString(tempDir.resolve("ProcessingServiceImpl.java"), """
                package com.example;
                public class ProcessingServiceImpl {
                    public void doProcess() {}
                }
                """);

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

        CallChain triggerProbe = CallChain.builder()
                .triggerPoint(TriggerPoint.builder()
                        .className("com.example.ProcessingServiceImpl")
                        .methodName("doProcess")
                        .event("INHERITED_SUBMIT")
                        .stateTypeFqn("java.lang.Object")
                        .eventTypeFqn("java.lang.Object")
                        .build())
                .methodChain(List.of())
                .build();

        assertTrue(engine.isRoutedToCorrectMachine(
                triggerProbe, "com.example.InheritanceStateMachineConfig", context));
    }
}
