<?php

declare(strict_types=1);

/**
 * WordPress Connectors credential provider.
 *
 * @package AIPT\Utils\Credentials
 * @since   2.6.0
 */

namespace AIPT\Utils\Credentials;

use AIPT\Core\Services\WordPressRuntimeDetector;
use AIPT\Utils\ProviderNameMapper;

/**
 * Resolves credentials from the WordPress 7.0 AI Connectors API.
 *
 * This provider mirrors WordPress core's documented Connectors credential
 * priority: environment variable, PHP constant, then database option. On
 * WordPress runtimes without the Connectors API it gracefully no-ops by
 * returning null credentials and the NONE source.
 *
 * @since 2.6.0
 */
final class ConnectorsCredentialProvider implements CredentialProviderInterface {

	/**
	 * Request-level resolved source cache keyed by AIPT provider slug.
	 *
	 * @since 2.6.0
	 * @var array<string, array{key: string|null, source: string}>
	 */
	private $resolved = array();

	/**
	 * Resolves an API key for the given AIPT provider.
	 *
	 * @since 2.6.0
	 *
	 * @param string $aiptProvider One of 'openai', 'gemini', 'claude', 'openrouter'.
	 * @return string|null The resolved API key, or null if not configured.
	 *
	 * @throws \InvalidArgumentException If $aiptProvider is not recognized.
	 */
	public function getKey( string $aiptProvider ): ?string {
		$resolved = $this->resolveSource( $aiptProvider );

		return $resolved['key'];
	}

	/**
	 * Returns the source of the credential for the given provider.
	 *
	 * @since 2.6.0
	 *
	 * @param string $aiptProvider One of 'openai', 'gemini', 'claude', 'openrouter'.
	 * @return string One of CredentialSource constants.
	 *
	 * @throws \InvalidArgumentException If $aiptProvider is not recognized.
	 */
	public function getSource( string $aiptProvider ): string {
		$resolved = $this->resolveSource( $aiptProvider );

		return $resolved['source'];
	}

	/**
	 * Checks if a credential is configured for the given provider.
	 *
	 * @since 2.6.0
	 *
	 * @param string $aiptProvider One of 'openai', 'gemini', 'claude', 'openrouter'.
	 * @return bool True if credential is configured, false otherwise.
	 *
	 * @throws \InvalidArgumentException If $aiptProvider is not recognized.
	 */
	public function isConfigured( string $aiptProvider ): bool {
		$key = $this->getKey( $aiptProvider );

		return null !== $key && '' !== $key;
	}

	/**
	 * Returns the human-readable label for the credential source.
	 *
	 * @since 2.6.0
	 *
	 * @param string $aiptProvider One of 'openai', 'gemini', 'claude', 'openrouter'.
	 * @return string Translated label suitable for display.
	 *
	 * @throws \InvalidArgumentException If $aiptProvider is not recognized.
	 */
	public function getSourceLabel( string $aiptProvider ): string {
		return CredentialSource::label( $this->getSource( $aiptProvider ) );
	}

	/**
	 * Clears request-level resolved credential memoization.
	 *
	 * Runtime credential writes call through CredentialResolver::flushMemoization()
	 * so subsequent reads in the same request observe fresh Connectors values.
	 *
	 * @since 2.6.0
	 *
	 * @return void
	 */
	public function clearCache(): void {
		$this->resolved = array();
	}

	/**
	 * Returns the Connectors environment variable name for a provider.
	 *
	 * @since 2.6.0
	 *
	 * @param string $aiptProvider One of 'openai', 'gemini', 'claude', 'openrouter'.
	 * @return string|null Environment variable name, or null if unavailable.
	 *
	 * @throws \InvalidArgumentException If $aiptProvider is not recognized.
	 */
	public function getEnvVarName( string $aiptProvider ): ?string {
		$authentication = $this->getAuthenticationConfig( $aiptProvider );

		if ( null === $authentication || empty( $authentication['env_var_name'] ) ) {
			return null;
		}

		return (string) $authentication['env_var_name'];
	}

	/**
	 * Returns the Connectors PHP constant name for a provider.
	 *
	 * @since 2.6.0
	 *
	 * @param string $aiptProvider One of 'openai', 'gemini', 'claude', 'openrouter'.
	 * @return string|null Constant name, or null if unavailable.
	 *
	 * @throws \InvalidArgumentException If $aiptProvider is not recognized.
	 */
	public function getConstantName( string $aiptProvider ): ?string {
		$authentication = $this->getAuthenticationConfig( $aiptProvider );

		if ( null === $authentication || empty( $authentication['constant_name'] ) ) {
			return null;
		}

		return (string) $authentication['constant_name'];
	}

	/**
	 * Resolves the credential key and source for a provider.
	 *
	 * Environment variables are read with getenv() instead of the PHP environment
	 * superglobal because that superglobal is often not populated consistently
	 * across PHP-FPM, safe_mode, or restricted hosting configurations.
	 *
	 * @since 2.6.0
	 *
	 * @param string $aiptProvider One of 'openai', 'gemini', 'claude', 'openrouter'.
	 * @return array{key: string|null, source: string} Resolved key and source.
	 *
	 * @throws \InvalidArgumentException If $aiptProvider is not recognized.
	 */
	private function resolveSource( string $aiptProvider ): array {
		$connector_id = ProviderNameMapper::toConnectorId( $aiptProvider );

		if ( isset( $this->resolved[ $aiptProvider ] ) ) {
			return $this->resolved[ $aiptProvider ];
		}

		$result = array(
			'key'    => null,
			'source' => CredentialSource::NONE,
		);

		if ( false === WordPressRuntimeDetector::supportsConnectorsApi() ) {
			$this->resolved[ $aiptProvider ] = $result;

			return $result;
		}

		$is_connector_registered = 'wp_is_connector_registered';
		$is_registered           = $is_connector_registered( $connector_id );
		if ( false === $is_registered ) {
			$this->resolved[ $aiptProvider ] = $result;

			return $result;
		}

		$get_connector = 'wp_get_connector';
		$connector     = $get_connector( $connector_id );
		if ( ! is_array( $connector ) ) {
			$this->resolved[ $aiptProvider ] = $result;

			return $result;
		}

		$authentication = isset( $connector['authentication'] ) && is_array( $connector['authentication'] )
			? $connector['authentication']
			: array();

		if ( ! empty( $authentication['env_var_name'] ) ) {
			$env_value = getenv( (string) $authentication['env_var_name'] );

			if ( false !== $env_value && '' !== $env_value ) {
				$result = array(
					'key'    => $env_value,
					'source' => CredentialSource::ENV,
				);

				$this->resolved[ $aiptProvider ] = $result;

				return $result;
			}
		}

		if ( ! empty( $authentication['constant_name'] ) && defined( (string) $authentication['constant_name'] ) ) {
			$constant_value = constant( (string) $authentication['constant_name'] );

			if ( '' !== $constant_value && is_string( $constant_value ) ) {
				$result = array(
					'key'    => $constant_value,
					'source' => CredentialSource::CONSTANT,
				);

				$this->resolved[ $aiptProvider ] = $result;

				return $result;
			}
		}

		if ( ! empty( $authentication['setting_name'] ) ) {
			$option_value = get_option( (string) $authentication['setting_name'], '' );

			if ( ! empty( $option_value ) ) {
				$result = array(
					'key'    => (string) $option_value,
					'source' => CredentialSource::DATABASE_CONNECTORS,
				);

				$this->resolved[ $aiptProvider ] = $result;

				return $result;
			}
		}

		$this->resolved[ $aiptProvider ] = $result;

		return $result;
	}

	/**
	 * Returns the Connectors authentication config for a provider.
	 *
	 * @since 2.6.0
	 *
	 * @param string $aiptProvider One of 'openai', 'gemini', 'claude', 'openrouter'.
	 * @return array<string, mixed>|null Authentication config, or null if unavailable.
	 *
	 * @throws \InvalidArgumentException If $aiptProvider is not recognized.
	 */
	private function getAuthenticationConfig( string $aiptProvider ): ?array {
		$connector_id = ProviderNameMapper::toConnectorId( $aiptProvider );

		if ( false === WordPressRuntimeDetector::supportsConnectorsApi() ) {
			return null;
		}

		$is_connector_registered = 'wp_is_connector_registered';
		$is_registered           = $is_connector_registered( $connector_id );
		if ( false === $is_registered ) {
			return null;
		}

		$get_connector = 'wp_get_connector';
		$connector     = $get_connector( $connector_id );
		if ( ! is_array( $connector ) || ! isset( $connector['authentication'] ) || ! is_array( $connector['authentication'] ) ) {
			return null;
		}

		return $connector['authentication'];
	}
}
