package de.gecheckt.pdf.umbenenner.bootstrap.adapter;

import java.util.Objects;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.AiModelCatalogPort;
import de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.ModelCatalogRequest;
import de.gecheckt.pdf.umbenenner.application.port.out.modelcatalog.ModelCatalogResult;

/**
 * Composition-root dispatcher that routes {@link AiModelCatalogPort#fetchAvailableModels}
 * calls to the concrete adapter matching the provider identifier carried in the request.
 * <p>
 * This class is not a new architectural layer between the port contract and its adapters.
 * It is a Bootstrap-only wiring convenience that allows the GUI to call a single
 * {@link AiModelCatalogPort} instance regardless of which provider the user has currently
 * selected. The concrete adapters remain independent of each other and of this class.
 * <p>
 * <strong>Dispatching rule:</strong> The value of
 * {@link ModelCatalogRequest#providerIdentifier()} is matched case-sensitively against
 * the known provider identifiers:
 * <ul>
 *   <li>{@code "claude"} — routed to the Claude adapter</li>
 *   <li>{@code "openai-compatible"} — routed to the OpenAI-compatible adapter</li>
 *   <li>any other value — returns {@link ModelCatalogResult.IncompleteConfiguration}
 *       with a human-readable explanation</li>
 * </ul>
 * <p>
 * <strong>Thread safety:</strong> The dispatcher holds only immutable references to
 * the two concrete adapters and is trivially thread-safe.
 */
public class AiModelCatalogDispatcher implements AiModelCatalogPort {

    private static final Logger LOG = LogManager.getLogger(AiModelCatalogDispatcher.class);

    private static final String PROVIDER_CLAUDE = "claude";
    private static final String PROVIDER_OPENAI_COMPATIBLE = "openai-compatible";

    private final AiModelCatalogPort claudeAdapter;
    private final AiModelCatalogPort openAiCompatibleAdapter;

    /**
     * Creates a dispatcher wired with both supported adapter implementations.
     *
     * @param claudeAdapter              the adapter for the Claude provider family;
     *                                   must not be {@code null}
     * @param openAiCompatibleAdapter    the adapter for the OpenAI-compatible provider family;
     *                                   must not be {@code null}
     * @throws NullPointerException if either adapter is {@code null}
     */
    public AiModelCatalogDispatcher(AiModelCatalogPort claudeAdapter,
                                    AiModelCatalogPort openAiCompatibleAdapter) {
        this.claudeAdapter = Objects.requireNonNull(claudeAdapter,
                "claudeAdapter must not be null");
        this.openAiCompatibleAdapter = Objects.requireNonNull(openAiCompatibleAdapter,
                "openAiCompatibleAdapter must not be null");
    }

    /**
     * Routes the model catalogue request to the concrete adapter for the provider
     * identified by {@link ModelCatalogRequest#providerIdentifier()}.
     * <p>
     * If the provider identifier does not match any known provider, returns
     * {@link ModelCatalogResult.IncompleteConfiguration} without calling any adapter.
     * This keeps the GUI informed without any crash or exception.
     *
     * @param request all values needed to contact the provider; must not be {@code null}
     * @return the result from the matched adapter, or {@link ModelCatalogResult.IncompleteConfiguration}
     *         when the provider identifier is unknown; never {@code null}
     * @throws NullPointerException if {@code request} is {@code null}
     */
    @Override
    public ModelCatalogResult fetchAvailableModels(ModelCatalogRequest request) {
        Objects.requireNonNull(request, "request must not be null");

        String provider = request.providerIdentifier();
        LOG.debug("Model catalogue dispatch: routing request for provider '{}'", provider);

        return switch (provider) {
            case PROVIDER_CLAUDE -> claudeAdapter.fetchAvailableModels(request);
            case PROVIDER_OPENAI_COMPATIBLE -> openAiCompatibleAdapter.fetchAvailableModels(request);
            default -> {
                LOG.warn("Model catalogue dispatch: unknown provider identifier '{}'", provider);
                yield new ModelCatalogResult.IncompleteConfiguration(provider,
                        "Unbekannter Provider: '" + provider + "'");
            }
        };
    }
}
