<?php

namespace Teknisa\Libs\Util;

use Zeedhi\Framework\Security\OAuth\OAuth;
use Zeedhi\Framework\Security\OAuth\Exception;
use Teknisa\Libs\Exception\Token as TokenException;

class OAuthCustom implements OAuth
{
    public function grantAccessToken($clientId, $clientSecret, $options = array(), $token = null)
    {
        $service = $this->findServiceByClientAndSecret($clientId, $clientSecret);
        $token = empty($token) ? $this->genToken() : $token;
        $this->registerSession($token, $service, $options['sessionId']);
        return $token;
    }

    /**
     * This method must generate a new unique token using random numbers and the uniqid of the php
     *
     * @return string A token
     * @throws Exception When token generated is invalid
     */
    private function genToken()
    {
        $token  = (string)base_convert(mt_rand(), 10, 16);
        $token .= (string)base_convert(mt_rand(), 10, 16);
        $token .= (string)base_convert(mt_rand(), 10, 16);
        $token = uniqid($token);
        $lastKeys = $this->genLastKeys($token);
        if (!empty($lastKeys)) {
            $token .= $lastKeys;
            return $token;
        }
        throw Exception::invalidFormatToken();
    }

    /**
     * This method must generate the two last keys of the token. They keys are generate from the random token.
     *
     * @param  string $key Random token
     *
     * @return mixed       Last keys of the token or NULL case the token is invalid
     */
    private function genLastKeys($key)
    {
        $keyLength = strlen($key);
        if (!empty($key) && $keyLength >= 3) {
            $code = round(ord($key[1]) * ord($key[(int)round((($keyLength / 4) * 0.6))]) / ord($key[$keyLength - 3]));
            return ((string)$code % 9) . ((string)$code % 4);
        }
        return null;
    }

    /**
     * This method must validate an access token
     *
     * @param  string $token An access token
     *
     * @return boolean
     */
    private function isValidToken($token)
    {
        $length = strlen($token);
        $key = substr($token, 0, $length - 2);
        $lastKeys = substr($token, $length - 2, $length - 1);
        return $lastKeys == $this->genLastKeys($key);
    }

    /**
     * This method should storage one record of a service on the cache type chosen
     *
     * @param  string $token An access token
     * @param  Service $service An instance of the service
     * @param $sessionId
     *
     * @return void
     */
    private function registerSession($token, $service, $sessionId)
    {
        $session = array(
            "serviceClientId" => $service->getClientId(),
            "serviceClientSecret" => $service->getClientSecret(),
            "serviceName" => $service->getName(),
            "sessionId" => $sessionId
        );
        InstanceProvider::getMongoCache()->save($token, $session, Utilities::getTokenLifeTimeParameter());
    }

    protected function findServiceByClientAndSecret($clientId, $clientSecret) {
        $service = InstanceProvider::getIntegrationProvider()->findByClientAndSecretId($clientId, $clientSecret);
        if (!$service) throw Exception::serviceNotFound($clientId);
        return $service;
    }

    /**
     * Validate an access token and return the session of one service
     *
     * @param  string $token The access token of a service
     * @param  string $clientSecret The secret id of the service
     * @param  array $options
     * @param  bool $keepConnected
     *
     * @throws Exception If the access token is invalid
     *
     * @return IntegrationProvider object of a service.
     */
    public function checkAccess($token, $clientSecret, $options = array(), $keepConnected = false)
    {
        if ($this->isValidToken($token)) {
            $data = InstanceProvider::getMongoCache()->fetch($token, Utilities::getTokenLifeTimeParameter(), $keepConnected);
            if (!empty($data)) {
                $service = InstanceProvider::getIntegrationProvider()->findByClientAndSecretId($data['serviceClientId'], $data['serviceClientSecret']);
                if (!empty($service)) {
                    return array('service' => $service, 'sessionId' => $data['sessionId']);
                }
            }
        }
        TokenException::invalidToken($token);
    }

    /**
     * @param $token
     */
    public function deleteToken($token) {
        InstanceProvider::getMongoCache()->delete($token);
    }
}