<?php

/**
 *
 */
class Core_OAuth2_Storage_Doctrine implements
    OAuth2_Storage_AuthorizationCodeInterface,
    OAuth2_Storage_AccessTokenInterface,
    OAuth2_Storage_ClientCredentialsInterface,
    OAuth2_Storage_UserCredentialsInterface,
    OAuth2_Storage_RefreshTokenInterface,
    OAuth2_Storage_JWTBearerInterface
{
    /**
     *
     * @var Doctrine_Connection
     */
    protected $connection;

    /**
     *
     * @var array
     */
    protected $config;

    public function __construct(Doctrine_Manager $manager, $config = array())
    {
        //$this->connection = $manager->getConnection();
        $this->config = array_merge(array(
            'client_table'          => 'OauthClient',
            'client_grant_table'    => 'OauthClientGrantType',
            'access_token_table'    => 'OauthAccessToken',
            'refresh_token_table'   => 'OauthRefreshToken',
            'code_table'            => 'OauthAuthorizationCode',
            'jwt_table'             => 'OauthJwt',
            'user_table'            => 'User',
        ), $config);
    }

    /**
     *
     * @param string $client_id
     * @param string $client_secret
     *
     * @return boolean
     */
    public function checkClientCredentials($client_id, $client_secret = null)
    {
        $client = $this->getClientDetails($client_id);
        return $client && $client['secret'] == $client_secret;
    }

    /**
     *
     * @param string $client_id
     *
     * @return User
     */
    public function getClientDetails($client_id)
    {
        return Doctrine_Core::getTable($this->config['client_table'])
                ->createQuery('c')
                ->leftJoin('c.grant_types g')
                ->where('c.client_id = ?', $client_id)
                ->fetchOne(array(), Doctrine_Core::HYDRATE_ARRAY);
    }

    /**
     *
     * @param string $client_id
     * @param string $grant_type
     *
     * @return boolean
     */
    public function checkRestrictedGrantType($client_id, $grant_type)
    {
        $details = $this->getClientDetails($client_id);
        if (empty($details->grant_types)) {
            // if grant_types are not defined, then none are restricted
            return true;
        }

        foreach ($details->grant_types as $grant) {
            if ($grant->type == $grant_type) {
                return true;
            }
        }

        return false;
    }

    /**
     *
     * @param string $access_token
     *
     * @return array
     */
    public function getAccessToken($access_token)
    {
        $hash  = 'oauth_access_token_'. md5($access_token);
        $cache = Core_Application::get('CacheManager')->getDefaultCache();

        if (false === ($token = $cache->load($hash))) {
            $token = Doctrine_Core::getTable($this->config['access_token_table'])
                        ->createQuery('tb')
                        ->select('tb.*')
                        ->where('tb.access_token = ?', $access_token)
                        ->useResultCache(true)
                        ->fetchOne();

            if ($token) {
                $token->expires = strtotime($token->expires);
                $token = $token->toArray();
            }
            $cache->save($token, $hash);
        }

        return $token;
    }

    /**
     *
     * @param string $access_token
     * @param string $client_id
     * @param int    $user_id
     * @param int    $expires
     * @param string $scope
     *
     * @return void
     */
    public function setAccessToken($access_token, $client_id, $user_id, $expires, $scope = null)
    {
        // convert expires to datestring
        $expires = date('Y-m-d H:i:s', $expires);

        // if it exists, update it.
        $token = Doctrine_Core::getTable($this->config['access_token_table'])
                    ->createQuery('tb')
                    ->select('tb.*')
                    ->where('tb.access_token = ?', $access_token)
                    ->useResultCache(true)
                    ->fetchOne();

        if (!$token) {
            $token = new $this->config['access_token_table'];
            $token->access_token = $access_token;
        }

        $token->client_id   = $client_id;
        $token->expires     = $expires;
        $token->user_id     = $user_id;
        $token->scope       = $scope;

        $token->save();
    }

    /**
     *
     * @param string $code
     *
     * @return array
     */
    public function getAuthorizationCode($code)
    {
        $code = Doctrine_Core::getTable($this->config['code_table'])
                    ->findOneBy('authorization_code', $code);

        if ($code) {
            $code->expires = strtotime($code->expires);
            $code = $code->toArray();
        }

        return $code;
    }

    /**
     *
     * @param string $code
     * @param string $client_id
     * @param int    $user_id
     * @param string $redirect_uri
     * @param int    $expires
     * @param string $scope
     */
    public function setAuthorizationCode($code, $client_id, $user_id, $redirect_uri, $expires, $scope = null)
    {
        // convert expires to datestring
        $expires = date('Y-m-d H:i:s', $expires);

        $item    = Doctrine_Core::getTable($this->config['code_table'])
                    ->findOneBy('authorization_code', $code);

        if (!$item) {
            $item                       = new $this->config['code_table'];
            $item->authorization_code   = $code;
        }

        $item->client_id            = $client_id;
        $item->user_id              = $user_id;
        $item->redirect_uri         = $redirect_uri;
        $item->expires              = $expires;
        $item->scope                = $scope;

        $item->save();
    }

    /**
     *
     * @param string $code
     *
     * @return boolean
     */
    public function expireAuthorizationCode($code)
    {
        $code = Doctrine_Core::getTable($this->config['code_table'])
                    ->findOneBy('authorization_code', $code);
        return $code->delete();
    }

    /**
     *
     * @param string $username
     * @param string $password
     *
     * @return boolean
     */
    public function checkUserCredentials($username, $password)
    {
        return Doctrine_Core::getTable($this->config['user_table'])->checkCredentials($username, $password);
    }

    /**
     *
     * @param string $username
     *
     * @return type
     */
    public function getUserDetails($username)
    {
        return $this->getUser($username);
    }

    /**
     *
     * @param string $refresh_token
     *
     * @return array
     */
    public function getRefreshToken($refresh_token)
    {
        $token = Doctrine_Core::getTable($this->config['refresh_token_table'])
                            ->findOneBy('refresh_token', $refresh_token);

        if ($token) {
            // convert expires to epoch time
            $token->expires = strtotime($token->expires);
            $token = $token->toArray();
        }

        return $token;
    }

    /**
     *
     * @param string $refresh_token
     * @param string $client_id
     * @param int    $user_id
     * @param int    $expires
     * @param string $scope
     */
    public function setRefreshToken($refresh_token, $client_id, $user_id, $expires, $scope = null)
    {
        // convert expires to datestring
        $expires = date('Y-m-d H:i:s', $expires);

        $token = new $this->config['refresh_token_table'];
        $token->refresh_token = $refresh_token;
        $token->client_id     = $client_id;
        $token->user_id       = $user_id;
        $token->expires       = $expires;
        $token->scope         = $scope;

        $token->save();
    }

    /**
     *
     * @param string $refresh_token
     *
     * @return boolean
     */
    public function unsetRefreshToken($refresh_token)
    {
        $token = Doctrine_Core::getTable($this->config['refresh_token_table'])
                            ->findOneBy('refresh_token', $refresh_token);

        return $token->delete();
    }

    /**
     *
     * @param User $user
     * @param string $password
     *
     * @return boolean
     */
    protected function checkPassword($user, $password)
    {
        return $user->hashPassword($password) == $user->password;
    }

    /**
     *
     * @param string $username
     *
     * @return User
     */
    public function getUser($username)
    {
        $user = Doctrine_Core::getTable($this->config['user_table'])
                ->findOneBy('email', $username);

        return $user;
    }

    /**
     *
     * @param string $username
     * @param string $password
     * @param string $firstName
     * @param string $lastName
     */
    public function setUser($username, $password, $firstName = null, $lastName = null)
    {
        $user = $this->getUser($username);

        if (!$user) {
            $user = new $this->config['user_table'];
            $user->username = $username;
        }

        $user->password = $user->hashPassword($password);
        $user->fname    = $firstName;
        $user->lname    = $lastName;

        $user->save();
    }

    /**
     *
     * @param string $client_id
     * @param string $subject
     *
     * @return string
     */
    public function getClientKey($client_id, $subject)
    {
        $key = Doctrine_Core::getTable($this->config['jwt_table'])
                ->findOneByClientIdAndSubject($client_id, $subject);

        if (!$key) {
            return null;
        }

        return $key->public_key;
    }
}
