<?php

/*
 * Copyright 2024 Google LLC
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
namespace DeliciousBrains\WP_Offload_Media\Gcp\Google\ApiCore;

use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\CredentialsLoader;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\FetchAuthTokenInterface;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\GetUniverseDomainInterface;
use DeliciousBrains\WP_Offload_Media\Gcp\Grpc\Gcp\ApiConfig;
use DeliciousBrains\WP_Offload_Media\Gcp\Grpc\Gcp\Config;
/**
 * Common functions used to work with various clients.
 *
 * @internal
 */
trait ClientOptionsTrait
{
    use ArrayTrait;
    private static $gapicVersionFromFile;
    private static function getGapicVersion(array $options)
    {
        if (isset($options['libVersion'])) {
            return $options['libVersion'];
        }
        if (!isset(self::$gapicVersionFromFile)) {
            self::$gapicVersionFromFile = AgentHeader::readGapicVersionFromFile(__CLASS__);
        }
        return self::$gapicVersionFromFile;
    }
    private static function initGrpcGcpConfig(string $hostName, string $confPath)
    {
        $apiConfig = new ApiConfig();
        $apiConfig->mergeFromJsonString(\file_get_contents($confPath));
        $config = new Config($hostName, $apiConfig);
        return $config;
    }
    /**
     * Get default options. This function should be "overridden" by clients using late static
     * binding to provide default options to the client.
     *
     * @return array
     * @access private
     */
    private static function getClientDefaults()
    {
        return [];
    }
    private function buildClientOptions(array $options)
    {
        // Build $defaultOptions starting from top level
        // variables, then going into deeper nesting, so that
        // we will not encounter missing keys
        $defaultOptions = self::getClientDefaults();
        $defaultOptions += ['disableRetries' => \false, 'credentials' => null, 'credentialsConfig' => [], 'transport' => null, 'transportConfig' => [], 'gapicVersion' => self::getGapicVersion($options), 'libName' => null, 'libVersion' => null, 'apiEndpoint' => null, 'clientCertSource' => null, 'universeDomain' => null];
        $supportedTransports = $this->supportedTransports();
        foreach ($supportedTransports as $transportName) {
            if (!\array_key_exists($transportName, $defaultOptions['transportConfig'])) {
                $defaultOptions['transportConfig'][$transportName] = [];
            }
        }
        if (\in_array('grpc', $supportedTransports)) {
            $defaultOptions['transportConfig']['grpc'] = ['stubOpts' => ['grpc.service_config_disable_resolution' => 1]];
        }
        // Keep track of the API Endpoint
        $apiEndpoint = $options['apiEndpoint'] ?? null;
        // Merge defaults into $options starting from top level
        // variables, then going into deeper nesting, so that
        // we will not encounter missing keys
        $options += $defaultOptions;
        $options['credentialsConfig'] += $defaultOptions['credentialsConfig'];
        $options['transportConfig'] += $defaultOptions['transportConfig'];
        // @phpstan-ignore-line
        if (isset($options['transportConfig']['grpc'])) {
            $options['transportConfig']['grpc'] += $defaultOptions['transportConfig']['grpc'];
            $options['transportConfig']['grpc']['stubOpts'] += $defaultOptions['transportConfig']['grpc']['stubOpts'];
        }
        if (isset($options['transportConfig']['rest'])) {
            $options['transportConfig']['rest'] += $defaultOptions['transportConfig']['rest'];
        }
        // These calls do not apply to "New Surface" clients.
        if ($this->isBackwardsCompatibilityMode()) {
            $preModifiedOptions = $options;
            $this->modifyClientOptions($options);
            // NOTE: this is required to ensure backwards compatiblity with $options['apiEndpoint']
            if ($options['apiEndpoint'] !== $preModifiedOptions['apiEndpoint']) {
                $apiEndpoint = $options['apiEndpoint'];
            }
            // serviceAddress is now deprecated and acts as an alias for apiEndpoint
            if (isset($options['serviceAddress'])) {
                $apiEndpoint = $this->pluck('serviceAddress', $options, \false);
            }
        } else {
            // Ads is using this method in their new surface clients, so we need to call it.
            // However, this method is not used anywhere else for the new surface clients
            // @TODO: Remove this in GAX V2
            $this->modifyClientOptions($options);
        }
        // If an API endpoint is different form the default, ensure the "audience" does not conflict
        // with the custom endpoint by setting "user defined" scopes.
        if ($apiEndpoint && $apiEndpoint != $defaultOptions['apiEndpoint'] && empty($options['credentialsConfig']['scopes']) && !empty($options['credentialsConfig']['defaultScopes'])) {
            $options['credentialsConfig']['scopes'] = $options['credentialsConfig']['defaultScopes'];
        }
        // mTLS: detect and load the default clientCertSource if the environment variable
        // "GOOGLE_API_USE_CLIENT_CERTIFICATE" is true, and the cert source is available
        if (empty($options['clientCertSource']) && CredentialsLoader::shouldLoadClientCertSource()) {
            if ($defaultCertSource = CredentialsLoader::getDefaultClientCertSource()) {
                $options['clientCertSource'] = function () use($defaultCertSource) {
                    $cert = \call_user_func($defaultCertSource);
                    // the key and the cert are returned in one string
                    return [$cert, $cert];
                };
            }
        }
        // mTLS: If no apiEndpoint has been supplied by the user, and either
        // GOOGLE_API_USE_MTLS_ENDPOINT tells us to, or mTLS is available, use the mTLS endpoint.
        if (\is_null($apiEndpoint) && $this->shouldUseMtlsEndpoint($options)) {
            $apiEndpoint = self::determineMtlsEndpoint($options['apiEndpoint']);
        }
        // If the user has not supplied a universe domain, use the environment variable if set.
        // Otherwise, use the default ("googleapis.com").
        $options['universeDomain'] ??= \getenv('GOOGLE_CLOUD_UNIVERSE_DOMAIN') ?: GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN;
        // mTLS: It is not valid to configure mTLS outside of "googleapis.com" (yet)
        if (isset($options['clientCertSource']) && $options['universeDomain'] !== GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN) {
            throw new ValidationException('mTLS is not supported outside the "googleapis.com" universe');
        }
        if (\is_null($apiEndpoint)) {
            if (\defined('self::SERVICE_ADDRESS_TEMPLATE')) {
                // Derive the endpoint from the service address template and the universe domain
                $apiEndpoint = \str_replace('UNIVERSE_DOMAIN', $options['universeDomain'], self::SERVICE_ADDRESS_TEMPLATE);
            } else {
                // For older clients, the service address template does not exist. Use the default
                // endpoint instead.
                $apiEndpoint = $defaultOptions['apiEndpoint'];
            }
        }
        if (\extension_loaded('sysvshm') && isset($options['gcpApiConfigPath']) && \file_exists($options['gcpApiConfigPath']) && !empty($apiEndpoint)) {
            $grpcGcpConfig = self::initGrpcGcpConfig($apiEndpoint, $options['gcpApiConfigPath']);
            if (!\array_key_exists('stubOpts', $options['transportConfig']['grpc'])) {
                $options['transportConfig']['grpc']['stubOpts'] = [];
            }
            $options['transportConfig']['grpc']['stubOpts'] += ['grpc_call_invoker' => $grpcGcpConfig->callInvoker()];
        }
        $options['apiEndpoint'] = $apiEndpoint;
        return $options;
    }
    private function shouldUseMtlsEndpoint(array $options)
    {
        $mtlsEndpointEnvVar = \getenv('GOOGLE_API_USE_MTLS_ENDPOINT');
        if ('always' === $mtlsEndpointEnvVar) {
            return \true;
        }
        if ('never' === $mtlsEndpointEnvVar) {
            return \false;
        }
        // For all other cases, assume "auto" and return true if clientCertSource exists
        return !empty($options['clientCertSource']);
    }
    private static function determineMtlsEndpoint(string $apiEndpoint)
    {
        $parts = \explode('.', $apiEndpoint);
        if (\count($parts) < 3) {
            return $apiEndpoint;
            // invalid endpoint!
        }
        return \sprintf('%s.mtls.%s', \array_shift($parts), \implode('.', $parts));
    }
    /**
     * @param mixed $credentials
     * @param array $credentialsConfig
     * @return CredentialsWrapper
     * @throws ValidationException
     */
    private function createCredentialsWrapper($credentials, array $credentialsConfig, string $universeDomain)
    {
        if (\is_null($credentials)) {
            return CredentialsWrapper::build($credentialsConfig, $universeDomain);
        } elseif (\is_string($credentials) || \is_array($credentials)) {
            return CredentialsWrapper::build(['keyFile' => $credentials] + $credentialsConfig, $universeDomain);
        } elseif ($credentials instanceof FetchAuthTokenInterface) {
            $authHttpHandler = $credentialsConfig['authHttpHandler'] ?? null;
            return new CredentialsWrapper($credentials, $authHttpHandler, $universeDomain);
        } elseif ($credentials instanceof CredentialsWrapper) {
            return $credentials;
        } else {
            throw new ValidationException('Unexpected value in $auth option, got: ' . \print_r($credentials, \true));
        }
    }
    /**
     * This defaults to all three transports, which One-Platform supports.
     * Discovery clients should define this function and only return ['rest'].
     */
    private static function supportedTransports()
    {
        return ['grpc', 'grpc-fallback', 'rest'];
    }
    // Gapic Client Extension Points
    // The methods below provide extension points that can be used to customize client
    // functionality. These extension points are currently considered
    // private and may change at any time.
    /**
     * Modify options passed to the client before calling setClientOptions.
     *
     * @param array $options
     * @access private
     * @internal
     */
    protected function modifyClientOptions(array &$options)
    {
        // Do nothing - this method exists to allow option modification by partial veneers.
    }
    /**
     * @internal
     */
    private function isBackwardsCompatibilityMode() : bool
    {
        return \false;
    }
}
