package click.kamil.springstatemachineexporter.analysis.service;

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;

class ExternalTriggerPolicyTest {

    @Test
    void pathVariableEventParameterShouldBeExternal() {
        EntryPoint entryPoint = EntryPoint.builder()
                .type(EntryPoint.Type.REST)
                .name("POST /api/{event}")
                .className("com.example.Api")
                .methodName("trigger")
                .parameters(List.of(EntryPoint.Parameter.builder()
                        .name("event")
                        .type("String")
                        .annotations(List.of("PathVariable"))
                        .build()))
                .build();
        TriggerPoint trigger = TriggerPoint.builder()
                .event("eventString.toUpperCase()")
                .build();

        assertThat(ExternalTriggerPolicy.isExternalFromSource(
                entryPoint, trigger, "com.example.Api.trigger", "event", null)).isTrue();
    }

    @Test
    void pathVariableEnumParameterShouldBeInternal(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            import org.springframework.web.bind.annotation.*;
            public class Api {
                @PostMapping("/api/order/{event}")
                public void transition(@PathVariable OrderEvent event) {
                    fire(event);
                }
                void fire(OrderEvent event) {}
            }
            enum OrderEvent { PAY, SHIP }
            """;
        Files.writeString(tempDir.resolve("Api.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        EntryPoint entryPoint = EntryPoint.builder()
                .type(EntryPoint.Type.REST)
                .name("POST /api/order/{event}")
                .className("com.example.Api")
                .methodName("transition")
                .parameters(List.of(EntryPoint.Parameter.builder()
                        .name("event")
                        .type("com.example.OrderEvent")
                        .annotations(List.of("PathVariable"))
                        .build()))
                .build();
        TriggerPoint trigger = TriggerPoint.builder()
                .event("event")
                .build();

        assertThat(ExternalTriggerPolicy.isExternalFromSource(
                entryPoint, trigger, "com.example.Api.transition", "event", context)).isFalse();
    }

    @Test
    void concreteResolvedPathVariableEndpointShouldBeInternal() {
        EntryPoint entryPoint = EntryPoint.builder()
                .type(EntryPoint.Type.REST)
                .name("POST /api/machine/ORDER/transition/PAY")
                .className("com.example.Api")
                .methodName("transition")
                .parameters(List.of(
                        EntryPoint.Parameter.builder()
                                .name("machineType")
                                .type("String")
                                .annotations(List.of("PathVariable"))
                                .build(),
                        EntryPoint.Parameter.builder()
                                .name("event")
                                .type("String")
                                .annotations(List.of("PathVariable"))
                                .build()))
                .build();
        TriggerPoint trigger = TriggerPoint.builder()
                .event("OrderEvent.valueOf(eventString.toUpperCase())")
                .build();

        assertThat(ExternalTriggerPolicy.isExternalFromSource(
                entryPoint, trigger, "com.example.Api.transition", "event", null)).isFalse();
    }

    @Test
    void requestBodyEnumShouldBeInternal(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            import org.springframework.web.bind.annotation.*;
            public class Api {
                @PostMapping("/pay")
                public void pay(@RequestBody OrderEvent event) {
                    fire(event);
                }
                void fire(OrderEvent event) {}
            }
            enum OrderEvent { PAY }
            """;
        Files.writeString(tempDir.resolve("Api.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.scan(tempDir);

        EntryPoint entryPoint = EntryPoint.builder()
                .type(EntryPoint.Type.REST)
                .name("POST /pay")
                .className("com.example.Api")
                .methodName("pay")
                .build();
        TriggerPoint trigger = TriggerPoint.builder()
                .event("com.example.OrderEvent.PAY")
                .polymorphicEvents(List.of("com.example.OrderEvent.PAY"))
                .build();

        assertThat(ExternalTriggerPolicy.isExternalFromSource(
                entryPoint, trigger, "com.example.Api.pay", "event", context)).isFalse();
    }

    @Test
    void dedicatedEndpointWithUnrelatedRequestParamShouldBeInternal() {
        EntryPoint entryPoint = EntryPoint.builder()
                .type(EntryPoint.Type.REST)
                .name("POST /api/machine/order/pay")
                .className("com.example.Api")
                .methodName("payOrder")
                .parameters(List.of(EntryPoint.Parameter.builder()
                        .name("userId")
                        .type("String")
                        .annotations(List.of("RequestParam"))
                        .build()))
                .build();
        TriggerPoint trigger = TriggerPoint.builder()
                .event("com.example.OrderEvent.PAY")
                .polymorphicEvents(List.of("com.example.OrderEvent.PAY"))
                .external(true) // call-graph may have mis-tagged via userId binding
                .build();

        assertThat(ExternalTriggerPolicy.isExternalFromSource(
                entryPoint, trigger, "com.example.Api.payOrder", null, null)).isFalse();
    }

    @Test
    void resourceIdPathVariableShouldNotMakeDedicatedEndpointExternal() {
        EntryPoint entryPoint = EntryPoint.builder()
                .type(EntryPoint.Type.REST)
                .name("POST /api/ticket/{id}/assign")
                .className("com.example.WebController")
                .methodName("assignTicket")
                .parameters(List.of(
                        EntryPoint.Parameter.builder()
                                .name("id")
                                .type("String")
                                .annotations(List.of("PathVariable"))
                                .build(),
                        EntryPoint.Parameter.builder()
                                .name("userId")
                                .type("String")
                                .annotations(List.of("RequestParam"))
                                .build()))
                .build();
        TriggerPoint trigger = TriggerPoint.builder()
                .event("com.example.TicketEvent.ASSIGN")
                .polymorphicEvents(List.of("com.example.TicketEvent.ASSIGN"))
                .external(true)
                .build();

        assertThat(ExternalTriggerPolicy.isExternalFromSource(
                entryPoint, trigger, "com.example.WebController.assignTicket", null, null)).isFalse();
    }

    @Test
    void concreteEnumLiteralFromDedicatedEndpointShouldBeInternal() {
        EntryPoint entryPoint = EntryPoint.builder()
                .type(EntryPoint.Type.REST)
                .name("POST /api/order/pay")
                .className("com.example.Api")
                .methodName("pay")
                .build();
        TriggerPoint trigger = TriggerPoint.builder()
                .event("com.example.OrderEvent.PAY")
                .polymorphicEvents(List.of("com.example.OrderEvent.PAY"))
                .build();

        assertThat(ExternalTriggerPolicy.isExternalFromSource(
                entryPoint, trigger, "com.example.Api.pay", null, null)).isFalse();
    }

    @Test
    void requestBodyRecordEnumComponentShouldBeInternal(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            import org.springframework.web.bind.annotation.*;
            public class Api {
                @PostMapping("/pay")
                public void pay(@RequestBody OrderRequest request) {
                    fire(request.event());
                }
                void fire(OrderEvent event) {}
            }
            record OrderRequest(OrderEvent event) {}
            enum OrderEvent { PAY }
            """;
        Files.writeString(tempDir.resolve("Api.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.scan(tempDir);

        EntryPoint entryPoint = EntryPoint.builder()
                .type(EntryPoint.Type.REST)
                .name("POST /pay")
                .className("com.example.Api")
                .methodName("pay")
                .build();
        TriggerPoint trigger = TriggerPoint.builder()
                .event("com.example.OrderEvent.PAY")
                .polymorphicEvents(List.of("com.example.OrderEvent.PAY"))
                .build();

        assertThat(ExternalTriggerPolicy.isExternalFromSource(
                entryPoint, trigger, "com.example.Api.pay", "event", context)).isFalse();
    }

    @Test
    void requestBodyNestedDtoEnumFieldShouldBeInternal(@TempDir Path tempDir) throws Exception {
        String source = """
            package com.example;
            import org.springframework.web.bind.annotation.*;
            public class Api {
                @PostMapping("/pay")
                public void pay(@RequestBody OrderRequest request) {
                    fire(request.details().event());
                }
                void fire(OrderEvent event) {}
            }
            record OrderRequest(OrderDetails details) {}
            record OrderDetails(OrderEvent event) {}
            enum OrderEvent { PAY }
            """;
        Files.writeString(tempDir.resolve("Api.java"), source);
        CodebaseContext context = new CodebaseContext();
        context.setResolveBindings(true);
        context.scan(tempDir);

        EntryPoint entryPoint = EntryPoint.builder()
                .type(EntryPoint.Type.REST)
                .name("POST /pay")
                .className("com.example.Api")
                .methodName("pay")
                .build();
        TriggerPoint trigger = TriggerPoint.builder()
                .event("com.example.OrderEvent.PAY")
                .polymorphicEvents(List.of("com.example.OrderEvent.PAY"))
                .build();

        assertThat(ExternalTriggerPolicy.isExternalFromSource(
                entryPoint, trigger, "com.example.Api.pay", "event", context)).isFalse();
    }
}
