<?php

declare(strict_types=1);

/**
 * Created by Aurélien RICHAUD (14/02/2018 10:03)
 */
class OAuthAccessToken extends ObjectModel
{
    public const TABLE = 'oauth_access_token';

    /**
     * @var int
     */
    public $id_oauth_access_token;

    /**
     * @var string
     */
    public $client_id;

    /**
     * @var int|null
     */
    public $id_employee;

    /**
     * @var int|null
     */
    public $id_customer;

    /**
     * @var string
     */
    public $redirect_uri;

    /**
     * @var string
     */
    public $token;

    /**
     * @var int
     */
    public $created;

    /**
     * @var int
     */
    public $expires;

    /**
     * @var int|null
     */
    public $last_used;

    /**
     * @var array<string, mixed>
     *
     * @see ObjectModel::$definition
     */
    public static $definition
        = [
            'table' => self::TABLE,
            'primary' => 'id_oauth_access_token',
            'multilang' => false,
            'multilang_shop' => false,
            'fields' => [
                'client_id' => ['type' => self::TYPE_INT, 'required' => true],
                'id_employee' => ['type' => self::TYPE_INT, 'required' => false],
                'id_customer' => ['type' => self::TYPE_INT, 'required' => false],
                'token' => ['type' => self::TYPE_STRING, 'required' => true],
                'created' => ['type' => self::TYPE_INT, 'required' => true],
                'expires' => ['type' => self::TYPE_INT, 'required' => true],
                'last_used' => ['type' => self::TYPE_INT, 'required' => false],
            ],
        ];

    public static function generateToken(): string
    {
        $bytes = false;
        if (function_exists('openssl_random_pseudo_bytes') && 0 !== stripos(PHP_OS, 'win')) {
            $bytes = openssl_random_pseudo_bytes(32, $strong);

            if (true !== $strong) {
                $bytes = false;
            }
        }

        // let's just hope we got a good seed
        if (false === $bytes) {
            $bytes = hash('sha256', uniqid(strval(mt_rand()), true), true);
        }

        return base_convert(bin2hex($bytes), 16, 36);
    }

    /**
     * @param bool $null_values
     * @param bool $auto_date
     *
     * @throws PrestaShopException
     *
     * {@inheritDoc}
     */
    public function save($null_values = false, $auto_date = true): bool
    {
        if (!$this->token) {
            $this->token = self::generateToken();
        }

        return parent::save($null_values, $auto_date);
    }

    /**
     * @throws PrestaShopDatabaseException
     * @throws PrestaShopException
     */
    public static function getByToken(string $token): ?OAuthAccessToken
    {
        // Token interne
        $db = Db::getInstance();

        $result = $db->executeS('SELECT * FROM `' . _DB_PREFIX_ . 'oauth_access_token` WHERE `token` = "' . $db->escape($token) . '"');

        if (!is_array($result)) {
            $result = [];
        }

        $tokens = ObjectModel::hydrateCollection(self::class, $result);

        if (count($tokens)) {
            return $tokens[0];
        }

        // Token délégué à UniVetzen ID

        return null;
    }

    /**
     * @throws PrestaShopDatabaseException
     * @throws PrestaShopException
     */
    public static function getOneActiveTokenForEmployee(int $id_employee): ?OAuthAccessToken
    {
        $tokens = self::getManyActiveTokenForEmployee($id_employee);

        if (count($tokens)) {
            return $tokens[0];
        }

        return null;
    }

    /**
     * @param int $employeeId
     *
     * @return array<OAuthAccessToken>
     *
     * @throws PrestaShopDatabaseException
     * @throws PrestaShopException
     */
    public static function getManyActiveTokenForEmployee(int $employeeId): array
    {
        $db = Db::getInstance();
        $result = $db->executeS('SELECT * FROM `' . _DB_PREFIX_ . 'oauth_access_token` WHERE `id_employee` = ' . (int) $employeeId . ' AND `expires` > ' . time());

        if (!is_array($result)) {
            $result = [];
        }

        return ObjectModel::hydrateCollection(self::class, $result);
    }

    /**
     * @param int $employeeId
     *
     * @return OAuthAccessToken[]|null
     *
     * @throws PrestaShopDatabaseException
     * @throws PrestaShopException
     */
    public static function getValidTokenNotificationForEmployee(int $employeeId): ?array
    {
        $tokens = self::getManyActiveTokenForEmployee($employeeId);

        // Filtre pour ne garder que les tokens actifs
        array_filter($tokens, function ($token) {
            $lastUsedTimestamp = $token->last_used ? $token->last_used : $token->created;
            $lastUsed = new DateTime();
            $lastUsed->setTimestamp((int) $lastUsedTimestamp);
            $dateLimit = new Datetime('- 1 months');

            // Dernière utilisation du token inférieur à x mois, on considère donc que le token est toujours actif
            return $lastUsed > $dateLimit;
        });

        if (count($tokens) > 0) {
            return $tokens;
        }

        return null;
    }

    /**
     * @param OAuthAccessToken $accessToken
     *
     * @return CliniquePush|null
     *
     * @throws PrestaShopDatabaseException
     * @throws PrestaShopException
     */
    public static function getPushCliniqueByToken(OAuthAccessToken $accessToken): ?CliniquePush
    {
        $db = Db::getInstance();
        $push = $db->getRow('SELECT * FROM `' . _DB_PREFIX_ . CliniquePush::TABLE . '` WHERE `id_oauth_access_token` = ' . (int) $accessToken->id);

        if (is_array($push)) {
            $cliniquePush = new CliniquePush();
            $cliniquePush->hydrate($push);

            return $cliniquePush;
        }

        return null;
    }

    /**
     * @throws PrestaShopDatabaseException
     * @throws PrestaShopException
     */
    public static function getOneActiveTokenForCustomer(int $id_customer): ?OAuthAccessToken
    {
        $db = Db::getInstance();

        $result = $db->executeS('SELECT * FROM `' . _DB_PREFIX_ . 'oauth_access_token` WHERE `id_customer` = ' . (int) $id_customer . ' AND `expires` > ' . time());

        if (!is_array($result)) {
            $result = [];
        }

        $tokens = ObjectModel::hydrateCollection(
            self::class, $result
        );

        if (count($tokens)) {
            return $tokens[0];
        }

        return null;
    }

    /**
     * Extraction d'un AccessToken depuis la requête
     */
    public static function getTokenFromRequest(): ?string
    {
        if (!function_exists('getallheaders')) {
            $headers = [];
        } else {
            $headers = getallheaders();
        }
        $headerKey = isset($headers['Authorization']) ? 'Authorization' : 'authorization';

        // Remplissage du token
        if (!isset($headers[$headerKey]) || 0 !== strpos($headers[$headerKey], 'Bearer ')) {
            if (isset($_GET['access_token']) && !empty($_GET['access_token'])) {
                $headers[$headerKey] = 'Bearer ' . $_GET['access_token'];
            } elseif (isset($_POST['access_token']) && !empty($_POST['access_token'])) {
                $headers[$headerKey] = 'Bearer ' . $_POST['access_token'];
            }
        }

        if (!isset($headers[$headerKey])) {
            return null;
        }

        return trim(str_replace('Bearer ', '', $headers[$headerKey]));
    }

    public static function install(): bool
    {
        $query = 'CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . static::TABLE . '` (
			  `id_oauth_access_token` INT(11) NOT NULL AUTO_INCREMENT,
			  `client_id` INT(11) NOT NULL,
			  `id_employee` INT(11) NOT NULL,
			  `token` VARCHAR(50)  NOT NULL,
			  `created` INT(11) NOT NULL,
			  `expires` INT(11) NOT NULL,
			  
			   PRIMARY KEY (`id_oauth_access_token`),
			   KEY (token)
			);';

        return Db::getInstance()->execute($query);
    }
}
