<?php

namespace TdyPlayers\Authentication\Adapter\DbTable;

use Zend\Authentication\Adapter\DbTable\AbstractAdapter;
use Zend\Authentication\Result as AuthenticationResult;
use Zend\Crypt\Password;
use Zend\Db\Adapter\Adapter as DbAdapter;
use Zend\Db\Sql;
use Zend\Db\Sql\Predicate\Operator as SqlOp;
use Zend\Db\Sql\Predicate\Expression as SqlEx;

class BcryptTreatment extends AbstractAdapter
{
    /**
     * @var null|string
     */
    protected $whereClause = null;

    /**
     * __construct() - Sets configuration options
     *
     * @param DbAdapter $zendDb
     * @param string    $tableName        Optional
     * @param string    $identityColumn   Optional
     * @param string    $credentialColumn Optional
     * @param string    $whereClause      Optional
     */
    public function __construct(DbAdapter $zendDb, $tableName = null, $identityColumn = null, $credentialColumn = null, $whereClause = null)
    {
        parent::__construct($zendDb, $tableName, $identityColumn, $credentialColumn);
        $this->whereClause = $whereClause;
    }

    /**
     * _authenticateCreateSelect() - This method creates a Zend\Db\Sql\Select object that
     * is completely configured to be queried against the database.
     *
     * @return Sql\Select
     */
    protected function authenticateCreateSelect()
    {
        $dbSelect = clone $this->getDbSelect();
        $dbSelect->from($this->tableName)->columns(['*'])->where(new SqlOp($this->identityColumn, '=', $this->identity));

        if (!is_null($this->whereClause)) {
            $dbSelect->where(new SqlEx($this->whereClause));
        }

        return $dbSelect;
    }

    /**
     * This method is called to attempt an authentication. Previous to this
     * call, this adapter would have already been configured with all
     * necessary information to successfully connect to a database table and
     * attempt to find a record matching the provided identity.
     *
     * @return bool|AuthenticationResult
     */
    public function authenticate()
    {
        $this->authenticateSetup();
        $dbSelect         = $this->authenticateCreateSelect();
        $resultIdentities = $this->authenticateQuerySelect($dbSelect);

        if (($authResult = $this->authenticateValidateResultSet($resultIdentities)) instanceof AuthenticationResult) {
            return $authResult;
        }

        // At this point, ambiguity is already done. Loop, check and break on success.
        $bcrypt = new Password\Bcrypt();
        foreach ($resultIdentities as $identity) {
            try {
                $valid                                  = $bcrypt->verify($this->credential, $identity[$this->credentialColumn]);
                $identity['zend_auth_credential_match'] = $valid ? '1' : '0';
            } catch (\Exception $e) {
                $identity['zend_auth_credential_match'] = '0';
            }

            $authResult = $this->authenticateValidateResult($identity);
            if ($authResult->isValid()) {
                break;
            }
        }

        return $authResult;
    }

    /**
     * _authenticateValidateResult() - This method attempts to validate that
     * the record in the resultset is indeed a record that matched the
     * identity provided to this adapter.
     *
     * @param  array $resultIdentity
     *
     * @return AuthenticationResult
     */
    protected function authenticateValidateResult($resultIdentity)
    {
        if ($resultIdentity['zend_auth_credential_match'] != '1') {
            $this->authenticateResultInfo['code']       = AuthenticationResult::FAILURE_CREDENTIAL_INVALID;
            $this->authenticateResultInfo['messages'][] = 'Supplied credential is invalid.';

            return $this->authenticateCreateAuthResult();
        }

        unset($resultIdentity['zend_auth_credential_match']);
        $this->resultRow = $resultIdentity;

        $this->authenticateResultInfo['code']       = AuthenticationResult::SUCCESS;
        $this->authenticateResultInfo['messages'][] = 'Authentication successful.';

        return $this->authenticateCreateAuthResult();
    }

}