package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.model.CallChain;
import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.model.TriggerPoint;
import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
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 BooleanTernaryBranchSplitTest {

    @Test
    void bindingExpanderShouldProduceBooleanVariantsForTernaryParameter(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class PolymorphicController {
                OrderService orderService;
                public void payTernary(boolean isPay) {
                    orderService.processEvent(isPay ? new PayEvent() : new CancelEvent());
                }
            }
            class OrderService {
                void processEvent(BaseEvent event) {}
            }
            class BaseEvent {}
            class PayEvent extends BaseEvent {}
            class CancelEvent extends BaseEvent {}
            """;
        Files.writeString(tempDir.resolve("App.java"), source);

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

        HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.PolymorphicController")
                .methodName("payTernary")
                .name("POST /pay-ternary")
                .parameters(List.of(EntryPoint.Parameter.builder()
                        .name("isPay")
                        .type("boolean")
                        .build()))
                .build();

        List<Map<String, String>> variants = EntryPointBindingExpander.expandEntryPointBindings(
                entryPoint, context, engine.buildCallGraph());

        assertThat(variants).extracting(map -> map.get("isPay"))
                .containsExactlyInAnyOrder("true", "false");
    }

    @Test
    void shouldResolveSingleEventPerBooleanBinding(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            public class PolymorphicController {
                OrderService orderService;
                public void payTernary(boolean isPay) {
                    orderService.processEvent(isPay ? new PayEvent() : new CancelEvent());
                }
            }
            class OrderService {
                void processEvent(BaseEvent event) {}
            }
            class BaseEvent {}
            class PayEvent extends BaseEvent {}
            class CancelEvent extends BaseEvent {}
            """;
        Files.writeString(tempDir.resolve("App.java"), source);

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

        HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
        EntryPoint entryPoint = EntryPoint.builder()
                .className("com.example.PolymorphicController")
                .methodName("payTernary")
                .name("POST /pay-ternary")
                .parameters(List.of(EntryPoint.Parameter.builder()
                        .name("isPay")
                        .type("boolean")
                        .build()))
                .build();

        TriggerPoint trigger = TriggerPoint.builder()
                .className("com.example.OrderService")
                .methodName("processEvent")
                .event("event")
                .build();

        List<CallChain> chains = engine.findChains(List.of(entryPoint), List.of(trigger));

        CallChain payChain = chains.stream()
                .filter(c -> c.getEntryPoint().getName().contains("isPay=true"))
                .findFirst()
                .orElseThrow();
        CallChain cancelChain = chains.stream()
                .filter(c -> c.getEntryPoint().getName().contains("isPay=false"))
                .findFirst()
                .orElseThrow();

        assertThat(payChain.getTriggerPoint().isAmbiguous()).isFalse();
        assertThat(cancelChain.getTriggerPoint().isAmbiguous()).isFalse();
        assertThat(payChain.getTriggerPoint().getEvent()).contains("PayEvent").doesNotContain("CancelEvent");
        assertThat(cancelChain.getTriggerPoint().getEvent()).contains("CancelEvent").doesNotContain("PayEvent");
        if (payChain.getTriggerPoint().getPolymorphicEvents() != null) {
            assertThat(payChain.getTriggerPoint().getPolymorphicEvents()).hasSizeLessThanOrEqualTo(1);
        }
        if (cancelChain.getTriggerPoint().getPolymorphicEvents() != null) {
            assertThat(cancelChain.getTriggerPoint().getPolymorphicEvents()).hasSizeLessThanOrEqualTo(1);
        }
    }
}
