<?php

namespace VendorDuplicator\Aws;

use VendorDuplicator\Aws\EndpointDiscovery\Configuration;
use Closure;
use VendorDuplicator\Psr\Http\Message\RequestInterface;
/**
 * Builds and injects the user agent header values.
 * This middleware must be appended into step where all the
 * metrics to be gathered are already resolved. As of now it should be
 * after the signing step.
 */
class UserAgentMiddleware
{
    const AGENT_VERSION = 2.1;
    static $userAgentFnList = ['getSdkVersion', 'getUserAgentVersion', 'getHhvmVersion', 'getOsName', 'getLangVersion', 'getExecEnv', 'getEndpointDiscovery', 'getAppId', 'getMetrics'];
    static $metricsFnList = ['appendEndpointMetric', 'appendAccountIdModeMetric', 'appendRetryConfigMetric'];
    /** @var callable  */
    private $nextHandler;
    /** @var array */
    private $args;
    /** @var MetricsBuilder */
    private $metricsBuilder;
    /**
     * Returns a middleware wrapper function.
     *
     * @param array $args
     *
     * @return Closure
     */
    public static function wrap(array $args): Closure
    {
        return function (callable $handler) use ($args) {
            return new self($handler, $args);
        };
    }
    /**
     * @param callable $nextHandler
     * @param array $args
     */
    public function __construct(callable $nextHandler, array $args = [])
    {
        $this->nextHandler = $nextHandler;
        $this->args = $args;
    }
    /**
     * When invoked, its injects the user agent header into the
     * request headers.
     *
     * @param CommandInterface $command
     * @param RequestInterface $request
     *
     * @return mixed
     */
    public function __invoke(CommandInterface $command, RequestInterface $request)
    {
        $handler = $this->nextHandler;
        $this->metricsBuilder = MetricsBuilder::fromCommand($command);
        $request = $this->requestWithUserAgentHeader($request);
        return $handler($command, $request);
    }
    /**
     * Builds the user agent header value, and injects it into the request
     * headers. Then, it returns the mutated request.
     *
     * @param RequestInterface $request
     *
     * @return RequestInterface
     */
    private function requestWithUserAgentHeader(RequestInterface $request): RequestInterface
    {
        $uaAppend = $this->args['ua_append'] ?? [];
        $userAgentValue = array_merge($this->buildUserAgentValue(), $uaAppend);
        // It includes the user agent values just for the User-Agent header.
        // The reason is that the SEP does not mention appending the
        // metrics into the X-Amz-User-Agent header.
        return $request->withHeader('User-Agent', implode(' ', array_merge($userAgentValue, $request->getHeader('User-Agent'))));
    }
    /**
     * Builds the different user agent values.
     *
     * @return array
     */
    private function buildUserAgentValue(): array
    {
        $userAgentValue = [];
        foreach (self::$userAgentFnList as $fn) {
            $val = $this->{$fn}();
            if (!empty($val)) {
                $userAgentValue[] = $val;
            }
        }
        return $userAgentValue;
    }
    /**
     * Returns the user agent value for SDK version.
     *
     * @return string
     */
    private function getSdkVersion(): string
    {
        return 'aws-sdk-php/' . Sdk::VERSION;
    }
    /**
     * Returns the user agent value for the agent version.
     *
     * @return string
     */
    private function getUserAgentVersion(): string
    {
        return 'ua/' . self::AGENT_VERSION;
    }
    /**
     * Returns the user agent value for the hhvm version, but just
     * when it is defined.
     *
     * @return string
     */
    private function getHhvmVersion(): string
    {
        if (defined('HHVM_VERSION')) {
            return 'HHVM/' . HHVM_VERSION;
        }
        return "";
    }
    /**
     * Returns the user agent value for the os version.
     *
     * @return string
     */
    private function getOsName(): string
    {
        $disabledFunctions = explode(',', ini_get('disable_functions'));
        if (function_exists('php_uname') && !in_array('php_uname', $disabledFunctions, \true)) {
            $osName = "OS/" . php_uname('s') . '#' . php_uname('r');
            if (!empty($osName)) {
                return $osName;
            }
        }
        return "";
    }
    /**
     * Returns the user agent value for the php language used.
     *
     * @return string
     */
    private function getLangVersion(): string
    {
        return 'lang/php#' . phpversion();
    }
    /**
     * Returns the user agent value for the execution env.
     *
     * @return string
     */
    private function getExecEnv(): string
    {
        if ($executionEnvironment = getenv('AWS_EXECUTION_ENV')) {
            return $executionEnvironment;
        }
        return "";
    }
    /**
     * Returns the user agent value for endpoint discovery as cfg.
     * This feature is deprecated.
     *
     * @return string
     */
    private function getEndpointDiscovery(): string
    {
        $args = $this->args;
        if (isset($args['endpoint_discovery'])) {
            if ($args['endpoint_discovery'] instanceof Configuration && $args['endpoint_discovery']->isEnabled()) {
                return 'cfg/endpoint-discovery';
            } elseif (is_array($args['endpoint_discovery']) && isset($args['endpoint_discovery']['enabled']) && $args['endpoint_discovery']['enabled']) {
                return 'cfg/endpoint-discovery';
            }
        }
        return "";
    }
    /**
     * Returns the user agent value for app id, but just when an
     * app id was provided as a client argument.
     *
     * @return string
     */
    private function getAppId(): string
    {
        if (empty($this->args['app_id'])) {
            return "";
        }
        return 'app/' . $this->args['app_id'];
    }
    /**
     * Returns the user agent value for metrics.
     *
     * @return string
     */
    private function getMetrics(): string
    {
        foreach (self::$metricsFnList as $fn) {
            $this->{$fn}();
        }
        $metricsEncoded = $this->metricsBuilder->build();
        if (empty($metricsEncoded)) {
            return "";
        }
        return "m/" . $metricsEncoded;
    }
    /**
     * Appends the endpoint metric into the metrics builder,
     * just if a custom endpoint was provided at client construction.
     */
    private function appendEndpointMetric(): void
    {
        if (!empty($this->args['endpoint_override'])) {
            $this->metricsBuilder->append(MetricsBuilder::ENDPOINT_OVERRIDE);
        }
    }
    /**
     * Appends the account id endpoint mode metric into the metrics builder,
     * based on the account id endpoint mode provide as client argument.
     */
    private function appendAccountIdModeMetric(): void
    {
        $accountIdMode = $this->args['account_id_endpoint_mode'] ?? null;
        if ($accountIdMode === null) {
            return;
        }
        if ($accountIdMode === 'preferred') {
            $this->metricsBuilder->append(MetricsBuilder::ACCOUNT_ID_MODE_PREFERRED);
        } elseif ($accountIdMode === 'disabled') {
            $this->metricsBuilder->append(MetricsBuilder::ACCOUNT_ID_MODE_DISABLED);
        } elseif ($accountIdMode === 'required') {
            $this->metricsBuilder->append(MetricsBuilder::ACCOUNT_ID_MODE_REQUIRED);
        }
    }
    /**
     * Appends the retry mode metric into the metrics builder,
     * based on the resolved retry config mode.
     */
    private function appendRetryConfigMetric(): void
    {
        $retries = $this->args['retries'] ?? null;
        if ($retries === null) {
            return;
        }
        $retryMode = '';
        if ($retries instanceof \VendorDuplicator\Aws\Retry\Configuration) {
            $retryMode = $retries->getMode();
        } elseif (is_array($retries) && isset($retries["mode"])) {
            $retryMode = $retries["mode"];
        }
        if ($retryMode === 'legacy') {
            $this->metricsBuilder->append(MetricsBuilder::RETRY_MODE_LEGACY);
        } elseif ($retryMode === 'standard') {
            $this->metricsBuilder->append(MetricsBuilder::RETRY_MODE_STANDARD);
        } elseif ($retryMode === 'adaptive') {
            $this->metricsBuilder->append(MetricsBuilder::RETRY_MODE_ADAPTIVE);
        }
    }
}
