package de.gecheckt.pdf.umbenenner.adapter.out.configuration;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import de.gecheckt.pdf.umbenenner.adapter.out.bootstrap.validation.InvalidStartConfigurationException;
import de.gecheckt.pdf.umbenenner.application.config.provider.AiProviderFamily;
import de.gecheckt.pdf.umbenenner.application.config.provider.MultiProviderConfiguration;
import de.gecheckt.pdf.umbenenner.application.config.provider.ProviderConfiguration;

/**
 * Validates a {@link MultiProviderConfiguration} before the application run begins.
 * <p>
 * Enforces all requirements for the active provider:
 * <ul>
 *   <li>{@code ai.provider.active} refers to a recognised provider family.</li>
 *   <li>{@code model} is non-blank.</li>
 *   <li>{@code timeoutSeconds} is a positive integer.</li>
 *   <li>{@code baseUrl} is a syntactically valid absolute URI with scheme {@code http} or
 *       {@code https} (required for the OpenAI-compatible family; the Claude family always
 *       has a default, but it is validated with the same rules).</li>
 *   <li>{@code apiKey} is non-blank after environment-variable precedence has been applied
 *       by {@link MultiProviderConfigurationParser}.</li>
 * </ul>
 * Required fields of the <em>inactive</em> provider are intentionally not enforced.
 * <p>
 * Validation errors are aggregated and reported together in a single
 * {@link InvalidStartConfigurationException}.
 */
public class MultiProviderConfigurationValidator {

    /**
     * Validates the given multi-provider configuration.
     * <p>
     * Only the active provider's required fields are validated. The inactive provider's
     * configuration may be incomplete.
     *
     * @param config the configuration to validate; must not be {@code null}
     * @throws InvalidStartConfigurationException if any validation rule fails, with an aggregated
     *         message listing all problems found
     */
    public void validate(MultiProviderConfiguration config) {
        List<String> errors = new ArrayList<>();

        validateActiveProvider(config, errors);

        if (!errors.isEmpty()) {
            throw new InvalidStartConfigurationException(
                    "Invalid AI provider configuration:\n" + String.join("\n", errors));
        }
    }

    private void validateActiveProvider(MultiProviderConfiguration config, List<String> errors) {
        AiProviderFamily activeFamily = config.activeProviderFamily();
        if (activeFamily == null) {
            // Parser already throws for missing/unknown ai.provider.active,
            // but guard defensively in case the record is constructed directly in tests.
            errors.add("- ai.provider.active: must be set to a supported provider "
                    + "(openai-compatible, claude)");
            return;
        }

        ProviderConfiguration providerConfig = config.activeProviderConfiguration();
        String providerLabel = "ai.provider." + activeFamily.getIdentifier();

        validateModel(providerConfig, providerLabel, errors);
        validateTimeoutSeconds(providerConfig, providerLabel, errors);
        validateBaseUrl(activeFamily, providerConfig, providerLabel, errors);
        validateApiKey(providerConfig, providerLabel, errors);
    }

    private void validateModel(ProviderConfiguration config, String providerLabel, List<String> errors) {
        if (config.model() == null || config.model().isBlank()) {
            errors.add("- " + providerLabel + ".model: must not be blank");
        }
    }

    private void validateTimeoutSeconds(ProviderConfiguration config, String providerLabel,
                                        List<String> errors) {
        if (config.timeoutSeconds() <= 0) {
            errors.add("- " + providerLabel + ".timeoutSeconds: must be a positive integer, got: "
                    + config.timeoutSeconds());
        }
    }

    /**
     * Validates the base URL of the active provider.
     * <p>
     * The URL must be:
     * <ul>
     *   <li>non-blank</li>
     *   <li>a syntactically valid URI</li>
     *   <li>an absolute URI (has a scheme component)</li>
     *   <li>using scheme {@code http} or {@code https}</li>
     * </ul>
     * The OpenAI-compatible family requires an explicit base URL.
     * The Claude family always has a default ({@code https://api.anthropic.com}) applied by the
     * parser, so this check serves both as a primary and safety-net enforcement.
     */
    private void validateBaseUrl(AiProviderFamily family, ProviderConfiguration config,
                                 String providerLabel, List<String> errors) {
        String baseUrl = config.baseUrl();
        if (baseUrl == null || baseUrl.isBlank()) {
            errors.add("- " + providerLabel + ".baseUrl: must not be blank");
            return;
        }
        try {
            URI uri = URI.create(baseUrl);
            if (!uri.isAbsolute()) {
                errors.add("- " + providerLabel + ".baseUrl: must be an absolute URI with http or https scheme, got: '"
                        + baseUrl + "'");
                return;
            }
            String scheme = uri.getScheme();
            if (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme)) {
                errors.add("- " + providerLabel + ".baseUrl: scheme must be http or https, got: '"
                        + scheme + "' in '" + baseUrl + "'");
            }
        } catch (IllegalArgumentException e) {
            errors.add("- " + providerLabel + ".baseUrl: not a valid URI: '" + baseUrl + "' (" + e.getMessage() + ")");
        }
    }

    private void validateApiKey(ProviderConfiguration config, String providerLabel,
                                List<String> errors) {
        if (config.apiKey() == null || config.apiKey().isBlank()) {
            errors.add("- " + providerLabel + ".apiKey: must not be blank "
                    + "(set via environment variable or properties)");
        }
    }
}
