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

/**
 * REST controllers call into shared abstract services. Context-sensitive pruning must only
 * apply to the entry's own type hierarchy — not collaborator abstract types — or every REST
 * chain dies at {@code AbstractService.doWork()} fan-out.
 */
class RestCollaboratorAbstractServiceCallGraphTest {

    @Test
    void restControllerStillReachesSharedAbstractServiceTrigger(@TempDir Path tempDir) throws Exception {
        Files.writeString(tempDir.resolve("App.java"), """
                package com.example;
                class OrderController {
                    private final OrderSharedService service = new OrderSharedService();
                    public void pay() {
                        service.process(OrderEvent.PAY);
                    }
                }
                abstract class AbstractSharedService {
                    protected abstract void fire(OrderEvent event);
                    void process(OrderEvent event) {
                        fire(event);
                    }
                }
                class OrderSharedService extends AbstractSharedService {
                    private final StateMachine sm = new StateMachine();
                    @Override
                    protected void fire(OrderEvent event) {
                        sm.sendEvent(event);
                    }
                }
                class DocumentSharedService extends AbstractSharedService {
                    private final StateMachine sm = new StateMachine();
                    @Override
                    protected void fire(OrderEvent event) {
                        sm.sendEvent(event);
                    }
                }
                class StateMachine {
                    void sendEvent(OrderEvent event) {}
                }
                enum OrderEvent { PAY }
                """);

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

        HeuristicCallGraphEngine engine = new HeuristicCallGraphEngine(context);
        List<CallChain> chains = engine.findChains(
                List.of(EntryPoint.builder()
                        .type(EntryPoint.Type.REST)
                        .name("POST /orders/pay")
                        .className("com.example.OrderController")
                        .methodName("pay")
                        .build()),
                List.of(TriggerPoint.builder()
                        .className("com.example.StateMachine")
                        .methodName("sendEvent")
                        .event("event")
                        .build()));

        assertThat(chains)
                .as("REST → AbstractSharedService.process → fire() must remain reachable")
                .isNotEmpty();
        assertThat(chains)
                .anyMatch(chain -> chain.getMethodChain().stream()
                        .anyMatch(hop -> hop.contains("OrderSharedService.fire")
                                || hop.contains("sendEvent")));
    }
}
