<?php
/**
 * Copyright (c) 2016 Serhii Borodai <clarifying@gmail.com>.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 *
 */

namespace App\Authentication;


use App\Authentication\Tokenizer\TokenizerInterface;
use App\Entity\IdentityTrait\Identity;
use App\Entity\User;
use App\Model\Common;
use App\Entity\IdentityInterface;

use App\Model\ModelIdentity;
use Dflydev\FigCookies\FigResponseCookies;
use Dflydev\FigCookies\SetCookie;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Authentication\AuthenticationServiceInterface;
use Zend\Authentication\Result;

/**
 * Class AbstractAuthService
 * @package App\Authentication
 */
abstract class AbstractAuthService implements AuthenticationServiceInterface
{

    const CREDENTIAL_EMAIL = 'email';
    const CREDENTIAL_PASSWORD = 'password';

    const COOKIE_TOKEN_NAME = 'at';

    /**
     * @var ModelIdentity
     */
    protected $model;

    /**
     * @var IdentityInterface
     */
    protected $entity;

    /**
     * @var IdentityInterface
     */
    protected $token;

    protected $credential;

    protected $tokenizer;

    /**
     * AbstractAuthService constructor.
     * @param Common $model
     */
    public function __construct(Common $model, TokenizerInterface $tokenizer)
    {
        $this->model = $model;
        $this->tokenizer = $tokenizer;
    }

    /**
     * @inheritdoc
     */
    public function hasIdentity()
    {
        return !empty($this->entity);
    }

    /**
     * @inheritdoc
     */
    public function getIdentity()
    {
        return $this->entity;
    }

    /**
     * @inheritdoc
     */
    public function clearIdentity()
    {
        $this->entity = null;
    }

    /**
     * @return mixed
     */
    public function getCredential()
    {
        return $this->credential;
    }

    /**
     * @param mixed $credential
     *
     * @return $this
     */
    public function setCredential($credential)
    {
        $this->credential = $credential;

        return $this;
    }


    /**
     * @param object $token
     */
    public function setToken($token)
    {
        $this->token = $token;
    }

    /**
     * @return IdentityInterface
     */
    public function getToken()
    {
        return $this->token;
    }

    /**
     * @return TokenizerInterface
     */
    public function getTokenizer(): TokenizerInterface
    {
        return $this->tokenizer;
    }

    /**
     * Returns cookie name for token storage
     *
     * @return string
     */
    public function getCookieTokenName()
    {
        return static::COOKIE_TOKEN_NAME;
    }

    /**
     * @inheritdoc
     */
    public function authenticate()
    {
        
        //@todo move activated && blocked status check to acl service
        if (!empty($this->getToken())) {
            $token = $this->getTokenizer()->decode($this->token);
            if (isset($token->data) && $identity = $token->data) {
                $id = $identity->id;
                $entity = $this->model->findById($id);
                if ($entity) {
                        $this->entity = $entity;
                        $result = new Result(Result::SUCCESS, $this->entity);
                } else {
                    $result = new Result(Result::FAILURE_IDENTITY_NOT_FOUND, $identity, ['user not found']);
                }
            } else {
                $result = new Result(Result::FAILURE_CREDENTIAL_INVALID, $this->token, ['incorrect token']);
            }
        } elseif (!empty($this->credential)) {
            if (isset($this->credential[self::CREDENTIAL_EMAIL], $this->credential[self::CREDENTIAL_PASSWORD])) {
                /** @var IdentityInterface $entity */
                $entity = $this->model->findOne([
                    self::CREDENTIAL_EMAIL => $this->credential[self::CREDENTIAL_EMAIL]
                    ]);

                if ($entity &&
                    password_verify($this->credential[self::CREDENTIAL_PASSWORD], $entity->getPassword())) {
                    
                    $this->entity = $entity;
                    $result = new Result(Result::SUCCESS, $entity);
                    
                    if ($entity instanceof User && !in_array($entity->getType(), ['shop', 'advertiser'])) {
                        $result = new Result(Result::FAILURE_CREDENTIAL_INVALID, $this->credential, ['invalid credential']);
                    }
                    
                } else {
                    $result = new Result(Result::FAILURE_CREDENTIAL_INVALID, $this->credential, ['invalid credential']);
                }
            } else {
                $result = new Result(Result::FAILURE_CREDENTIAL_INVALID, $this->credential, ['invalid credential']);
            }
        } else {
            $result = new Result(Result::FAILURE_UNCATEGORIZED, null, ['no authentication data']);
        }
        
        return $result;
    }

    /**
     * @param ServerRequestInterface $request
     * @param ResponseInterface $response
     * @return ResponseInterface
     */
    public function clearCookie(ServerRequestInterface $request, ResponseInterface $response)
    {
        $response = FigResponseCookies::modify($response, $this->getCookieTokenName(), function(SetCookie $setCookie) use ($request) {
            return $setCookie
                ->withPath('/')
                ->withDomain($request->getUri()->getHost())
                ->withMaxAge(0)
                ->withExpires(new \DateTime('-1 year'))
                ->withValue(null);
        });
        return $response;
    }
}