package click.kamil.springstatemachineexporter.analysis.pipeline;

import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;

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

class SwitchConstSupportTest {

    @Test
    void collectsIntCaseArrowAndDefault(@TempDir Path tempDir) throws Exception {
        Files.writeString(tempDir.resolve("C.java"), """
                package com.example;
                class C {
                  void m(int type) {
                    String e = (((switch(type) {
                      case 1 -> "PAY";
                      default -> { yield "CANCEL"; }
                    })));
                  }
                }
                """);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        TypeDeclaration td = context.getTypeDeclaration("com.example.C");
        MethodDeclaration md = context.findMethodDeclaration(td, "m", true);
        org.eclipse.jdt.core.dom.SwitchExpression[] se = {null};
        md.accept(new org.eclipse.jdt.core.dom.ASTVisitor() {
            @Override
            public boolean visit(org.eclipse.jdt.core.dom.SwitchExpression node) {
                se[0] = node;
                return false;
            }
        });
        assertThat(se[0]).isNotNull();
        List<String> kinds = new java.util.ArrayList<>();
        for (Object o : se[0].statements()) {
            kinds.add(o.getClass().getSimpleName() + ":" + o.toString().replace('\n', ' '));
        }
        List<Expression> arms = SwitchConstSupport.armPayloadExpressions(se[0]);
        assertThat(arms)
                .as("statements=%s", kinds)
                .hasSize(2);
    }

    @Test
    void collectsArrowAndDefaultBlockArms(@TempDir Path tempDir) throws Exception {
        MethodDeclaration md = method(tempDir, """
                package com.example;
                class C {
                  String map(int type) {
                    return switch (type) {
                      case 1 -> "PAY";
                      default -> { yield "CANCEL"; }
                    };
                  }
                }
                """);
        ReturnStatement rs = findReturn(md);
        assertThat(rs.getExpression()).isInstanceOf(org.eclipse.jdt.core.dom.SwitchExpression.class);
        List<Expression> arms = SwitchConstSupport.armPayloadExpressions(
                (org.eclipse.jdt.core.dom.ASTNode) rs.getExpression());
        assertThat(arms).hasSize(2);
        assertThat(arms.get(0).toString()).contains("PAY");
        assertThat(arms.get(1).toString()).contains("CANCEL");
    }

    @Test
    void prefersYieldOverSideEffectInBlock(@TempDir Path tempDir) throws Exception {
        MethodDeclaration md = method(tempDir, """
                package com.example;
                class C {
                  String map(String q) {
                    return switch (q) {
                      case "a" -> {
                        System.out.println("Processing a");
                        yield "A8";
                      }
                      case "b" -> { yield "A133"; }
                      default -> "PAY";
                    };
                  }
                }
                """);
        ReturnStatement rs = findReturn(md);
        List<Expression> arms = SwitchConstSupport.armPayloadExpressions(
                (org.eclipse.jdt.core.dom.ASTNode) rs.getExpression());
        assertThat(arms).hasSize(3);
        assertThat(arms.get(0).toString()).contains("A8");
        assertThat(arms.get(1).toString()).contains("A133");
        assertThat(arms.get(2).toString()).contains("PAY");
    }

    @Test
    void stringCasePayloadsSkipsDefault(@TempDir Path tempDir) throws Exception {
        MethodDeclaration md = method(tempDir, """
                package com.example;
                class C {
                  String map(String code) {
                    return switch (code) {
                      case "PAY" -> "PAY";
                      case "SHIP" -> "SHIP";
                      default -> { yield "CANCEL"; }
                    };
                  }
                }
                """);
        ReturnStatement rs = findReturn(md);
        Map<String, Expression> map = SwitchConstSupport.stringCasePayloads(
                (org.eclipse.jdt.core.dom.ASTNode) rs.getExpression(),
                ex -> null);
        assertThat(map).containsOnlyKeys("PAY", "SHIP");
        assertThat(map.get("PAY").toString()).contains("PAY");
    }

    private static MethodDeclaration method(Path tempDir, String source) throws Exception {
        Files.writeString(tempDir.resolve("C.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);
        TypeDeclaration td = context.getTypeDeclaration("com.example.C");
        if (td == null) {
            td = context.getTypeDeclaration("C");
        }
        assertThat(td).isNotNull();
        MethodDeclaration md = context.findMethodDeclaration(td, "map", true);
        assertThat(md).isNotNull();
        return md;
    }

    private static ReturnStatement findReturn(MethodDeclaration md) {
        ReturnStatement[] found = {null};
        md.accept(new org.eclipse.jdt.core.dom.ASTVisitor() {
            @Override
            public boolean visit(ReturnStatement node) {
                found[0] = node;
                return false;
            }
        });
        assertThat(found[0]).isNotNull();
        return found[0];
    }
}
