<?php 
/**
 * @package    Proxim 
 * @author     Davison Pro <davisonpro.coder@gmail.com>
 * @copyright  2019 Proxim
 * @version    1.0.0
 * @since      File available since Release 1.0.0
 */

namespace Proxim\User;

use Db;
use Proxim\Application;
use Proxim\Database\DbQuery;
use Proxim\Cache\Cache;
use Proxim\ObjectModel;
use Proxim\Tools;
use Proxim\Validate;
use Proxim\Util\DateUtils;
use Proxim\Crypto\Hashing;
use Proxim\Hook;

/**
 * Customer
 */
class Customer extends ObjectModel {
    /** @var $id Customer ID */
    public $id;

    public $google_analytics_id;

    public $email;

    public $name;

    public $phone;

    public $country_id;

    public $site_id = PROX_SITE_ID;

    public $password;

    public $new_password;

    public $reset_password_token;

    public $reset_password_validity;
    
    public $last_passwd_gen;

    public $secure_key;

    public $policy_accepted = 0;

    public $policy_accepted_at;

    public $total_spent = 0;

    public $referrer_id;

    public $affiliate_used = 0;

    public $affiliate_balance = 0;

    public $last_academic_level;

    public $last_payment_method;

    public $is_subscribed = 0;

    public $is_test = 0;

    public $is_banned = 0;

    public $push_notifications = 0;

    public $last_login;

    public $last_activity;
    
    public $reg_date;

    /** @var bool is the user logged in */
    public $logged = false;
    
    /**
     * @see ObjectModel::$definition
     */
    public static $definition = array(
        'table' => 'customer',
        'primary' => 'customer_id',
        'fields' => array(
            'google_analytics_id' => array('type' => self::TYPE_STRING, 'size' => 255),
            'email' => array('type' => self::TYPE_STRING, 'validate' => 'isEmail', 'required' => true, 'size' => 255),
            'name' => array('type' => self::TYPE_STRING, 'validate' => 'isName', 'required' => false, 'size' => 255),
            'phone' => array('type' => self::TYPE_STRING, 'validate' => 'isPhoneNumber', 'required' => false, 'size' => 50),
            'country_id' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
            'site_id' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
            'password' => array('type' => self::TYPE_STRING, 'validate' => 'isPasswd', 'required' => true, 'size' => 255),
            'new_password' => array('type' => self::TYPE_STRING, 'validate' => 'isPasswd', 'required' => true, 'size' => 255),
            'reset_password_token' => array('type' => self::TYPE_STRING, 'validate' => 'isSha1', 'size' => 40),
            'reset_password_validity' => array('type' => self::TYPE_DATE, 'validate' => 'isDateOrNull'),
            'last_passwd_gen' => array('type' => self::TYPE_DATE, 'validate' => 'isDateOrNull'),
            'secure_key' => array('type' => self::TYPE_STRING, 'validate' => 'isMd5'),
            'policy_accepted' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
            'policy_accepted_at' => array('type' => self::TYPE_DATE, 'validate' => 'isDateOrNull'),
            'total_spent' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice'),
            'referrer_id' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
            'affiliate_used' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
            'affiliate_balance' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice'),
            'last_academic_level' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
            'last_payment_method' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
            'is_subscribed' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
            'is_test' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
            'is_banned' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
            'push_notifications' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'),
            'last_login' => array('type' => self::TYPE_DATE, 'validate' => 'isDateOrNull'),
            'last_activity' => array('type' => self::TYPE_DATE, 'validate' => 'isDateOrNull'),
            'reg_date' => array('type' => self::TYPE_DATE, 'validate' => 'isDate')
        )
    );

    /**
     * constructor.
     *
     * @param null $id
     */
    public function __construct($id = null)
    {
        parent::__construct($id);
    }

    public function add($autoDate = true, $nullValues = true)
    {
        $this->secure_key = md5(uniqid(rand(), true));
        $this->last_passwd_gen = DateUtils::now();

        if ( !$this->getNextCustomerId() ) {
            $this->id = 452441;
        } else {
            $next_customer_id = Db::getInstance()->getValue('SELECT MAX(`customer_id`)+2 FROM `' . PROX_DB_PREFIX . 'customer`');
            $this->id = $next_customer_id;
        }

        $this->force_id = true;

        $success = parent::add($autoDate, $nullValues);

        return $success;
    }

    /**
     * Return user instance from its e-mail (optionally check password).
     *
     * @param string $email e-mail
     * @param string $plaintextPassword Password is also checked if specified
     *
     * @return bool|User User instance
     */
    public function getByEmail($email, $plaintextPassword = null, $siteId = null )
    {
        if (!Validate::isEmail($email) || ($plaintextPassword && !Validate::isPasswd($plaintextPassword))) {
            return false;
        }

        if(\is_null($siteId)) {
            $siteId = PROX_SITE_ID;
        }
        
        $sql = new DbQuery();
        $sql->select('c.*');
        $sql->from('customer', 'c');
        $sql->where('c.`email` = \'' . pSQL($email) . '\'');

        if ($siteId) {
            $sql->where('c.`site_id` = \'' . (int) $siteId . '\'');
        }
 
        $result = Db::getInstance()->getRow($sql);
        if (!$result) {
            return false;
        }

        $crypto = new Hashing();

        $passwordHash = $result['password'];
        $shouldCheckPassword = !is_null($plaintextPassword);
        if ($shouldCheckPassword && !$crypto->checkHash($plaintextPassword, $passwordHash)) {
            return false;
        }
        
        $this->id = $result['customer_id'];
        foreach ($result as $key => $value) {
            if (property_exists($this, $key)) {
                $this->{$key} = $value;
            }
        }
        
        if ($shouldCheckPassword && !$crypto->isFirstHash($plaintextPassword, $passwordHash)) {
            $this->password = $crypto->hash($plaintextPassword);
            $this->update();
        }

        return $this;

    }
    
    /**
     * Check user information and return user validity.
     *
     * @return bool user validity
     */
    public function isLogged()
    {
        /* Customer is valid only if it can be load and if session is active */
        return $this->logged == 1 && (int) $this->id && Customer::checkPassword($this->id, $this->password);
    }

    /**
     * Soft logout, delete everything that links to the user
     *
     * @since 1.0.0
     */
    public function logout() {
		Hook::exec('actionCustomerLogoutBefore', ['customer' => $this]);

        // remove auth cookie
        if (isset(Application::getInstance()->container->cookie)) {
            Application::getInstance()->container->cookie->logout();
        }

		Hook::exec('actionCustomerLogoutAfter', ['customer' => $this]);

        $this->logged = false;
    }

    /**
     * Check if customer password is the right one.
     *
     * @param int $customer_id Customer ID
     * @param string $passwordHash Hashed password
     *
     * @return bool result
     */
    public static function checkPassword($customer_id, $passwordHash)
    {
        if (!Validate::isUnsignedId($customer_id)) {
            die(Tools::displayError());
        }

        $cacheId = 'Customer::checkPassword' . (int) $customer_id . '-' . $passwordHash;
        if (!Cache::isStored($cacheId)) {
            $sql = new DbQuery();
            $sql->select('c.`customer_id`');
            $sql->from('customer', 'c');
            $sql->where('c.`customer_id` = ' . (int) $customer_id);
            $sql->where('c.`password` = \'' . pSQL($passwordHash) . '\'');

            $result = (bool) Db::getInstance(PROX_USE_SQL_SLAVE)->getValue($sql);

            Cache::store($cacheId, $result);

            return $result;
        }

        return Cache::retrieve($cacheId);
    }

    /**
     * Fill Reset password unique token with random sha1 and its validity date. For forgot password feature.
     */
    public function stampResetPasswordToken()
    {
        $salt = $this->id . '-' . $this->secure_key;
        $this->reset_password_token = sha1(time() . $salt);
        $this->reset_password_validity = date('Y-m-d H:i:s', strtotime('+' . 1440 . ' minutes'));
    }

    /**
     * Test if a reset password token is present and is recent enough to avoid creating a new one (in case of customer triggering the forgot password link too often).
     */
    public function hasRecentResetPasswordToken()
    {
        if (!$this->reset_password_token || $this->reset_password_token == '') {
            return false;
        }

        // TODO maybe use another 'recent' value for this test. For instance, equals password validity value.
        if (!$this->reset_password_validity || strtotime($this->reset_password_validity) < time()) {
            return false;
        }

        return true;
    }
    
    /**
     * Returns the valid reset password token if it validity date is > now().
     */
    public function getValidResetPasswordToken()
    {
        if (!$this->reset_password_token || $this->reset_password_token == '') {
            return false;
        }

        if (!$this->reset_password_validity || strtotime($this->reset_password_validity) < time()) {
            return false;
        }

        return $this->reset_password_token;
    }

    /**
     * Delete reset password token data.
     */
    public function removeResetPasswordToken()
    {
        $this->reset_password_token = null;
        $this->reset_password_validity = null;
    }

    /**
     * This method return the ID of the next customer.
     *
     * @since 1.0.0
     *
     * @return int
     */
    public function getNextCustomerId()
    {
        return Db::getInstance()->getValue('
            SELECT customer_id
            FROM ' . PROX_DB_PREFIX . 'customer
            WHERE customer_id > ' . (int) $this->id . ' 
            ORDER BY customer_id ASC');
    }
}