package click.kamil.springstatemachineexporter.analysis.enricher;

import click.kamil.springstatemachineexporter.analysis.model.LinkResolution;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import org.junit.jupiter.api.Test;

import java.util.List;

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

class CallChainLinkPolicyTest {

    @Test
    void shouldFailClosedOnEnumSetAmbiguousWiden() {
        TriggerPoint trigger = TriggerPoint.builder()
                .ambiguous(true)
                .event("ENUM_SET:com.example.OrderEvent.PAY,com.example.OrderEvent.SHIP")
                .polymorphicEvents(List.of("com.example.OrderEvent.PAY", "com.example.OrderEvent.SHIP"))
                .build();

        assertThat(CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden(trigger)).isTrue();
        assertThat(CallChainLinkPolicy.resolveLinkResolution(trigger, List.of(), false))
                .isEqualTo(LinkResolution.AMBIGUOUS_WIDEN);
    }

    @Test
    void shouldMarkExternalTriggersUnresolved() {
        TriggerPoint trigger = TriggerPoint.builder()
                .external(true)
                .event("eventString")
                .build();

        assertThat(CallChainLinkPolicy.resolveLinkResolution(trigger, List.of(), false))
                .isEqualTo(LinkResolution.UNRESOLVED_EXTERNAL);
    }

    @Test
    void shouldPreferMatchesOverExternalFlag() {
        TriggerPoint trigger = TriggerPoint.builder()
                .external(true)
                .event("OrderEvent.PAY")
                .polymorphicEvents(List.of("OrderEvent.PAY"))
                .build();

        var matched = List.of(
                click.kamil.springstatemachineexporter.analysis.model.MatchedTransition.builder()
                        .event("com.example.order.OrderEvent.PAY")
                        .sourceState("com.example.order.OrderState.NEW")
                        .build());

        assertThat(CallChainLinkPolicy.resolveLinkResolution(trigger, matched, false))
                .isEqualTo(LinkResolution.RESOLVED);
    }

    @Test
    void shouldFailClosedOnValueOfAmbiguousWiden() {
        TriggerPoint trigger = TriggerPoint.builder()
                .ambiguous(true)
                .event("OrderEvent.valueOf(eventStr)")
                .polymorphicEvents(List.of("a.OrderEvent.PAY", "a.OrderEvent.SHIP"))
                .build();

        assertThat(CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden(trigger)).isTrue();
        assertThat(CallChainLinkPolicy.resolveLinkResolution(trigger, List.of(), false))
                .isEqualTo(LinkResolution.AMBIGUOUS_WIDEN);
    }

    @Test
    void shouldAllowEndpointNarrowSingleConcretePoly() {
        TriggerPoint trigger = TriggerPoint.builder()
                .ambiguous(true)
                .eventTypeFqn("com.example.OrderEvent")
                .event("OrderEvent.valueOf(eventStr)")
                .polymorphicEvents(List.of("com.example.OrderEvent.PAY"))
                .build();

        assertThat(CallChainLinkPolicy.isEndpointNarrowPolyEvidence(trigger)).isTrue();
        assertThat(CallChainLinkPolicy.isTrustedEnumPolymorphicWiden(trigger)).isTrue();
        assertThat(CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden(trigger)).isFalse();
    }

    @Test
    void shouldFailClosedWhenMultipleConcretePolyCandidatesRemain() {
        TriggerPoint trigger = TriggerPoint.builder()
                .ambiguous(false) // even when ambiguous flag was cleared upstream
                .event("OrderEvent.valueOf(eventStr)")
                .polymorphicEvents(List.of("com.example.OrderEvent.PAY", "com.example.OrderEvent.SHIP"))
                .build();

        assertThat(CallChainLinkPolicy.isEndpointNarrowPolyEvidence(
                trigger, "com.example.OrderEvent")).isFalse();
        assertThat(CallChainLinkPolicy.isTrustedEnumPolymorphicWiden(
                trigger, "com.example.OrderEvent")).isTrue();
        assertThat(CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden(
                trigger, "com.example.OrderEvent")).isTrue();
    }

    @Test
    void shouldResolveLinkWhenEndpointNarrowPolyHasMatches() {
        TriggerPoint trigger = TriggerPoint.builder()
                .ambiguous(true)
                .event("OrderEvent.valueOf(eventStr)")
                .polymorphicEvents(List.of("OrderEvent.PAY"))
                .build();

        var matched = List.of(
                click.kamil.springstatemachineexporter.analysis.model.MatchedTransition.builder()
                        .event("com.example.order.OrderEvent.PAY")
                        .build());

        assertThat(CallChainLinkPolicy.resolveLinkResolution(
                trigger,
                matched,
                false,
                "com.example.order.OrderEvent")).isEqualTo(LinkResolution.RESOLVED);
    }

    @Test
    void shouldNotResolveWhenBroadPolyWidenHasMatches() {
        TriggerPoint trigger = TriggerPoint.builder()
                .ambiguous(true)
                .event("OrderEvent.valueOf(eventStr)")
                .polymorphicEvents(List.of("OrderEvent.PAY", "OrderEvent.SHIP"))
                .build();

        var matched = List.of(
                click.kamil.springstatemachineexporter.analysis.model.MatchedTransition.builder()
                        .event("com.example.order.OrderEvent.PAY")
                        .build());

        assertThat(CallChainLinkPolicy.resolveLinkResolution(
                trigger,
                matched,
                false,
                "com.example.order.OrderEvent")).isEqualTo(LinkResolution.AMBIGUOUS_WIDEN);
    }

    @Test
    void shouldTrustImportStyleSingleConcretePolyWhenMachineEventTypeIsKnown() {
        TriggerPoint trigger = TriggerPoint.builder()
                .ambiguous(true)
                .event("OrderEvent.valueOf(eventStr)")
                .polymorphicEvents(List.of("OrderEvent.PAY"))
                .build();

        assertThat(CallChainLinkPolicy.isTrustedEnumPolymorphicWiden(
                trigger, "com.example.order.OrderEvent")).isTrue();
        assertThat(CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden(
                trigger, "com.example.order.OrderEvent")).isFalse();
    }

    @Test
    void shouldTrustBareConstantPolymorphicCandidatesWhenMachineEventTypeIsKnown() {
        TriggerPoint trigger = TriggerPoint.builder()
                .ambiguous(true)
                .event("OrderEvent.valueOf(eventStr)")
                .polymorphicEvents(List.of("PAY", "SHIP"))
                .build();

        assertThat(CallChainLinkPolicy.isTrustedEnumPolymorphicWiden(
                trigger, "com.example.order.OrderEvent")).isTrue();
        // Multiple concrete events on a dynamic trigger still fail-closed for linking.
        assertThat(CallChainLinkPolicy.shouldFailClosedOnAmbiguousCallGraphWiden(
                trigger, "com.example.order.OrderEvent")).isTrue();
    }

    @Test
    void shouldPreferMatchedTransitionsOverSoftAmbiguousSource() {
        TriggerPoint trigger = TriggerPoint.builder()
                .event("OrderEvent.PAY")
                .polymorphicEvents(List.of("OrderEvent.PAY"))
                .build();

        var matched = List.of(
                click.kamil.springstatemachineexporter.analysis.model.MatchedTransition.builder()
                        .event("com.example.order.OrderEvent.PAY")
                        .sourceState("com.example.order.OrderState.NEW")
                        .build(),
                click.kamil.springstatemachineexporter.analysis.model.MatchedTransition.builder()
                        .event("com.example.order.OrderEvent.PAY")
                        .sourceState("com.example.order.OrderState.PENDING")
                        .build());

        assertThat(CallChainLinkPolicy.resolveLinkResolution(
                trigger, matched, true, "com.example.order.OrderEvent"))
                .isEqualTo(LinkResolution.RESOLVED);
    }
}
