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 HeuristicEventMatchingEngineTest {

    private final HeuristicEventMatchingEngine engine = new HeuristicEventMatchingEngine();

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

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

    @Test
    void shouldMatchBidirectionalFqnAndShortName() {
        // Case 1: smEvent is short, trigger event is FQN
        Event smEvent1 = Event.of("PAY", "OrderEvents.PAY");
        TriggerPoint triggerPoint1 = TriggerPoint.builder().event("com.example.OrderEvents.PAY").build();
        assertThat(engine.matches(smEvent1, triggerPoint1)).isTrue();

        // Case 2: smEvent is short, polymorphic event is FQN
        Event smEvent2 = Event.of("PAY", "OrderEvents.PAY");
        TriggerPoint triggerPoint2 = TriggerPoint.builder().event("getType()").polymorphicEvents(List.of("com.example.OrderEvents.PAY")).build();
        assertThat(engine.matches(smEvent2, triggerPoint2)).isTrue();
    }

    @Test
    void shouldMatchCaseInsensitive() {
        Event smEvent = Event.of("PAY", "PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder().event("pay").build();
        assertThat(engine.matches(smEvent, triggerPoint)).isTrue();
    }

    @Test
    void shouldMatchGuardedContains() {
        Event smEvent = Event.of("PAY", "PAY");
        TriggerPoint triggerPoint = TriggerPoint.builder().event("pay_order").build();
        assertThat(engine.matches(smEvent, triggerPoint)).isTrue();

        Event smEvent2 = Event.of("PAY_ORDER", "PAY_ORDER");
        TriggerPoint triggerPoint2 = TriggerPoint.builder().event("pay").build();
        assertThat(engine.matches(smEvent2, triggerPoint2)).isTrue();
    }

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

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

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

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

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

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

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

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