package click.kamil.springstatemachineexporter.analysis.spring;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class SpringDependencyResolver {
    private final SpringBeanRegistry registry;

    public SpringDependencyResolver(SpringBeanRegistry registry) {
        this.registry = registry;
    }

    /**
     * Resolves the exact SpringBean that would be injected for a given injection point.
     *
     * @param requiredTypeFqn The exact FQN of the type requested at the injection point.
     * @param qualifier       The value of the @Qualifier annotation on the injection point (if any).
     * @param injectionName   The name of the field or parameter being injected (used as fallback).
     * @return A list of resolved beans. Usually 1. If 0, unresolved. If > 1, ambiguous.
     */
    public List<SpringBean> resolve(String requiredTypeFqn, String qualifier, String injectionName) {
        if (requiredTypeFqn == null) return new ArrayList<>();
        log.debug("RESOLVING {} qual={} injectionName={}", requiredTypeFqn, qualifier, injectionName);

        // 1. Filter by Type (Exact FQN or Assignable Type)
        List<SpringBean> candidates = registry.getBeans().stream()
                .filter(bean -> requiredTypeFqn.equals(bean.getTypeFqn()) || bean.getAssignableTypes().contains(requiredTypeFqn))
                .collect(Collectors.toList());

        log.debug("CANDIDATES AFTER TYPE: {}", candidates.size());
        if (log.isDebugEnabled()) {
            log.debug(
                    "CANDIDATE DETAILS: {}",
                    candidates.stream()
                            .map(c -> c.getTypeFqn()
                                    + " (primary=" + c.isPrimary()
                                    + " names=" + c.getBeanNames()
                                    + " qual=" + c.getQualifiers() + ")")
                            .collect(Collectors.toList()));
        }
        if (candidates.isEmpty() || candidates.size() == 1) {
            log.debug("RETURNING: {}", candidates.size());
            return candidates;
        }

        // 2. Filter by Qualifier (if provided)
        if (qualifier != null && !qualifier.isEmpty()) {
            List<SpringBean> qualifiedCandidates = candidates.stream()
                    .filter(bean -> bean.getQualifiers().contains(qualifier) || bean.getBeanNames().contains(qualifier))
                    .collect(Collectors.toList());
            if (qualifiedCandidates.size() == 1) {
                return qualifiedCandidates;
            } else if (!qualifiedCandidates.isEmpty()) {
                candidates = qualifiedCandidates;
            }
        }

        // 3. Filter by @Primary
        List<SpringBean> primaryCandidates = candidates.stream()
                .filter(SpringBean::isPrimary)
                .collect(Collectors.toList());
        if (primaryCandidates.size() == 1) {
            return primaryCandidates;
        }

        // 4. Fallback to Injection Name (field name or parameter name)
        if (injectionName != null && !injectionName.isEmpty()) {
            List<SpringBean> namedCandidates = candidates.stream()
                    .filter(bean -> bean.getBeanNames().contains(injectionName))
                    .collect(Collectors.toList());
            if (namedCandidates.size() == 1) {
                return namedCandidates;
            }
        }

        // 5. Fallback to @Order (lowest order wins)
        Integer minOrder = candidates.stream()
                .map(SpringBean::getOrder)
                .filter(o -> o != null)
                .min(Integer::compareTo)
                .orElse(null);

        if (minOrder != null) {
            List<SpringBean> orderedCandidates = candidates.stream()
                    .filter(bean -> bean.getOrder() != null && bean.getOrder().equals(minOrder))
                    .collect(Collectors.toList());
            if (!orderedCandidates.isEmpty()) {
                candidates = orderedCandidates;
            }
        }

        // Return whatever candidates remain (could be ambiguous)
        log.debug("RETURNING: {}", candidates.size());
        return candidates;
    }
}
