package click.kamil.springstatemachineexporter.ast.common;

import org.eclipse.jdt.core.dom.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

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

class JdtDataFlowModelTest {

    @Test
    void testRecursiveJdtDataFlowResolution(@TempDir Path tempDir) throws IOException {
        Path comFoo = tempDir.resolve("com/foo");
        Files.createDirectories(comFoo);
        Files.writeString(comFoo.resolve("Test.java"),
            "package com.foo;\n" +
            "public class Test {\n" +
            "    public void run() {\n" +
            "        String a = \"VAL1\";\n" +
            "        String b = a;\n" +
            "        String c = b;\n" +
            "    }\n" +
            "}"
        );

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
        assertThat(td).isNotNull();

        MethodDeclaration md = td.getMethods()[0];
        VariableDeclarationStatement cStmt = (VariableDeclarationStatement) md.getBody().statements().get(2);
        VariableDeclarationFragment cFrag = (VariableDeclarationFragment) cStmt.fragments().get(0);
        Expression initializer = cFrag.getInitializer(); // b

        DataFlowModel model = new JdtDataFlowModel(context);

        List<Expression> defs = model.getReachingDefinitions(initializer);
        assertThat(defs).isNotEmpty();
        
        Expression singleDef = defs.get(defs.size() - 1);
        assertThat(singleDef).isInstanceOf(StringLiteral.class);
        assertThat(((StringLiteral) singleDef).getLiteralValue()).isEqualTo("VAL1");

        String val = model.resolveValue(initializer, context);
        assertThat(val).isEqualTo("VAL1");
    }

    @Test
    void testBranchDivergenceJdtDataFlowResolution(@TempDir Path tempDir) throws IOException {
        Path comFoo = tempDir.resolve("com/foo");
        Files.createDirectories(comFoo);
        Files.writeString(comFoo.resolve("Test.java"),
            "package com.foo;\n" +
            "public class Test {\n" +
            "    public void run(boolean flag) {\n" +
            "        String a = \"SAME\";\n" +
            "        if (flag) {\n" +
            "            a = \"SAME\";\n" +
            "        } else {\n" +
            "            a = \"SAME\";\n" +
            "        }\n" +
            "        String b = a;\n" +
            "    }\n" +
            "}"
        );

        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
        assertThat(td).isNotNull();

        MethodDeclaration md = td.getMethods()[0];
        VariableDeclarationStatement bStmt = (VariableDeclarationStatement) md.getBody().statements().get(2);
        VariableDeclarationFragment bFrag = (VariableDeclarationFragment) bStmt.fragments().get(0);
        Expression initializer = bFrag.getInitializer(); // a

        DataFlowModel model = new JdtDataFlowModel(context);

        String val = model.resolveValue(initializer, context);
        // Both branches assign "SAME", so it converges to "SAME"
        assertThat(val).isEqualTo("SAME");
    }

    @Test
    void testFieldConstantPropagation(@TempDir Path tempDir) throws IOException {
        Path comFoo = tempDir.resolve("com/foo");
        Files.createDirectories(comFoo);
        Files.writeString(comFoo.resolve("Test.java"),
            "package com.foo;\n" +
            "public class Test {\n" +
            "    private static final String MY_CONST = \"CONST_VAL\";\n" +
            "    public void run() {\n" +
            "        String a = MY_CONST;\n" +
            "    }\n" +
            "}"
        );

        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
        context.scan(tempDir);

        TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
        assertThat(td).isNotNull();

        MethodDeclaration md = td.getMethods()[0];
        VariableDeclarationStatement aStmt = (VariableDeclarationStatement) md.getBody().statements().get(0);
        VariableDeclarationFragment aFrag = (VariableDeclarationFragment) aStmt.fragments().get(0);
        Expression initializer = aFrag.getInitializer();

        DataFlowModel model = new JdtDataFlowModel(context);
        String val = model.resolveValue(initializer, context);
        assertThat(val).isEqualTo("CONST_VAL");
    }

    @Test
    void testTernaryExpressionResolution(@TempDir Path tempDir) throws IOException {
        Path comFoo = tempDir.resolve("com/foo");
        Files.createDirectories(comFoo);
        Files.writeString(comFoo.resolve("Test.java"),
            "package com.foo;\n" +
            "public class Test {\n" +
            "    public void run(boolean flag) {\n" +
            "        String a = flag ? \"TRUE_VAL\" : \"FALSE_VAL\";\n" +
            "        String b = true ? \"TRUE_VAL\" : \"FALSE_VAL\";\n" +
            "    }\n" +
            "}"
        );

        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
        context.scan(tempDir);

        TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
        assertThat(td).isNotNull();

        MethodDeclaration md = td.getMethods()[0];
        
        // 1. Unresolvable condition (flag)
        VariableDeclarationStatement aStmt = (VariableDeclarationStatement) md.getBody().statements().get(0);
        VariableDeclarationFragment aFrag = (VariableDeclarationFragment) aStmt.fragments().get(0);
        Expression initA = aFrag.getInitializer();

        DataFlowModel model = new JdtDataFlowModel(context);
        List<Expression> defsA = model.getReachingDefinitions(initA);
        assertThat(defsA).hasSize(1);
        assertThat(defsA.get(0)).isInstanceOf(ConditionalExpression.class);

        // 2. Statically resolvable condition (true)
        VariableDeclarationStatement bStmt = (VariableDeclarationStatement) md.getBody().statements().get(1);
        VariableDeclarationFragment bFrag = (VariableDeclarationFragment) bStmt.fragments().get(0);
        Expression initB = bFrag.getInitializer();

        List<Expression> defsB = model.getReachingDefinitions(initB);
        assertThat(defsB).hasSize(1);
        assertThat(defsB.get(0)).isInstanceOf(StringLiteral.class);
        assertThat(((StringLiteral) defsB.get(0)).getLiteralValue()).isEqualTo("TRUE_VAL");
    }

    @Test
    void testStaticFactoryMethodTracking(@TempDir Path tempDir) throws IOException {
        Path comFoo = tempDir.resolve("com/foo");
        Files.createDirectories(comFoo);
        Files.writeString(comFoo.resolve("Test.java"),
            "package com.foo;\n" +
            "public class Test {\n" +
            "    public static String create(String value) {\n" +
            "        return value;\n" +
            "    }\n" +
            "    public void run() {\n" +
            "        String a = create(\"FACTORY_VAL\");\n" +
            "    }\n" +
            "}"
        );

        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
        context.scan(tempDir);

        TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
        assertThat(td).isNotNull();

        MethodDeclaration runMd = null;
        for (MethodDeclaration md : td.getMethods()) {
            if (md.getName().getIdentifier().equals("run")) {
                runMd = md;
            }
        }
        assertThat(runMd).isNotNull();

        VariableDeclarationStatement aStmt = (VariableDeclarationStatement) runMd.getBody().statements().get(0);
        VariableDeclarationFragment aFrag = (VariableDeclarationFragment) aStmt.fragments().get(0);
        Expression initializer = aFrag.getInitializer();

        DataFlowModel model = new JdtDataFlowModel(context);
        String val = model.resolveValue(initializer, context);
        assertThat(val).isEqualTo("FACTORY_VAL");
    }

    @Test
    void testDoWhileLoopFlow(@TempDir Path tempDir) throws IOException {
        Path comFoo = tempDir.resolve("com/foo");
        Files.createDirectories(comFoo);
        Files.writeString(comFoo.resolve("Test.java"),
            "package com.foo;\n" +
            "public class Test {\n" +
            "    public void run(boolean flag) {\n" +
            "        int a = 1;\n" +
            "        do {\n" +
            "            a = 2;\n" +
            "        } while (flag);\n" +
            "        int b = a;\n" +
            "    }\n" +
            "}"
        );

        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
        context.scan(tempDir);

        TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
        assertThat(td).isNotNull();

        MethodDeclaration md = td.getMethods()[0];
        ControlFlowGraph cfg = CfgBuilder.build(md);
        
        // Find the condition node (flag)
        ControlFlowGraph.CfgNode condNode = null;
        for (ControlFlowGraph.CfgNode node : cfg.getNodes()) {
            if (node.getAstNode() instanceof SimpleName sn && sn.getIdentifier().equals("flag")) {
                condNode = node;
                break;
            }
        }
        assertThat(condNode).isNotNull();

        // The condition node should loop back to the statement inside the body (a = 2;)
        boolean hasLoopBackToBody = false;
        for (ControlFlowGraph.CfgNode succ : condNode.getSuccessors()) {
            if (succ.getAstNode() instanceof ExpressionStatement es) {
                if (es.getExpression() instanceof Assignment assign) {
                    if (assign.getLeftHandSide() instanceof SimpleName sn && sn.getIdentifier().equals("a")) {
                        hasLoopBackToBody = true;
                    }
                }
            }
        }
        assertThat(hasLoopBackToBody).isTrue();
    }

    @Test
    void testConstructorAssignmentPropagation(@TempDir Path tempDir) throws IOException {
        Path comFoo = tempDir.resolve("com/foo");
        Files.createDirectories(comFoo);
        Files.writeString(comFoo.resolve("Test.java"),
            "package com.foo;\n" +
            "public class Test {\n" +
            "    private final String myVar;\n" +
            "    public Test() {\n" +
            "        this.myVar = \"CONSTRUCTOR_VAL\";\n" +
            "    }\n" +
            "    public void run() {\n" +
            "        String a = myVar;\n" +
            "    }\n" +
            "}"
        );

        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
        context.scan(tempDir);

        TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
        assertThat(td).isNotNull();

        MethodDeclaration md = null;
        for (MethodDeclaration m : td.getMethods()) {
            if (m.getName().getIdentifier().equals("run")) {
                md = m;
            }
        }
        assertThat(md).isNotNull();

        VariableDeclarationStatement aStmt = (VariableDeclarationStatement) md.getBody().statements().get(0);
        VariableDeclarationFragment aFrag = (VariableDeclarationFragment) aStmt.fragments().get(0);
        Expression initializer = aFrag.getInitializer();

        DataFlowModel model = new JdtDataFlowModel(context);
        String val = model.resolveValue(initializer, context);
        assertThat(val).isEqualTo("CONSTRUCTOR_VAL");
    }

    @Test
    void testObjectSensitiveInstanceFieldTracing(@TempDir Path tempDir) throws IOException {
        Path comFoo = tempDir.resolve("com/foo");
        Files.createDirectories(comFoo);
        Files.writeString(comFoo.resolve("Test.java"),
            "package com.foo;\n" +
            "public class Test {\n" +
            "    public static class Builder {\n" +
            "        private final String val;\n" +
            "        public Builder(String val) {\n" +
            "            this.val = val;\n" +
            "        }\n" +
            "        public String getVal() {\n" +
            "            return this.val;\n" +
            "        }\n" +
            "    }\n" +
            "    public void run() {\n" +
            "        Builder b = new Builder(\"OBJECT_SENSITIVE\");\n" +
            "        String res = b.getVal();\n" +
            "    }\n" +
            "}"
        );

        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
        context.scan(tempDir);

        TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
        assertThat(td).isNotNull();

        MethodDeclaration md = null;
        for (MethodDeclaration m : td.getMethods()) {
            if (m.getName().getIdentifier().equals("run")) {
                md = m;
            }
        }
        assertThat(md).isNotNull();

        VariableDeclarationStatement resStmt = (VariableDeclarationStatement) md.getBody().statements().get(1);
        VariableDeclarationFragment resFrag = (VariableDeclarationFragment) resStmt.fragments().get(0);
        Expression initializer = resFrag.getInitializer(); // b.getVal()

        DataFlowModel model = new JdtDataFlowModel(context);
        String val = model.resolveValue(initializer, context);
        assertThat(val).isEqualTo("OBJECT_SENSITIVE");
    }

    @Test
    void testConstructorChaining(@TempDir Path tempDir) throws IOException {
        Path comFoo = tempDir.resolve("com/foo");
        Files.createDirectories(comFoo);
        Files.writeString(comFoo.resolve("Test.java"),
            "package com.foo;\n" +
            "public class Test {\n" +
            "    public static class Command {\n" +
            "        private final String event;\n" +
            "        private final String info;\n" +
            "        public Command(String event, String info) {\n" +
            "            this.event = event;\n" +
            "            this.info = info;\n" +
            "        }\n" +
            "        public Command(String event) {\n" +
            "            this(event, \"DEFAULT_INFO\");\n" +
            "        }\n" +
            "        public Command() {\n" +
            "            this(\"DEFAULT_EVENT\");\n" +
            "        }\n" +
            "        public String getEvent() { return this.event; }\n" +
            "        public String getInfo() { return this.info; }\n" +
            "    }\n" +
            "    public void run() {\n" +
            "        Command cmd = new Command();\n" +
            "        String ev = cmd.getEvent();\n" +
            "        String inf = cmd.getInfo();\n" +
            "    }\n" +
            "}"
        );

        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
        context.scan(tempDir);

        TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
        assertThat(td).isNotNull();

        MethodDeclaration md = null;
        for (MethodDeclaration m : td.getMethods()) {
            if (m.getName().getIdentifier().equals("run")) {
                md = m;
            }
        }
        assertThat(md).isNotNull();

        VariableDeclarationStatement evStmt = (VariableDeclarationStatement) md.getBody().statements().get(1);
        VariableDeclarationFragment evFrag = (VariableDeclarationFragment) evStmt.fragments().get(0);
        Expression initEv = evFrag.getInitializer();

        DataFlowModel model = new JdtDataFlowModel(context);
        String valEv = model.resolveValue(initEv, context);
        assertThat(valEv).isEqualTo("DEFAULT_EVENT");

        VariableDeclarationStatement infStmt = (VariableDeclarationStatement) md.getBody().statements().get(2);
        VariableDeclarationFragment infFrag = (VariableDeclarationFragment) infStmt.fragments().get(0);
        Expression initInf = infFrag.getInitializer();

        String valInf = model.resolveValue(initInf, context);
        assertThat(valInf).isEqualTo("DEFAULT_INFO");
    }

    @Test
    void testExplicitSuperCalls(@TempDir Path tempDir) throws IOException {
        Path comFoo = tempDir.resolve("com/foo");
        Files.createDirectories(comFoo);
        Files.writeString(comFoo.resolve("Test.java"),
            "package com.foo;\n" +
            "public class Test {\n" +
            "    public static class Base {\n" +
            "        public String getValue() {\n" +
            "            return \"BASE_VAL\";\n" +
            "        }\n" +
            "    }\n" +
            "    public static class Sub extends Base {\n" +
            "        @Override\n" +
            "        public String getValue() {\n" +
            "            return super.getValue();\n" +
            "        }\n" +
            "    }\n" +
            "    public void run() {\n" +
            "        Sub sub = new Sub();\n" +
            "        String val = sub.getValue();\n" +
            "    }\n" +
            "}"
        );

        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
        context.scan(tempDir);

        TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
        assertThat(td).isNotNull();

        MethodDeclaration md = null;
        for (MethodDeclaration m : td.getMethods()) {
            if (m.getName().getIdentifier().equals("run")) {
                md = m;
            }
        }
        assertThat(md).isNotNull();

        VariableDeclarationStatement valStmt = (VariableDeclarationStatement) md.getBody().statements().get(1);
        VariableDeclarationFragment valFrag = (VariableDeclarationFragment) valStmt.fragments().get(0);
        Expression initVal = valFrag.getInitializer();

        DataFlowModel model = new JdtDataFlowModel(context);
        String val = model.resolveValue(initVal, context);
        assertThat(val).isEqualTo("BASE_VAL");
    }

    @Test
    void testDynamicDispatchNarrowing(@TempDir Path tempDir) throws IOException {
        Path comFoo = tempDir.resolve("com/foo");
        Files.createDirectories(comFoo);
        Files.writeString(comFoo.resolve("Test.java"),
            "package com.foo;\n" +
            "public class Test {\n" +
            "    public interface Service {\n" +
            "        String getEvent();\n" +
            "    }\n" +
            "    public static class PayService implements Service {\n" +
            "        public String getEvent() { return \"PAY\"; }\n" +
            "    }\n" +
            "    public static class CancelService implements Service {\n" +
            "        public String getEvent() { return \"CANCEL\"; }\n" +
            "    }\n" +
            "    public void run() {\n" +
            "        Service s = new PayService();\n" +
            "        String ev = s.getEvent();\n" +
            "    }\n" +
            "}"
        );

        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
        context.scan(tempDir);

        TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
        assertThat(td).isNotNull();

        MethodDeclaration md = null;
        for (MethodDeclaration m : td.getMethods()) {
            if (m.getName().getIdentifier().equals("run")) {
                md = m;
            }
        }
        assertThat(md).isNotNull();

        VariableDeclarationStatement evStmt = (VariableDeclarationStatement) md.getBody().statements().get(1);
        VariableDeclarationFragment evFrag = (VariableDeclarationFragment) evStmt.fragments().get(0);
        Expression initEv = evFrag.getInitializer();

        DataFlowModel model = new JdtDataFlowModel(context);
        List<Expression> defs = model.getReachingDefinitions(initEv);
        assertThat(defs).isNotEmpty();

        String val = model.resolveValue(initEv, context);
        assertThat(val).isEqualTo("PAY");
    }

    @Test
    void testFlowSensitiveLocalFieldMutations(@TempDir Path tempDir) throws IOException {
        Path comFoo = tempDir.resolve("com/foo");
        Files.createDirectories(comFoo);
        Files.writeString(comFoo.resolve("Test.java"),
            "package com.foo;\n" +
            "public class Test {\n" +
            "    public static class Command {\n" +
            "        private String event = \"DEFAULT\";\n" +
            "        public void setEvent(String event) {\n" +
            "            this.event = event;\n" +
            "        }\n" +
            "        public String getEvent() {\n" +
            "            return this.event;\n" +
            "        }\n" +
            "    }\n" +
            "    public void run() {\n" +
            "        Command cmd = new Command();\n" +
            "        cmd.setEvent(\"PAY\");\n" +
            "        String res = cmd.getEvent();\n" +
            "    }\n" +
            "}"
        );

        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
        context.scan(tempDir);

        TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
        assertThat(td).isNotNull();

        MethodDeclaration md = null;
        for (MethodDeclaration m : td.getMethods()) {
            if (m.getName().getIdentifier().equals("run")) {
                md = m;
            }
        }
        assertThat(md).isNotNull();

        VariableDeclarationStatement resStmt = (VariableDeclarationStatement) md.getBody().statements().get(2);
        VariableDeclarationFragment resFrag = (VariableDeclarationFragment) resStmt.fragments().get(0);
        Expression initializer = resFrag.getInitializer();

        DataFlowModel model = new JdtDataFlowModel(context);
        String val = model.resolveValue(initializer, context);
        assertThat(val).isEqualTo("PAY");
    }

    @Test
    void testFluentBuilderPatternPropagation(@TempDir Path tempDir) throws IOException {
        Path comFoo = tempDir.resolve("com/foo");
        Files.createDirectories(comFoo);
        Files.writeString(comFoo.resolve("Test.java"),
            "package com.foo;\n" +
            "public class Test {\n" +
            "    public static class EventWrapper {\n" +
            "        private final String event;\n" +
            "        private EventWrapper(String event) {\n" +
            "            this.event = event;\n" +
            "        }\n" +
            "        public String getEvent() { return this.event; }\n" +
            "        public static Builder builder() {\n" +
            "            return new Builder();\n" +
            "        }\n" +
            "        public static class Builder {\n" +
            "            private String event;\n" +
            "            public Builder event(String event) {\n" +
            "                this.event = event;\n" +
            "                return this;\n" +
            "            }\n" +
            "            public EventWrapper build() {\n" +
            "                return new EventWrapper(this.event);\n" +
            "            }\n" +
            "        }\n" +
            "    }\n" +
            "    public void run() {\n" +
            "        EventWrapper w = EventWrapper.builder().event(\"PAY_SUCCESS\").build();\n" +
            "        String res = w.getEvent();\n" +
            "    }\n" +
            "}"
        );

        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
        context.scan(tempDir);

        TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
        assertThat(td).isNotNull();

        MethodDeclaration md = null;
        for (MethodDeclaration m : td.getMethods()) {
            if (m.getName().getIdentifier().equals("run")) {
                md = m;
            }
        }
        assertThat(md).isNotNull();

        VariableDeclarationStatement resStmt = (VariableDeclarationStatement) md.getBody().statements().get(1);
        VariableDeclarationFragment resFrag = (VariableDeclarationFragment) resStmt.fragments().get(0);
        Expression initializer = resFrag.getInitializer();

        DataFlowModel model = new JdtDataFlowModel(context);
        String val = model.resolveValue(initializer, context);
        assertThat(val).isEqualTo("PAY_SUCCESS");
    }

    @Test
    void testPolymorphicUnionFallback(@TempDir Path tempDir) throws IOException {
        Path comFoo = tempDir.resolve("com/foo");
        Files.createDirectories(comFoo);
        Files.writeString(comFoo.resolve("Test.java"),
            "package com.foo;\n" +
            "public class Test {\n" +
            "    public interface Service {\n" +
            "        String getEvent();\n" +
            "    }\n" +
            "    public static class PayService implements Service {\n" +
            "        public String getEvent() { return \"PAY\"; }\n" +
            "    }\n" +
            "    public static class CancelService implements Service {\n" +
            "        public String getEvent() { return \"CANCEL\"; }\n" +
            "    }\n" +
            "    public void run(Service s) {\n" +
            "        String ev = s.getEvent();\n" +
            "    }\n" +
            "}"
        );

        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.setSourcepath(List.of(tempDir.toAbsolutePath().toString()));
        context.scan(tempDir);

        TypeDeclaration td = context.getTypeDeclaration("com.foo.Test");
        assertThat(td).isNotNull();

        MethodDeclaration md = null;
        for (MethodDeclaration m : td.getMethods()) {
            if (m.getName().getIdentifier().equals("run")) {
                md = m;
            }
        }
        assertThat(md).isNotNull();

        VariableDeclarationStatement evStmt = (VariableDeclarationStatement) md.getBody().statements().get(0);
        VariableDeclarationFragment evFrag = (VariableDeclarationFragment) evStmt.fragments().get(0);
        Expression initEv = evFrag.getInitializer();

        DataFlowModel model = new JdtDataFlowModel(context);
        List<Expression> defs = model.getReachingDefinitions(initEv);
        assertThat(defs).hasSize(2);

        List<String> values = defs.stream()
            .filter(e -> e instanceof StringLiteral)
            .map(e -> ((StringLiteral) e).getLiteralValue())
            .toList();
        assertThat(values).containsExactlyInAnyOrder("PAY", "CANCEL");
    }
}

