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 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 shouldMatchWildcards() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder().event("event").build();

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

    @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 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 shouldMatchValueOfWithCorrectEnum() {
        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)).isTrue();
    }

    @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 shouldMatchDynamicVariableWithMatchingTypeFqn() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("myCustomEvt") // Not "event", "e", etc., but a custom variable name
                .eventTypeFqn("com.example.OrderEvents")
                .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 shouldMatchDynamicVariableWithUnknownTypeFqn() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("myCustomEvt")
                .eventTypeFqn(null) // Unknown type, fallback to true
                .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 shouldMatchDynamicMethodCallAsVariable() {
        Event smEvent = Event.of("PAY", "com.example.OrderEvents.PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder()
                .event("event.getPayload()") // Dynamic method call
                .eventTypeFqn("com.example.OrderEvents")
                .build();

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