package click.kamil.springstatemachineexporter.analysis.service;

import click.kamil.springstatemachineexporter.analysis.model.EntryPoint;
import click.kamil.springstatemachineexporter.analysis.resolver.MachineEnumCanonicalizer;

import java.util.Locale;
import java.util.Set;

/**
 * Shared identity for parameters that carry event / payload / machine-route discriminators
 * rather than SM source-state enum values. Used by binding expansion, trigger detection,
 * linking, and poly narrowing so name lists are not duplicated.
 */
public final class EventCarrierPolicy {

    private static final Set<String> CARRIER_NAMES = Set.of(
            "message",
            "msg",
            "payload",
            "body",
            "command",
            "commandkey",
            "event",
            "e",
            "eventstring",
            "action",
            "actionkey",
            "text",
            "transition");

    private static final Set<String> MACHINE_DISCRIMINATOR_NAMES = Set.of(
            "machinetype",
            "machineid",
            "machinename",
            "domain",
            "type",
            "version");

    private static final Set<String> STATE_ACCESSOR_NAMES = Set.of(
            "state",
            "currentstate",
            "sourcestate",
            "fromstate");

    private static final Set<String> STATE_ACCESSOR_METHOD_NAMES = Set.of(
            "getstate",
            "getid");

    /**
     * Bare unresolved trigger tokens derived from {@link #CARRIER_NAMES} so the sets cannot drift.
     * Kept as an explicit subset of common payload/event identifiers.
     */
    private static final Set<String> DYNAMIC_EVENT_TOKENS = Set.of(
                    "event",
                    "e",
                    "message",
                    "msg",
                    "payload")
            .stream()
            .filter(CARRIER_NAMES::contains)
            .collect(java.util.stream.Collectors.toUnmodifiableSet());

    private EventCarrierPolicy() {
    }

    /** Event / payload / command body parameter names (not machine domain keys). */
    public static boolean isCarrierParamName(String name) {
        if (name == null || name.isBlank()) {
            return false;
        }
        String lower = name.toLowerCase(Locale.ROOT);
        if (CARRIER_NAMES.contains(lower)) {
            return true;
        }
        return lower.endsWith("event");
    }

    /** Machine-type / domain discriminators used for multi-machine routing. */
    public static boolean isMachineDiscriminatorName(String name) {
        if (name == null || name.isBlank()) {
            return false;
        }
        return MACHINE_DISCRIMINATOR_NAMES.contains(name.toLowerCase(Locale.ROOT));
    }

    /** Carrier or machine discriminant — must not be treated as SM source state. */
    public static boolean isRoutingDiscriminantName(String name) {
        return isCarrierParamName(name) || isMachineDiscriminatorName(name);
    }

    /**
     * Bare identifier used as an unresolved trigger event expression
     * ({@code event}, {@code message}, {@code payload}, …).
     */
    public static boolean isDynamicEventToken(String eventStr) {
        if (eventStr == null || eventStr.isBlank()) {
            return false;
        }
        return DYNAMIC_EVENT_TOKENS.contains(eventStr.toLowerCase(Locale.ROOT));
    }

    /** Clear state-variable / accessor names used for positive source-state inference. */
    public static boolean isStateAccessorName(String name) {
        if (name == null || name.isBlank()) {
            return false;
        }
        String lower = name.toLowerCase(Locale.ROOT);
        if (STATE_ACCESSOR_NAMES.contains(lower)) {
            return true;
        }
        return lower.endsWith("state") && !isCarrierParamName(name);
    }

    /**
     * Method names that positively indicate an SM source-state selector
     * ({@code getState()}, {@code getId()}, or names already treated as state accessors).
     */
    public static boolean isStateAccessorMethodName(String name) {
        if (name == null || name.isBlank()) {
            return false;
        }
        String lower = name.toLowerCase(Locale.ROOT);
        if (STATE_ACCESSOR_METHOD_NAMES.contains(lower)) {
            return true;
        }
        return isStateAccessorName(name);
    }

    /**
     * Messaging {@code @Payload}, conventional carrier name, or the sole unannotated String body.
     */
    public static boolean isMessagingPayloadParameter(EntryPoint entryPoint, EntryPoint.Parameter parameter) {
        if (entryPoint == null || parameter == null || !isMessagingEntryPoint(entryPoint)) {
            return false;
        }
        if (hasAnnotation(parameter, "Payload")) {
            return true;
        }
        boolean unannotated = parameter.getAnnotations() == null || parameter.getAnnotations().isEmpty();
        if (!unannotated) {
            return false;
        }
        if (!isStringType(parameter.getType())) {
            return false;
        }
        if (isCarrierParamName(parameter.getName())) {
            return true;
        }
        return isSoleUnannotatedStringMessagingBody(entryPoint, parameter);
    }

    /**
     * Unannotated {@code String} body that is the only such param on a messaging entry —
     * treat as carrier even when named {@code cmd}.
     */
    public static boolean isSoleUnannotatedStringMessagingBody(
            EntryPoint entryPoint, EntryPoint.Parameter parameter) {
        if (entryPoint == null || parameter == null || !isMessagingEntryPoint(entryPoint)) {
            return false;
        }
        if (parameter.getAnnotations() != null && !parameter.getAnnotations().isEmpty()) {
            return false;
        }
        if (!isStringType(parameter.getType())) {
            return false;
        }
        if (entryPoint.getParameters() == null) {
            return false;
        }
        int unannotatedStringBodies = 0;
        for (EntryPoint.Parameter candidate : entryPoint.getParameters()) {
            if (candidate == null) {
                continue;
            }
            boolean unannotated = candidate.getAnnotations() == null || candidate.getAnnotations().isEmpty();
            if (unannotated && isStringType(candidate.getType())) {
                unannotatedStringBodies++;
            }
        }
        return unannotatedStringBodies == 1;
    }

    /**
     * REST path/query params that carry the event key (not resource ids).
     */
    public static boolean isRestEventCarrierParameter(EntryPoint.Parameter parameter) {
        if (parameter == null || parameter.getAnnotations() == null) {
            return false;
        }
        boolean pathOrQuery = hasAnnotation(parameter, "PathVariable")
                || hasAnnotation(parameter, "RequestParam");
        if (!pathOrQuery) {
            return false;
        }
        return isCarrierParamName(parameter.getName()) || isMachineDiscriminatorName(parameter.getName());
    }

    /**
     * Entry-point parameter that expands / binds as an event or payload carrier.
     */
    public static boolean isCarrierParameter(EntryPoint entryPoint, EntryPoint.Parameter parameter) {
        if (parameter == null) {
            return false;
        }
        if (hasAnnotation(parameter, "Payload")) {
            return true;
        }
        if (isMessagingPayloadParameter(entryPoint, parameter)) {
            return true;
        }
        if (isRestEventCarrierParameter(parameter)) {
            return true;
        }
        return false;
    }

    public static boolean isMessagingEntryPoint(EntryPoint entryPoint) {
        if (entryPoint == null || entryPoint.getType() == null) {
            return false;
        }
        return switch (entryPoint.getType()) {
            case JMS, KAFKA, RABBIT, SQS, SNS -> true;
            default -> false;
        };
    }

    private static boolean hasAnnotation(EntryPoint.Parameter parameter, String simpleName) {
        if (parameter.getAnnotations() == null || simpleName == null) {
            return false;
        }
        return parameter.getAnnotations().stream().anyMatch(a ->
                simpleName.equals(a) || (a != null && a.endsWith("." + simpleName)));
    }

    private static boolean isStringType(String type) {
        return MachineEnumCanonicalizer.isJavaStringType(type);
    }
}
