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.Set;
import java.util.stream.Collectors;

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

class ReachingDefinitionsTest {

    @Test
    void testReachingDefinitionsBranchMerge(@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" +
            "        if (flag) {\n" +
            "            a = 2;\n" +
            "        } else {\n" +
            "            a = 3;\n" +
            "        }\n" +
            "        int 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];
        ControlFlowGraph cfg = CfgBuilder.build(md);
        ReachingDefinitions rd = new ReachingDefinitions(cfg);

        // Find usage 'a' in 'int b = a;'
        VariableDeclarationStatement bStmt = (VariableDeclarationStatement) md.getBody().statements().get(2);
        VariableDeclarationFragment bFrag = (VariableDeclarationFragment) bStmt.fragments().get(0);
        Expression initializer = bFrag.getInitializer(); // simpleName 'a'

        Set<ASTNode> defs = rd.getReachingDefinitions(initializer, "a");
        assertThat(defs).hasSize(2);

        Set<String> values = defs.stream()
            .map(ReachingDefinitions::getDefinitionExpression)
            .map(expr -> {
                if (expr instanceof NumberLiteral nl) {
                    return nl.getToken();
                }
                return expr.toString();
            })
            .collect(Collectors.toSet());

        assertThat(values).containsExactlyInAnyOrder("2", "3");
    }

    @Test
    void testReachingDefinitionsKilled(@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" +
            "        int a = 1;\n" +
            "        a = 2;\n" +
            "        int 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];
        ControlFlowGraph cfg = CfgBuilder.build(md);
        ReachingDefinitions rd = new ReachingDefinitions(cfg);

        // Find usage 'a' in 'int b = a;'
        VariableDeclarationStatement bStmt = (VariableDeclarationStatement) md.getBody().statements().get(2);
        VariableDeclarationFragment bFrag = (VariableDeclarationFragment) bStmt.fragments().get(0);
        Expression initializer = bFrag.getInitializer();

        Set<ASTNode> defs = rd.getReachingDefinitions(initializer, "a");
        assertThat(defs).hasSize(1);

        ASTNode singleDef = defs.iterator().next();
        Expression defExpr = ReachingDefinitions.getDefinitionExpression(singleDef);
        assertThat(defExpr).isInstanceOf(NumberLiteral.class);
        assertThat(((NumberLiteral) defExpr).getToken()).isEqualTo("2");
    }
}
