package click.kamil.springstatemachineexporter.analysis.resolver;

import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.StateMachineTypeResolver.MachineTypes;
import click.kamil.springstatemachineexporter.model.Event;
import click.kamil.springstatemachineexporter.model.State;
import click.kamil.springstatemachineexporter.model.Transition;
import org.junit.jupiter.api.Test;

import java.util.List;

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

class MachineEnumCanonicalizerBoundValueOfTest {

    @Test
    void shouldExpandSingleBoundEventLiteralForValueOfTrigger() {
        TriggerPoint trigger = TriggerPoint.builder()
                .event("OrderEvent.valueOf(eventString.toUpperCase())")
                .constraint("\"ORDER\".equalsIgnoreCase(machineType) && \"PAY\".equalsIgnoreCase(event)")
                .external(true)
                .build();
        MachineTypes machineTypes =
                new MachineTypes(
                        "com.example.order.OrderState",
                        "com.example.order.OrderEvent");

        TriggerPoint expanded = MachineEnumCanonicalizer.expandBoundValueOfFromConstraints(
                trigger, machineTypes, null);

        assertThat(expanded.getPolymorphicEvents())
                .containsExactly("com.example.order.OrderEvent.PAY");
        assertThat(expanded.isExternal()).isFalse();
        assertThat(expanded.isAmbiguous()).isFalse();
    }

    @Test
    void shouldExpandBoundValueOfFromEqCompareConstraint() {
        TriggerPoint trigger = TriggerPoint.builder()
                .event("OrderEvent.valueOf(message)")
                .constraint("message == \"PAY\"")
                .external(true)
                .build();
        MachineTypes machineTypes = new MachineTypes(
                "com.example.order.OrderState",
                "com.example.order.OrderEvent");

        TriggerPoint expanded = MachineEnumCanonicalizer.expandBoundValueOfFromConstraints(
                trigger, machineTypes, null);

        assertThat(expanded.getPolymorphicEvents())
                .containsExactly("com.example.order.OrderEvent.PAY");
        assertThat(expanded.isExternal()).isFalse();
    }

    @Test
    void shouldWidenUnboundValueOfToTransitionEvents() {
        TriggerPoint trigger = TriggerPoint.builder()
                .event("OrderEvent.valueOf(message)")
                .external(true)
                .build();
        MachineTypes machineTypes = new MachineTypes(
                "com.example.order.OrderState",
                "com.example.order.OrderEvent");
        List<Transition> transitions = List.of(
                transition("PAY", "com.example.order.OrderEvent.PAY"),
                transition("SHIP", "com.example.order.OrderEvent.SHIP"));

        TriggerPoint expanded = MachineEnumCanonicalizer.ensureCallChainPolymorphicEvents(
                trigger, machineTypes, null, transitions, true);

        assertThat(expanded.getPolymorphicEvents())
                .containsExactly(
                        "com.example.order.OrderEvent.PAY",
                        "com.example.order.OrderEvent.SHIP");
        assertThat(expanded.isAmbiguous()).isTrue();
    }

    @Test
    void shouldExpandBoundValueOfFromPathBindingsAlone() {
        TriggerPoint trigger = TriggerPoint.builder()
                .event("OrderEvent.valueOf(message)")
                .external(true)
                .build();
        MachineTypes machineTypes = new MachineTypes(
                "com.example.order.OrderState",
                "com.example.order.OrderEvent");

        TriggerPoint expanded = MachineEnumCanonicalizer.expandBoundValueOfFromConstraints(
                trigger,
                machineTypes,
                null,
                java.util.Map.of("message", "PAY"));

        assertThat(expanded.getPolymorphicEvents())
                .containsExactly("com.example.order.OrderEvent.PAY");
        assertThat(expanded.isExternal()).isFalse();
    }

    @Test
    void prepareCallChainTriggerBindsFromPathBindingsAndBlocksWiden() {
        TriggerPoint trigger = TriggerPoint.builder()
                .event("OrderEvent.valueOf(message)")
                .external(true)
                .build();
        MachineTypes machineTypes = new MachineTypes(
                "com.example.order.OrderState",
                "com.example.order.OrderEvent");
        List<Transition> transitions = List.of(
                transition("PAY", "com.example.order.OrderEvent.PAY"),
                transition("SHIP", "com.example.order.OrderEvent.SHIP"));

        TriggerPoint prepared = MachineEnumCanonicalizer.prepareCallChainTrigger(
                trigger,
                machineTypes,
                null,
                transitions,
                java.util.Map.of("message", "PAY"));

        assertThat(prepared.getPolymorphicEvents())
                .containsExactly("com.example.order.OrderEvent.PAY");
        assertThat(prepared.isAmbiguous()).isFalse();
    }

    @Test
    void shouldExpandBoundValueOfFromObjectsEqualsConstraint() {
        TriggerPoint trigger = TriggerPoint.builder()
                .event("OrderEvent.valueOf(message)")
                .constraint("Objects.equals(\"PAY\", message)")
                .external(true)
                .build();
        MachineTypes machineTypes = new MachineTypes(
                "com.example.order.OrderState",
                "com.example.order.OrderEvent");

        TriggerPoint expanded = MachineEnumCanonicalizer.expandBoundValueOfFromConstraints(
                trigger, machineTypes, null);

        assertThat(expanded.getPolymorphicEvents())
                .containsExactly("com.example.order.OrderEvent.PAY");
    }

    private static Transition transition(String raw, String full) {
        Transition t = new Transition();
        t.setEvent(Event.of(raw, full));
        t.setSourceStates(List.of(State.of("S1", "S1")));
        t.setTargetStates(List.of(State.of("S2", "S2")));
        return t;
    }
}
