package click.kamil.springstatemachineexporter.analysis.index;

import click.kamil.springstatemachineexporter.ast.common.CodebaseContext;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.TypeDeclaration;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

@Slf4j
public final class PolymorphicAccessorIndexBuilder {

    public PolymorphicAccessorIndex build(CodebaseContext context, AccessorIndex accessorIndex) {
        if (context == null || accessorIndex == null) {
            return PolymorphicAccessorIndex.empty();
        }

        Map<String, Map<String, PolymorphicAccessorEntry>> entries = new HashMap<>();
        int entryCount = 0;

        for (String typeFqn : context.getIndexedTypeFqns()) {
            TypeDeclaration typeDeclaration = context.getTypeDeclaration(typeFqn);
            if (!isPolymorphicType(typeDeclaration)) {
                continue;
            }
            List<String> implementations = context.getImplementations(typeFqn);
            if (implementations.isEmpty()) {
                continue;
            }

            Set<String> methodNames = collectGetterMethodNames(context, accessorIndex, typeFqn, implementations);
            for (String methodName : methodNames) {
                PolymorphicAccessorEntry entry = buildEntry(
                        context, accessorIndex, typeFqn, methodName, implementations);
                if (entry == null) {
                    continue;
                }
                entries.computeIfAbsent(typeFqn, ignored -> new HashMap<>()).put(methodName, entry);
                entryCount++;
            }
        }

        log.info("Built polymorphic accessor index: {} entries across {} types", entryCount, entries.size());
        return new PolymorphicAccessorIndex(entries, entryCount);
    }

    private static PolymorphicAccessorEntry buildEntry(
            CodebaseContext context,
            AccessorIndex accessorIndex,
            String ownerFqn,
            String methodName,
            List<String> implementations) {
        LinkedHashSet<String> widenedConstants = new LinkedHashSet<>();
        widenedConstants.addAll(ScanTimeAccessorConstants.resolve(context, accessorIndex, ownerFqn, methodName));

        Optional<AccessorSummary> representative = findRepresentativeImplAccessor(accessorIndex, implementations, methodName);
        for (String implementationFqn : implementations) {
            widenedConstants.addAll(
                    ScanTimeAccessorConstants.resolve(context, accessorIndex, implementationFqn, methodName));
        }

        if (representative.isEmpty() && widenedConstants.isEmpty()) {
            return null;
        }
        boolean staticallyComplete = isStaticallyComplete(accessorIndex, methodName, implementations);
        return new PolymorphicAccessorEntry(
                ownerFqn,
                methodName,
                representative,
                List.copyOf(widenedConstants),
                staticallyComplete);
    }

    private static boolean isStaticallyComplete(
            AccessorIndex accessorIndex,
            String methodName,
            List<String> implementations) {
        for (String implementationFqn : implementations) {
            if (!isConstantLikeAccessor(accessorIndex, implementationFqn, methodName)) {
                return false;
            }
        }
        return true;
    }

    private static boolean isConstantLikeAccessor(
            AccessorIndex accessorIndex,
            String typeFqn,
            String methodName) {
        Optional<AccessorSummary> accessor = accessorIndex.lookup(typeFqn, methodName);
        if (accessor.isEmpty() || !accessor.get().isGetter()) {
            return false;
        }
        return accessor.get().kind() == AccessorKind.CONSTANT_GETTER;
    }

    private static Optional<AccessorSummary> findRepresentativeImplAccessor(
            AccessorIndex accessorIndex,
            List<String> implementations,
            String methodName) {
        for (String implementationFqn : implementations) {
            Optional<AccessorSummary> implAccessor = accessorIndex.lookup(implementationFqn, methodName);
            if (implAccessor.isPresent() && implAccessor.get().isGetter()) {
                return implAccessor;
            }
        }
        return Optional.empty();
    }

    private static Set<String> collectGetterMethodNames(
            CodebaseContext context,
            AccessorIndex accessorIndex,
            String ownerFqn,
            List<String> implementations) {
        Set<String> methodNames = new LinkedHashSet<>();
        for (AccessorSummary accessor : accessorIndex.accessorsForType(ownerFqn)) {
            if (accessor.isGetter()) {
                methodNames.add(accessor.methodName());
            }
        }
        TypeDeclaration ownerType = context.getTypeDeclaration(ownerFqn);
        if (ownerType != null) {
            for (MethodDeclaration method : ownerType.getMethods()) {
                String name = method.getName().getIdentifier();
                if (looksLikeGetterName(name)) {
                    methodNames.add(name);
                }
            }
        }
        for (String implementationFqn : implementations) {
            for (AccessorSummary accessor : accessorIndex.accessorsForType(implementationFqn)) {
                if (accessor.isGetter()) {
                    methodNames.add(accessor.methodName());
                }
            }
        }
        return methodNames;
    }

    private static boolean isPolymorphicType(TypeDeclaration typeDeclaration) {
        return typeDeclaration != null
                && (typeDeclaration.isInterface()
                || Modifier.isAbstract(typeDeclaration.getModifiers()));
    }

    private static boolean looksLikeGetterName(String methodName) {
        return methodName.startsWith("get") || methodName.startsWith("is");
    }
}
