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.MethodInvocation;
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 static org.assertj.core.api.Assertions.assertThat;

/**
 * Peel vs expand-hint split: 0-arg peel for EI; any-arity case transforms + name() for ACE.
 */
class IdentityStringTransformSupportTest {

    @Test
    void zeroArgToUpperCaseIsPeelAndExpand(@TempDir Path tempDir) throws Exception {
        MethodInvocation mi = invocation(tempDir, "map", """
                package com.example;
                class C {
                  String map(String action) {
                    return action.toUpperCase();
                  }
                }
                """);
        assertThat(IdentityStringTransformSupport.isPeelInvocation(mi)).isTrue();
        assertThat(IdentityStringTransformSupport.isExpandHintInvocation(mi)).isTrue();
    }

    @Test
    void localeToUpperCaseIsExpandNotPeel(@TempDir Path tempDir) throws Exception {
        MethodInvocation mi = invocation(tempDir, "map", """
                package com.example;
                import java.util.Locale;
                class C {
                  String map(String action) {
                    return action.toUpperCase(Locale.ROOT);
                  }
                }
                """);
        assertThat(IdentityStringTransformSupport.isPeelInvocation(mi)).isFalse();
        assertThat(IdentityStringTransformSupport.isExpandHintInvocation(mi)).isTrue();
    }

    @Test
    void zeroArgToLowerCaseIsPeelAndExpand(@TempDir Path tempDir) throws Exception {
        MethodInvocation mi = invocation(tempDir, "map", """
                package com.example;
                class C {
                  String map(String action) {
                    return action.toLowerCase();
                  }
                }
                """);
        assertThat(IdentityStringTransformSupport.isPeelInvocation(mi)).isTrue();
        assertThat(IdentityStringTransformSupport.isExpandHintInvocation(mi)).isTrue();
    }

    @Test
    void localeToLowerCaseIsExpandNotPeel(@TempDir Path tempDir) throws Exception {
        MethodInvocation mi = invocation(tempDir, "map", """
                package com.example;
                import java.util.Locale;
                class C {
                  String map(String action) {
                    return action.toLowerCase(Locale.ROOT);
                  }
                }
                """);
        assertThat(IdentityStringTransformSupport.isPeelInvocation(mi)).isFalse();
        assertThat(IdentityStringTransformSupport.isExpandHintInvocation(mi)).isTrue();
    }

    @Test
    void trimIsPeelNotExpand(@TempDir Path tempDir) throws Exception {
        MethodInvocation mi = invocation(tempDir, "map", """
                package com.example;
                class C {
                  String map(String action) {
                    return action.trim();
                  }
                }
                """);
        assertThat(IdentityStringTransformSupport.isPeelInvocation(mi)).isTrue();
        assertThat(IdentityStringTransformSupport.isExpandHintInvocation(mi)).isFalse();
        assertThat(IdentityStringTransformSupport.expressionHasExpandHint(mi)).isFalse();
    }

    @Test
    void zeroArgNameIsExpandNotPeel(@TempDir Path tempDir) throws Exception {
        MethodInvocation mi = invocation(tempDir, "map", """
                package com.example;
                class C {
                  String map(OrderEvent e) {
                    return e.name();
                  }
                }
                enum OrderEvent { PAY }
                """);
        assertThat(IdentityStringTransformSupport.isPeelInvocation(mi)).isFalse();
        assertThat(IdentityStringTransformSupport.isExpandHintInvocation(mi)).isTrue();
    }

    @Test
    void getNameIsNeither(@TempDir Path tempDir) throws Exception {
        Expression expr = returnExpr(tempDir, """
                package com.example;
                class C {
                  String map(Person p) {
                    return p.getName();
                  }
                }
                class Person { String getName() { return "x"; } }
                """);
        assertThat(IdentityStringTransformSupport.expressionHasExpandHint(expr)).isFalse();
    }

    @Test
    void peelIdentityStringOpsUnwrapsTrimThenUpper(@TempDir Path tempDir) throws Exception {
        Expression expr = returnExpr(tempDir, """
                package com.example;
                class C {
                  String map(String action) {
                    return action.trim().toUpperCase();
                  }
                }
                """);
        Expression peeled = IdentityStringTransformSupport.peelIdentityStringOps(expr);
        assertThat(peeled.toString()).isEqualTo("action");
    }

    @Test
    void peelIdentityStringOpsLeavesLocaleIntact(@TempDir Path tempDir) throws Exception {
        Expression expr = returnExpr(tempDir, """
                package com.example;
                import java.util.Locale;
                class C {
                  String map(String action) {
                    return action.toUpperCase(Locale.ROOT);
                  }
                }
                """);
        Expression peeled = IdentityStringTransformSupport.peelIdentityStringOps(expr);
        assertThat(peeled.toString()).contains("toUpperCase");
    }

    @Test
    void plainValueOfHasNoExpandHint(@TempDir Path tempDir) throws Exception {
        Expression expr = returnExpr(tempDir, """
                package com.example;
                class C {
                  OrderEvent map(String str) {
                    return OrderEvent.valueOf(str);
                  }
                }
                enum OrderEvent { PAY }
                """);
        assertThat(IdentityStringTransformSupport.expressionHasExpandHint(expr)).isFalse();
    }

    @Test
    void returnExpressionsHaveExpandHintIgnoresSideEffectNoise(@TempDir Path tempDir) throws Exception {
        MethodDeclaration noisy = method(tempDir, "map", """
                package com.example;
                class C {
                  String map(String s) {
                    String ignored = OrderEvent.PAY.name();
                    return MyEvents.valueOf(s);
                  }
                }
                enum OrderEvent { PAY }
                enum MyEvents { A, B }
                """);
        assertThat(IdentityStringTransformSupport.methodBodyHasExpandHint(noisy)).isTrue();
        assertThat(IdentityStringTransformSupport.returnExpressionsHaveExpandHint(noisy)).isFalse();
    }

    @Test
    void returnExpressionsHaveExpandHintForValueOfName(@TempDir Path tempDir) throws Exception {
        MethodDeclaration md = method(tempDir, "map", """
                package com.example;
                class C {
                  String map(String event) {
                    return OrderEvent.valueOf(event).name();
                  }
                }
                enum OrderEvent { PAY }
                """);
        assertThat(IdentityStringTransformSupport.returnExpressionsHaveExpandHint(md)).isTrue();
    }

    @Test
    void nameWithArgsIsNotExpandHint(@TempDir Path tempDir) throws Exception {
        MethodInvocation mi = invocation(tempDir, "map", """
                package com.example;
                class C {
                  String map(Weird w) {
                    return w.name("x");
                  }
                }
                class Weird { String name(String s) { return s; } }
                """);
        assertThat(IdentityStringTransformSupport.isExpandHintInvocation(mi)).isFalse();
    }

    @Test
    void stringToUpperCaseMethodReferenceIsExpandNotPeel(@TempDir Path tempDir) throws Exception {
        Expression expr = returnExpr(tempDir, """
                package com.example;
                import java.util.function.Function;
                class C {
                  Function<String, String> map() {
                    return String::toUpperCase;
                  }
                }
                """);
        assertThat(IdentityStringTransformSupport.isExpandHintMethodReference(expr)).isTrue();
        assertThat(IdentityStringTransformSupport.expressionHasExpandHint(expr)).isTrue();
    }

    @Test
    void enumNameMethodReferenceIsExpandHint(@TempDir Path tempDir) throws Exception {
        Expression expr = returnExpr(tempDir, """
                package com.example;
                import java.util.function.Function;
                class C {
                  Function<OrderEvent, String> map() {
                    return OrderEvent::name;
                  }
                }
                enum OrderEvent { PAY }
                """);
        assertThat(IdentityStringTransformSupport.isExpandHintMethodReference(expr)).isTrue();
        assertThat(IdentityStringTransformSupport.expressionHasExpandHint(expr)).isTrue();
    }

    @Test
    void getNameMethodReferenceIsNotExpandHint(@TempDir Path tempDir) throws Exception {
        Expression expr = returnExpr(tempDir, """
                package com.example;
                import java.util.function.Function;
                class C {
                  Function<Person, String> map() {
                    return Person::getName;
                  }
                }
                class Person { String getName() { return "x"; } }
                """);
        assertThat(IdentityStringTransformSupport.isExpandHintMethodReference(expr)).isFalse();
        assertThat(IdentityStringTransformSupport.expressionHasExpandHint(expr)).isFalse();
    }

    private static MethodInvocation invocation(Path tempDir, String methodName, String source)
            throws Exception {
        MethodDeclaration md = method(tempDir, methodName, source);
        MethodInvocation[] found = {null};
        md.accept(new org.eclipse.jdt.core.dom.ASTVisitor() {
            @Override
            public boolean visit(MethodInvocation node) {
                found[0] = node;
                return false;
            }
        });
        assertThat(found[0]).isNotNull();
        return found[0];
    }

    private static Expression returnExpr(Path tempDir, String source) throws Exception {
        MethodDeclaration md = method(tempDir, "map", source);
        ReturnStatement rs = findReturn(md);
        return rs.getExpression();
    }

    private static MethodDeclaration method(Path tempDir, String methodName, 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, methodName, 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];
    }
}
