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

import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.model.Event;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.util.List;

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

class StrictFqnMatchingEngineTest {

    private final StrictFqnMatchingEngine engine = new StrictFqnMatchingEngine();

    @Test
    void shouldMatchExactFqn() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("PAY")
                .eventTypeFqn("com.example.OrderEvents")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
    }

    @Test
    void shouldMatchEnumQualifierToFqn() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("OrderEvents.PAY")
                .eventTypeFqn("com.example.OrderEvents")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
    }

    @Test
    void shouldPreventCrossEnumContamination() {
        Event smEvent = Event.of("PAY", "com.example.InvoiceEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("OrderEvents.PAY")
                .eventTypeFqn("com.example.OrderEvents")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldPreventCrossEnumContaminationWithPolymorphicEvents() {
        Event smEvent = Event.of("PAY", "com.example.InvoiceEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("getType()")
                .eventTypeFqn("com.example.OrderEvents")
                .polymorphicEvents(List.of("OrderEvents.PAY"))
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldMatchPolymorphicEnumQualifierToFqn() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("getType()")
                .eventTypeFqn("com.example.OrderEvents")
                .polymorphicEvents(List.of("OrderEvents.PAY"))
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
    }

    @Test
    void shouldMatchRawStringToFqn() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("PAY")
                .eventTypeFqn("com.example.OrderEvents")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
    }

    @Test
    void shouldMatchRawStringToRawString() {
        Event smEvent = Event.of("PAY", "PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder().event("PAY").build();

        assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
    }

    @Test
    void shouldMatchPolymorphicRawStringToFqn() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("getType()")
                .eventTypeFqn("com.example.OrderEvents")
                .polymorphicEvents(List.of("PAY"))
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
    }

    @Test
    void shouldNotMatchWildcardAgainstEnumTransitionWithoutPolymorphicEvents() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder().event("event").build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldNotMatchDynamicMethodCallWithoutResolvedPolymorphicEvents() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("event.getPayload()")
                .eventTypeFqn("com.example.OrderEvents")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldNotMatchMismatchedFqn() {
        Event smEvent = Event.of("CREATE", "com.example.other.OrderEvent.CREATE");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("CREATE")
                .eventTypeFqn("com.example.some.OrderEvent")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldMatchStringLiteralTriggerToConfiguredStringTransition() {
        Event smEvent = Event.of("\"AUDIT_EVENT\"", "String.AUDIT_EVENT");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("AUDIT_EVENT")
                .eventTypeFqn("java.lang.String")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
    }

    @Test
    void shouldMatchQualifiedStringTriggerToBareConfiguredStringTransition() {
        Event smEvent = Event.of("\"INHERITED_SUBMIT\"", "INHERITED_SUBMIT");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("String.INHERITED_SUBMIT")
                .eventTypeFqn("String")
                .polymorphicEvents(List.of("String.INHERITED_SUBMIT"))
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
    }

    @Test
    void shouldMatchQualifiedStringTriggerToBareWhenEventTypeFqnMissing() {
        Event smEvent = Event.of("ORDER_EVENT", "ORDER_EVENT");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("String.ORDER_EVENT")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
    }

    @Test
    void shouldNotMatchEnumQualifiedTriggerToBareConstant() {
        Event smEvent = Event.of("PAY", "PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("OrderEvent.PAY")
                .eventTypeFqn("com.example.OrderEvent")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldNotMatchEnumQualifiedTriggerToBareWhenEventTypeFqnMissing() {
        Event smEvent = Event.of("PAY", "PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("OrderEvent.PAY")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldMatchCanonicalStringLiteralTriggerToConfiguredStringTransition() {
        Event smEvent = Event.of("\"AUDIT_EVENT\"", "String.AUDIT_EVENT");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("java.lang.String.AUDIT_EVENT")
                .eventTypeFqn("java.lang.String")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
    }

    @Test
    void shouldNotMatchStringTypeToEnumFqn() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("PAY")
                .eventTypeFqn("java.lang.String")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldNotMatchEnumTypeToStringFqn() {
        Event smEvent = Event.of("PAY", "PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("OrderEvents.PAY")
                .eventTypeFqn("com.example.OrderEvents")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldMatchValueOfWithCorrectEnumWhenPolymorphicEventIsResolved() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("OrderEvents.valueOf(str)")
                .eventTypeFqn("com.example.OrderEvents")
                .polymorphicEvents(List.of("OrderEvents.PAY"))
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
    }

    @Test
    void shouldNotMatchUnresolvedValueOfWithoutPolymorphicEvents() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("OrderEvents.valueOf(str)")
                .eventTypeFqn("com.example.OrderEvents")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldNotMatchExternalTriggerWithoutPolymorphicEvents() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("event")
                .external(true)
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldNotMatchValueOfWithIncorrectEnum() {
        Event smEvent = Event.of("PAY", "com.example.InvoiceEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("OrderEvents.valueOf(str)")
                .eventTypeFqn("com.example.OrderEvents")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldNotMatchDynamicVariableWithoutResolvedPolymorphicEvents() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("myCustomEvt")
                .eventTypeFqn("com.example.OrderEvents")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldMatchDynamicVariableWhenPolymorphicEventsAreResolved() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("myCustomEvt")
                .eventTypeFqn("com.example.OrderEvents")
                .polymorphicEvents(List.of("com.example.OrderEvents.PAY"))
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
    }

    @Test
    void shouldNotMatchDynamicVariableWithMismatchedTypeFqn() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("myCustomEvt")
                .eventTypeFqn("com.example.InvoiceEvents") // Mismatched type
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldNotMatchDynamicVariableWithUnknownTypeFqnWhenSmEventIsEnum() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("myCustomEvt")
                .eventTypeFqn(null)
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldMatchDynamicVariableWithUnknownTypeFqnForStringEvents() {
        Event smEvent = Event.of("PAY", "PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("myCustomEvt")
                .eventTypeFqn(null)
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
    }

    @Test
    void shouldNotMatchBareStrippedConstantToAnyEnumWithoutTypeContext() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("PAY")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldNotMatchSameEnumSimpleNameFromDifferentPackages() {
        Event smEvent = Event.of("PAY", "com.example.other.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("OrderEvents.PAY")
                .eventTypeFqn("com.example.OrderEvents")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldNotMatchPolymorphicStrippedConstantWithoutTypeContext() {
        Event smEvent = Event.of("PAY", "com.example.InvoiceEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("getType()")
                .polymorphicEvents(List.of("PAY"))
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldNotMatchGetterChainWithoutPolymorphicEventsAgainstBareStringEvent() {
        Event smEvent = Event.of("PAY", "PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("richEvent.getId()")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldNotMatchGetterChainWithoutPolymorphicEventsAgainstEnumEvent() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("payload.getType()")
                .eventTypeFqn("com.example.OrderEvents")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldNotMatchMethodCallWithoutPolymorphicEventsAgainstBareStringEvent() {
        Event smEvent = Event.of("PAY", "PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("getType()")
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isFalse();
    }

    @Test
    void shouldMatchImportStyleEnumToMachineFqnWhenSimpleNamesAlign(@TempDir java.nio.file.Path tempDir) throws Exception {
        java.nio.file.Files.createDirectories(tempDir.resolve("a"));
        java.nio.file.Files.createDirectories(tempDir.resolve("b"));
        java.nio.file.Files.writeString(tempDir.resolve("a/OrderEvent.java"),
                "package a; public enum OrderEvent { PAY }");
        java.nio.file.Files.writeString(tempDir.resolve("b/OrderEvent.java"),
                "package b; public enum OrderEvent { PAY }");

        click.kamil.springstatemachineexporter.ast.common.CodebaseContext context =
                new click.kamil.springstatemachineexporter.ast.common.CodebaseContext();
        context.scan(tempDir);

        StrictFqnMatchingEngine engine = new StrictFqnMatchingEngine(context);
        Event smEvent = Event.of("PAY", "a.OrderEvent.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("OrderEvent.PAY")
                .polymorphicEvents(List.of("OrderEvent.PAY"))
                .build();

        assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
    }
}
