<?php
/**
 * 2007-2021 PrestaShop
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Academic Free License (AFL 3.0)
 * that is bundled with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://opensource.org/licenses/afl-3.0.php
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@prestashop.com, so we can send you a copy immediately.
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade PrestaShop to newer
 * versions in the future. If you wish to customize PrestaShop for your
 * needs please refer to http://www.prestashop.com for more information.
 *
 * @author    PrestaShop SA <contact@prestashop.com>
 * @copyright 2007-2021 PrestaShop SA
 * @license   http://opensource.org/licenses/afl-3.0.php  Academic Free License
 *            (AFL 3.0) International Registered Trademark & Property of
 *            PrestaShop SA
 */

// @codingStandardsIgnoreStart
/** @noinspection PhpMultipleClassDeclarationsInspection */
/** @noinspection PhpDeprecationInspection */
// @codingStandardsIgnoreEnd

namespace Prestashop\Module\AddonPayments\Services;

use addonpayments;
use ComerciaGlobalPayments\AddonPayments\SDK\Api\ApiClientInterface;
use ComerciaGlobalPayments\AddonPayments\SDK\Api\EPGJs;
use Configuration;
use Context;
use Exception;
use Hashids\Hashids;
use InvalidArgumentException;
use Module;
use Order;
use Prestashop\Module\AddonPayments\AddonPaymentsConfig;
use Prestashop\Module\AddonPayments\Helpers\SecurityTrait;
use Prestashop\Module\AddonPayments\Operations\Authorize;
use PrestaShopLogger;
use Tools;
use Transactions;

/**
 * Factory class for API client.
 */
class ApiFactory
{
    use SecurityTrait;

    private static array $CLIENTS
        = [
            EPGJs::NAME => EPGJs::class,
        ];

    public static function createInstance(string $name): ApiClientInterface
    {
        // @TODO: Change this later is only to allow using the trait.
        $self = new static();
        if (!array_key_exists($name, self::$CLIENTS)) {
            $allowed = implode(', ', array_keys(self::$CLIENTS));
            throw new InvalidArgumentException("Unknown API client '{name}', allowed values are: {allowed}");
        }

        $method = "crate{$name}Instance";
        if (method_exists(__CLASS__, $method)) {
            return self::$method();
        }

        $configuration = [
          'live' => (bool) Configuration::get(AddonPaymentsConfig::LIVE_MODE),
          'merchantId' => Configuration::get(AddonPaymentsConfig::MERCHANT_ID),
          'merchantKey' => Configuration::get(AddonPaymentsConfig::MERCHANT_KEY),
          'merchantSecret' => Configuration::get(AddonPaymentsConfig::MERCHANT_PASSWORD),
          'defaultProductId' => Configuration::get(AddonPaymentsConfig::EPG_PRODUCT_ID),
          'encryption' => Configuration::get(AddonPaymentsConfig::ENCRYPTION_MODE),
        ];
        // Decrypt credentials.
        foreach (['merchantId', 'merchantKey', 'merchantSecret'] as $key) {
            $configuration[$key] = $self->decrypt($configuration[$key],
                self::getEncryptionKey()
            );
        }

        $class = self::$CLIENTS[$name];

        return new $class($configuration);
    }

    /**
     * Load i18n strings to be used by EPGJs cashier.
     *
     * @param string $langcode
     *
     * @return array
     *
     * @see https://api-developer.easypaymentgateway.com/EPGJS/#form-international
     */
    public static function getI18n(string $langcode): array
    {
        $module = Module::getInstanceByName('addonpayments');
        $default = $module->getLocalPath() . 'data/i18n/en.json';
        $i18n = $module->getLocalPath() . 'data/i18n/' . $langcode . '.json';

        if (!is_readable($i18n)) {
            PrestaShopLogger::addLog("Cannot load translations file '{i18n}'", 3);
            $i18n = $default;
        }

        $contents = Tools::file_get_contents('file://' . $i18n);

        return json_decode($contents, true);
    }

    /**
     * Generates an instance ID to be used as identifier for API requests.
     *
     * The customer ID is generated using current customer session. If the customer
     * is anonymous the ID is obtained through a 16 bytes random string and then
     * hashed to ensure a fresh ID for every anonymous customer.
     *
     * @param Context|null $context
     *
     * @return string
     *
     * @throws Exception
     */
    public static function getInstanceIdFromContext(Context $context = null): string
    {
        if (!empty($_REQUEST['addonpayments_instance'])) {
            return $_REQUEST['addonpayments_instance'];
        }

        if (null === $context) {
            $context = Context::getContext();
        }

        $_REQUEST['addonpayments_instance'] = sha1(
            $context->cart->id . $context->customer->id . $context->customer->secure_key
        );

        return $_REQUEST['addonpayments_instance'];
    }

    /**
     * Generates an ID to be used for the charge/pay transaction.
     *
     * @param Context|null $context
     *
     * @return string
     */
    public static function getTransactionIdFromContext(Context $context = null): string
    {
        if (!empty($_REQUEST['addonpayments_transactions'])) {
            return $_REQUEST['addonpayments_transactions'];
        }

        if (null === $context) {
            $context = Context::getContext();
        }

        // Count any previous transactions to get an unique transaction id.
        $order_id = Order::getOrderByCartId($context->cart->id);
        $count = Transactions::countAttemptsByOrder($order_id ?: $context->cart->id);
        $_REQUEST['addonpayments_transactions'] = (new Hashids(
            self::getEncryptionKey()
        ))->encode(
            $context->cart->id, $context->customer->id, $count
        );

        return $_REQUEST['addonpayments_transactions'];
    }

    /**
     * Returns the configured token validity threshold (in milliseconds).
     */
    public static function getTokenValidityThreshold(): int
    {
        $value = Configuration::get(addonpayments::TOKEN_VALIDITY);

        return !empty($value) ? (int)$value : addonpayments::AUTH_TOKEN_VALIDITY_THRESHOLD;
    }

    /**
     * Get the URL used for redirection responses with relative URI.
     */
    public static function getRedirectionTargetUrl(): string
    {
        return EPGJs::getEndpoints(self::isLive())['redirect'];
    }

    /**
     * Check if module is set for production.
     */
    public static function isLive(): bool
    {
        return (bool) Configuration::get(AddonPaymentsConfig::LIVE_MODE);
    }

    /**
     * Check web API is up.
     * @throws Exception
     */
    public static function hcWebApi(): bool
    {
        $context = Context::getContext();

        return null !== (new Authorize())(
                self::getCustomerIdFromContext($context),
                $context->currency->iso_code,
                $context->country->iso_code
            );
    }

    /**
     * Generates a customer ID to be used as identifier for API requests.
     *
     * The customer ID is generated using current customer session. If the customer
     * is anonymous the ID is obtained through a 16 bytes random string and then
     * hashed to ensure a fresh ID for every anonymous customer.
     *
     * @param Context|null $context
     *
     * @return string
     *
     * @throws Exception
     */
    public static function getCustomerIdFromContext(Context $context = null): string
    {
        if (!empty($_REQUEST['addonpayments_customer'])) {
            return $_REQUEST['addonpayments_customer'];
        }

        if (null === $context) {
            $context = Context::getContext();
        }

        $customer = $context->customer;
        if (null === $customer || empty($customer->secure_key) || false === $customer->isLogged()) {
            $_REQUEST['addonpayments_customer'] = sha1(random_bytes(16));

            return $_REQUEST['addonpayments_customer'];
        }

        $_REQUEST['addonpayments_customer'] = sha1($customer->id . $customer->secure_key);

        return $_REQUEST['addonpayments_customer'];
    }

    /**
     * Check EPGJs library availability.
     */
    public static function hcJsLibrary(): bool
    {
        $url = EPGJs::getEndpoints(self::isLive())['render'];
        $context = stream_context_create(
            [
                'http' => [
                    'method' => 'HEAD',
                ],
            ]
        );
        $headers = get_headers($url, false, $context);

        return false !== stripos($headers[0], '200 OK');
    }
}
