package click.kamil.springstatemachineexporter.analysis.resolver;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.util.Map;

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

class BooleanConstraintEvaluatorBindingsTest {

    @Test
    void shouldRejectEnumSetBindingAsConcreteForShipConstraint() {
        // ENUM_SET:…ORDER_SHIP must not prove command == ORDER_SHIP via trailing .CONST
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "command == ORDER_SHIP",
                Map.of("command", "ENUM_SET:DomainCommand.ORDER_PAY,DomainCommand.ORDER_SHIP")))
                .isTrue(); // unbound/token → constraint ignored (compatible), not uniquely SHIP
        // When treated as concrete endsWith, ship would falsely match; token rejection means
        // the variable stays unsubstituted — isCompatible returns true for unbound vars.
        // Prove it is NOT a unique concrete SHIP proof by also accepting PAY constraint:
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "command == ORDER_PAY",
                Map.of("command", "ENUM_SET:DomainCommand.ORDER_PAY,DomainCommand.ORDER_SHIP")))
                .isTrue();
    }

    @Test
    void shouldAcceptMatchingEnumSwitchConstraint() {
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "command == ORDER_PAY",
                Map.of("command", "DomainCommand.ORDER_PAY"))).isTrue();
    }

    @Test
    void shouldRejectMismatchedEnumSwitchConstraint() {
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "command == ORDER_SHIP",
                Map.of("command", "DomainCommand.ORDER_PAY"))).isFalse();
    }

    @Test
    void shouldAcceptMatchingStringSwitchConstraint() {
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "commandKey == \"order.pay\"",
                Map.of("commandKey", "order.pay"))).isTrue();
    }

    @Test
    void shouldRejectCrossPackageQualifiedEnumSwitchConstraint() {
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "command == a.OrderEvent.PAY",
                Map.of("command", "b.OrderEvent.PAY"))).isFalse();
    }

    @Test
    void shouldAcceptPackageQualifiedRhsAgainstImportStyleBinding() {
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "command == com.example.DomainCommand.ORDER_PAY",
                Map.of("command", "DomainCommand.ORDER_PAY"))).isTrue();
    }

    @Test
    void shouldRejectConstraintWhenBindingIsMissing() {
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "command == ORDER_PAY",
                Map.of("commandKey", "order.pay"))).isFalse();
    }

    @Test
    void shouldEvaluateEqualsIgnoreCaseWithHelperWrappedParameter() {
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "\"ORDER\".equalsIgnoreCase(normalizeType(machineType))",
                Map.of("machineType", "ORDER"))).isTrue();
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "\"ORDER\".equalsIgnoreCase(normalizeType(machineType))",
                Map.of("machineType", "DOCUMENT"))).isFalse();
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "\"DOCUMENT\".equalsIgnoreCase(normalizeType(machineType))",
                Map.of("machineType", "DOCUMENT"))).isTrue();
    }

    @Test
    void shouldEvaluateObjectsEqualsWithHelperWrappedParameter() {
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "java.util.Objects.equals(\"ORDER\", normalizeType(machineType))",
                Map.of("machineType", "ORDER"))).isTrue();
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "java.util.Objects.equals(\"ORDER\", normalizeType(machineType))",
                Map.of("machineType", "DOCUMENT"))).isFalse();
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "java.util.Objects.equals(normalizeType(machineType), \"DOCUMENT\")",
                Map.of("machineType", "DOCUMENT"))).isTrue();
    }

    @Test
    void shouldNotThrowWhenBindingValueEndsWithDot() {
        // Unresolved/truncated FQNs can end with '.' — must not charAt past the end,
        // and must not be treated as concrete (which would force a false match).
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "command == ORDER_PAY",
                Map.of("command", "com.example.DomainCommand."))).isTrue();
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "key == \"x\"",
                Map.of("key", "."))).isTrue();
    }

    @Test
    void shouldRejectIncompleteDottedNamesAsConcreteBindings() {
        assertThat(BooleanConstraintEvaluator.isIncompleteDottedName("com.example.DomainCommand.")).isTrue();
        assertThat(BooleanConstraintEvaluator.isIncompleteDottedName(".PAY")).isTrue();
        assertThat(BooleanConstraintEvaluator.isIncompleteDottedName("com..DomainCommand.PAY")).isTrue();
        assertThat(BooleanConstraintEvaluator.isIncompleteDottedName("com.example.DomainCommand.PAY")).isFalse();
        assertThat(BooleanConstraintEvaluator.isIncompleteDottedName("order.pay")).isFalse();

        // Incomplete values must not be treated as concrete routing keys.
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "command == ORDER_PAY",
                Map.of("command", "com.example.DomainCommand."))).isTrue();
    }

    @Test
    void shouldReconstructTruncatedEnumBindingWithPreferredType(@TempDir java.nio.file.Path tempDir)
            throws Exception {
        java.nio.file.Path dir = tempDir.resolve("com/example");
        java.nio.file.Files.createDirectories(dir);
        java.nio.file.Files.writeString(dir.resolve("OrderEvent.java"),
                "package com.example;\npublic enum OrderEvent { PAY, SHIP }\n");
        java.nio.file.Files.writeString(dir.resolve("DocumentEvent.java"),
                "package com.example;\npublic enum DocumentEvent { PAY, ARCHIVE }\n");

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

        // Without preferred type, truncated binding is not concrete → constraint left unevaluated (true).
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "event == PAY",
                Map.of("event", "com.example.PAY"),
                null,
                context)).isTrue();

        // With preferred type, truncated ref becomes OrderEvent.PAY and matches.
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "event == PAY",
                Map.of("event", "com.example.PAY"),
                "com.example.OrderEvent",
                context)).isTrue();
        assertThat(BooleanConstraintEvaluator.isCompatibleWithBindings(
                "event == SHIP",
                Map.of("event", "com.example.PAY"),
                "com.example.OrderEvent",
                context)).isFalse();
    }
}
