package click.kamil.springstatemachineexporter.analysis.resolver;

import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * Repairs truncated enum references produced by older resolution bugs, e.g.
 * {@code com.example.PAY} (package + constant, type segment dropped) →
 * {@code com.example.OrderEvent.PAY} when the match is unique or matches a preferred type.
 */
public final class TruncatedEnumRefReconstructor {

    private static final Pattern ENUM_CONSTANT = Pattern.compile("[A-Z_][A-Z0-9_]*");

    private TruncatedEnumRefReconstructor() {
    }

    /**
     * @return reconstructed FQN when uniquely determined; otherwise the original {@code value}
     */
    public static String reconstruct(String value, CodebaseContext context) {
        return reconstruct(value, null, context);
    }

    /**
     * @param preferredEnumTypeFqn optional machine/event type that disambiguates when several enums
     *                             share the same constant name under a package prefix
     * @return reconstructed FQN when uniquely determined; otherwise the original {@code value}
     */
    public static String reconstruct(String value, String preferredEnumTypeFqn, CodebaseContext context) {
        if (value == null || value.isBlank() || context == null) {
            return value;
        }
        String stripped = stripQuotes(value.trim());
        if (stripped.isBlank() || stripped.contains("(") || stripped.contains(")")) {
            return value;
        }
        if (stripped.startsWith(".") || stripped.contains("..")) {
            return value;
        }

        // Type-only trailing dot (com.example.OrderEvent.) — no constant to recover.
        if (stripped.endsWith(".")) {
            return value;
        }

        int lastDot = stripped.lastIndexOf('.');
        if (lastDot < 0) {
            return reconstructBareConstant(stripped, preferredEnumTypeFqn, context, value);
        }
        if (lastDot == 0 || lastDot >= stripped.length() - 1) {
            return value;
        }

        String prefix = stripped.substring(0, lastDot);
        String constant = stripped.substring(lastDot + 1);
        if (!ENUM_CONSTANT.matcher(constant).matches()) {
            return value;
        }

        // Already a known Type.CONSTANT (simple or FQN) — normalize to indexed FQN when possible.
        if (isKnownEnumType(prefix, context)) {
            String canonical = canonicalConstantRef(prefix, constant, context);
            return canonical != null ? canonical : value;
        }

        // Preferred type: classic truncation com.example.PAY + preferred com.example.OrderEvent.
        if (preferredEnumTypeFqn != null
                && !preferredEnumTypeFqn.isBlank()
                && enumHasConstant(preferredEnumTypeFqn, constant, context)
                && prefixMatchesPreferredType(prefix, preferredEnumTypeFqn)) {
            return preferredEnumTypeFqn + "." + constant;
        }

        List<String> matches = findConstantsUnderPrefix(prefix, constant, context);
        if (matches.size() == 1) {
            return matches.get(0);
        }
        if (preferredEnumTypeFqn != null) {
            for (String match : matches) {
                if (match.startsWith(preferredEnumTypeFqn + ".")) {
                    return match;
                }
            }
        }
        return value;
    }

    /**
     * True when {@code value} looks like the old package+CONSTANT truncation
     * ({@code com.example.PAY}) rather than a real {@code Type.CONSTANT} or routing key.
     */
    public static boolean looksLikePackageTruncatedEnumRef(String value) {
        if (value == null || value.isBlank() || value.contains("(")) {
            return false;
        }
        String stripped = stripQuotes(value.trim());
        int lastDot = stripped.lastIndexOf('.');
        if (lastDot <= 0 || lastDot >= stripped.length() - 1) {
            return false;
        }
        String constant = stripped.substring(lastDot + 1);
        if (!ENUM_CONSTANT.matcher(constant).matches()) {
            return false;
        }
        String prefix = stripped.substring(0, lastDot);
        int prevDot = prefix.lastIndexOf('.');
        String lastSegment = prevDot >= 0 ? prefix.substring(prevDot + 1) : prefix;
        return !lastSegment.isEmpty() && Character.isLowerCase(lastSegment.charAt(0));
    }

    private static String reconstructBareConstant(
            String constant, String preferredEnumTypeFqn, CodebaseContext context, String original) {
        if (!ENUM_CONSTANT.matcher(constant).matches() || preferredEnumTypeFqn == null) {
            return original;
        }
        if (enumHasConstant(preferredEnumTypeFqn, constant, context)) {
            return preferredEnumTypeFqn + "." + constant;
        }
        return original;
    }

    private static boolean prefixMatchesPreferredType(String prefix, String preferredEnumTypeFqn) {
        if (prefix.equals(preferredEnumTypeFqn)) {
            return true;
        }
        String preferredPackage = packageOf(preferredEnumTypeFqn);
        if (prefix.equals(preferredPackage)) {
            return true;
        }
        return preferredEnumTypeFqn.startsWith(prefix + ".");
    }

    private static List<String> findConstantsUnderPrefix(String prefix, String constant, CodebaseContext context) {
        LinkedHashSet<String> matches = new LinkedHashSet<>();
        for (Map.Entry<String, List<String>> entry : context.getEnumValuesMap().entrySet()) {
            String enumFqn = entry.getKey();
            if (!enumUnderPrefix(enumFqn, prefix)) {
                continue;
            }
            String candidate = enumFqn + "." + constant;
            List<String> values = entry.getValue();
            if (values == null) {
                continue;
            }
            if (values.contains(candidate)
                    || values.stream().anyMatch(v -> v != null && v.endsWith("." + constant))) {
                matches.add(candidate);
            }
        }
        return new ArrayList<>(matches);
    }

    private static boolean enumUnderPrefix(String enumFqn, String prefix) {
        if (enumFqn == null || prefix == null) {
            return false;
        }
        if (enumFqn.equals(prefix)) {
            return true;
        }
        if (enumFqn.startsWith(prefix + ".")) {
            return true;
        }
        return prefix.equals(packageOf(enumFqn));
    }

    private static boolean isKnownEnumType(String typeName, CodebaseContext context) {
        if (typeName == null || typeName.isBlank()) {
            return false;
        }
        if (context.getEnumValuesMap().containsKey(typeName)) {
            return true;
        }
        List<String> values = context.getEnumValues(typeName);
        return values != null && !values.isEmpty();
    }

    private static boolean enumHasConstant(String enumTypeFqn, String constant, CodebaseContext context) {
        List<String> values = context.getEnumValues(enumTypeFqn);
        if (values == null || values.isEmpty()) {
            return false;
        }
        String suffix = "." + constant;
        for (String value : values) {
            if (value != null && (value.endsWith(suffix) || value.equals(constant))) {
                return true;
            }
        }
        return false;
    }

    private static String canonicalConstantRef(String typeName, String constant, CodebaseContext context) {
        List<String> values = context.getEnumValues(typeName);
        if (values == null) {
            return null;
        }
        String suffix = "." + constant;
        for (String value : values) {
            if (value != null && value.endsWith(suffix)) {
                return value;
            }
        }
        return null;
    }

    private static String packageOf(String typeFqn) {
        int dot = typeFqn.lastIndexOf('.');
        return dot > 0 ? typeFqn.substring(0, dot) : "";
    }

    private static String stripQuotes(String value) {
        if (value.length() >= 2
                && ((value.startsWith("\"") && value.endsWith("\""))
                || (value.startsWith("'") && value.endsWith("'")))) {
            return value.substring(1, value.length() - 1);
        }
        return value;
    }
}
