<?php
use \Firebase\JWT\JWT;

/**
 * Class for identif
 */
class User{
    private static $isLoggedIn;
    private static $authToken;
    private static $userInfo = [];
    private static $userTypesTableKeys = [
        'broker' => 'brokers',
        'customer' => 'customers',
        'commercial_lead' => 'commercial_leads',
        'supplier' => 'suppliers'
    ];
    private static $validationUsername = '';

    function __construct($authToken = null){
        if($authToken){
            self::$authToken = str_replace('Bearer ', '', $authToken);
            self::$isLoggedIn = self::validateAccessToken(self::$authToken);
        }else{
            self::$isLoggedIn = self::validateSession();
        }
    }

    public function getTableByUser($userType){
        return self::$userTypesTableKeys[$userType];
    }

    private static function validateSession(){
        session_start();
        if(isset($_SESSION['data'])){
            self::$userInfo = $_SESSION['data'];

            return true;
        }

        return false;
    }

    private static function validateAccessToken($authToken){
        $secretKey = base64_decode(JWT_API_SECRET_KEY);
        try {
            $decodedJWT = JWT::decode($authToken, $secretKey, array(JWT_ALGORITHM));
            self::$userInfo = (array) $decodedJWT->data;

            return true;

        } catch (Exception $e) {
            self::$userInfo['errorMessage'] = $e->getMessage();

            return false;
        }
    }

    /**
     * genereates a has for a password string. The value is 60 charactes MAX due to options
     * @param  String $password password to be hashed
     * @return Sting           60 characters long hashed string
     */
    public static function hashPassword($password){
        $hashOptions = [
            'cost' => 12
        ];

        return password_hash($password, PASSWORD_BCRYPT, $hashOptions);
    }

    public static function generateApiToken($userInfo){
        $tokenId    = base64_encode(random_bytes(32));
        $issuedAt   = time();
        $notBefore  = $issuedAt;
        $expire     = $notBefore + JWT_MAX_LIFE; // 1 hour
        $serverName = WIAAS_URL;
        $data = [
            'iat'  => $issuedAt,         // Issued at: time when the token was generated
            'jti'  => $tokenId,          // Json Token Id: an unique identifier for the token
            'iss'  => $serverName,       // Issuer
            'nbf'  => $notBefore,        // Not before
            'exp'  => $expire,           // Expire
            'data' => $userInfo,
            'type' => 'authorization_token'   //Token type
        ];

        $secretKey = base64_decode(JWT_API_SECRET_KEY);
        $jwt = JWT::encode(
                  $data,
                  $secretKey,
                   JWT_ALGORITHM
               );

        return $jwt;
    }

    public function getRefreshToken() {
        global $database;
        $sql = "SELECT u.refresh_token
                FROM ".TABLES['users']." u
                WHERE id=".self::$userInfo['wiaas_id_user'];
        $result = $database->fetchResultArray($sql);

        return isset($result[0]) ? $result[0]['refresh_token'] : false;
    }

    private static function saveRefreshToken($userInfo, $refreshJwt){
        global $database;

        $sql = "UPDATE ".TABLES['users']."
                SET refresh_token='$refreshJwt'
                WHERE id=".$userInfo['wiaas_id_user'];
        $query = $database->query($sql);

        return $database->affectedRows() > 0;
    }

    private static function generateRefreshToken($userInfo){
        global $user;

        $tokenId    = base64_encode(random_bytes(32));
        $issuedAt   = time();
        $notBefore  = $issuedAt;
        $expire     = $notBefore + (JWT_MAX_LIFE * 2); // 1 hour
        $serverName = WIAAS_URL;
        $data = [
            'iat'  => $issuedAt,         // Issued at: time when the token was generated
            'jti'  => $tokenId,          // Json Token Id: an unique identifier for the token
            'iss'  => $serverName,       // Issuer
            'nbf'  => $notBefore,        // Not before
            'exp'  => $expire,           // Expire
            'type' => 'refresh_token',   //Token type
            'data' => $userInfo
        ];

        $secretKey = base64_decode(JWT_API_SECRET_KEY);
        $jwt = JWT::encode(
                  $data,
                  $secretKey,
                   JWT_ALGORITHM
               );

        self::saveRefreshToken($userInfo, $jwt);

        return $jwt;
    }

    public function getUserInfo() {
        return self::$userInfo;
    }

    /**
     * reutnrs if user is logged in
     * @return boolean returns if a user is logged in
     */
    public static function isLoggedIn(){
        return self::$isLoggedIn;
    }

    /**
     * login processing
     * @param  String $username username
     * @param  String $password password
     * @return boolean           true if the user is logged in
     */
    public static function login($username, $password, $withToken = false){
        global $database;

        $username = $database->escapeValue($username);
        $password = $database->escapeValue($password);

        if(empty($username) OR empty($password)){
            return ['status' => 'fail', 'errorMessage' => 'INVALID_USERNAME_PASSWORD'];
        }

        $sql = "SELECT u.id, u.username, u.password, ut.type, ut.id as idUserType, isCompanyAdmin
                FROM ".TABLES['users']." u
                INNER JOIN ".TABLES['rel_user_type']." rel_ut
                ON rel_ut.idUser=u.id
                INNER JOIN ".TABLES['user_types']." ut
                ON ut.id=rel_ut.idType
                WHERE u.username='".$username."'
                LIMIT 1";
        $query = $database->query($sql);

        if($database->numRows($query) !== 1){
            self::$isLoggedIn = false;

            return ['status' => 'fail', 'errorMessage' => 'INVALID_USERNAME_PASSWORD'];
        }

        $row = $database->fetchArray($query);
        if(!password_verify($password, $row['password'])){
            return ['status' => 'fail', 'errorMessage' => 'INVALID_USERNAME_PASSWORD'];
        }

        $userTypeInfo = self::getUserTypeInfo($row['id'], $row['type']);

        if(count($userTypeInfo) === 0){
            return ['status' => 'fail', 'errorMessage' => 'invalid user type '.$row['type'].'. Add type in User class or add the user in the correct table!'];
        }

        self::$userInfo['wiaas_id_user'] = $row['id'];
        self::$userInfo['wiaas_username'] = $row['username'];
        self::$userInfo['wiaas_user_type'] = $row['type'];
        self::$userInfo['wiaas_id_user_type'] = $row['idUserType'];
        self::$userInfo['wiaas_user_full_name'] = $userTypeInfo['fullName'];
        self::$userInfo['wiaas_is_company_admin'] = $row['isCompanyAdmin'] === '1' ? true : false;

        self::$isLoggedIn = true;

        if($withToken){
            self::$authToken = self::generateApiToken(self::$userInfo);
            $refreshToken = self::generateRefreshToken(self::$userInfo);

            return ['status' => 'success', 'accessToken' => self::$authToken, 'refreshToken' => $refreshToken];
        }else{
            $_SESSION['data'] = self::$userInfo;

            return ['status' => 'success'];
        }

    }

    public function refreshToken($refreshToken, $lastActivity){
        $inactivity = time() - (intval($lastActivity) / 1000);
        $maxInactivity = 30 * 60; // 30 minutes

        if($inactivity > $maxInactivity){//session expires if invactivity is more than 30 minutes
            return ['status' => 'error', 'errorMessage' => 'EXPIRED_SESSION'];
        }

        if(self::validateAccessToken($refreshToken)){
            self::$authToken = self::generateApiToken(self::$userInfo);
            $refreshToken = self::generateRefreshToken(self::$userInfo);

            return ['status' => 'success', 'accessToken' => self::$authToken, 'refreshToken' => $refreshToken];
        }

        return ['status' => 'error', 'errorMessage' => 'INVALID_REFRESH_TOKEN', 'extra' => $this->getErrorMessage()];
    }

    public static function setLastActivity(){
        $_SESSION['LAST_ACTIVITY'] = time();
    }

    public static function getLastActivity(){
        return $_SESSION['LAST_ACTIVITY'];
    }

    /**
     * get extra info for connected user by user type
     * @param  Int $idUser   id of the user
     * @param  string $userType the type of the user
     * @return [Array]   retruns an empty array if no info or an array with the info
     */
    private static function getUserTypeInfo($idUser, $userType){
        global $database;

        if(!array_key_exists($userType, self::$userTypesTableKeys)){
            return [];
        }

        $sql = "SELECT info.name AS fullName
                FROM ".TABLES[self::$userTypesTableKeys[$userType]]." info
                WHERE idUser=".$idUser."
                LIMIT 1";
        $query = $database->query($sql);

        if($database->numRows($query) !== 1){
            return [];
        }

        $row = $database->fetchArray($query);

        return $row;
    }

    public function logout(){
        session_unset();
        session_destroy();

        header("location:index.php");
    }

    /**
     * get the logged username
     * @return String Username
     */
    public function getUser(){
        return isset(self::$userInfo['wiaas_username']) ? self::$userInfo['wiaas_username'] : false;
    }

    /**
     * get the logged id user
     * @return String id suer
     */
    public function getUserId(){
        return isset(self::$userInfo['wiaas_id_user']) ? self::$userInfo['wiaas_id_user'] : 0;
    }

    /**
     * get the logged user full name
     * @return String full name
     */
    public function getUserFullName(){
        return isset(self::$userInfo['wiaas_user_full_name']) ? self::$userInfo['wiaas_user_full_name'] : false;
    }

    /**
     * get the user type
     * @return String returns the user type
     */
    public function getUserType(){
        return isset(self::$userInfo['wiaas_user_type']) ? self::$userInfo['wiaas_user_type'] : false;
    }

    /**
     * get the user type id
     * @return String returns the user type id
     */
    public function getIdUserType(){
        return isset(self::$userInfo['wiaas_id_user_type']) ? self::$userInfo['wiaas_id_user_type'] : 0;
    }

    public function getErrorMessage(){
        return isset(self::$userInfo['errorMessage']) ? self::$userInfo['errorMessage'] : '';
    }


    /**
     * checks whether the token from the url provided is valid or not
     * @param  String $token the token to check the url for
     * @return String        confirmation message
     */
    public function checkPasswordToken($token) {
        global $database;

        $sql = "SELECT u.username
                    FROM ".TABLES['users']." u
                WHERE u.token='".$database->escapeValue($token)."'
                    AND u.tokenTS <= DATE_ADD(u.tokenTS , INTERVAL 5 DAY)";

        $result = $database->query($sql);
        if($database->numRows($result) !== 1) {
            return 'error';
        }

        self::$validationUsername = $database->fetchArray($result)['username'];

        return 'success';
    }

    /**
     * gets the username of the user that wants to reset the password
     * @return String username
     */
    public function getSetPasswordUsername() {
        return self::$validationUsername;
    }

    /**
     * Resets the password for the user with the right token number
     * @param Array $passwords the new and confirmed passwords
     * @return Array with confirmation message
     */
    public function resetPassword($passwords) {
        global $database;
        $data = [];

        if(self::$validationUsername) {
            $data = UtilsModel::changePassword($passwords, self::$validationUsername);
            $data['username'] = self::$validationUsername;
            self::$validationUsername = '';
        } else {
            $data['messages'][] = [
                'code' => 'error',
                'message' => 'USER_NOT_SET'
            ];
        }

        return $data;
    }

    /**
     * generatates a new password for the user given
     * @param  String $mail email of the user
     * @return String       confirtmation message
     */
    public static function forgotPassword($mail) {
        global $database;
        $mail = $database->escapeValue($mail);
        $now = new DateTime();
        $now = $now->format('Y-m-d H:i:s');
        $data = [];

        $sql = "SELECT
                    u.username,
                    u.mail,
                    IF(
                        ADDTIME(u.tokenTS, '00:05') > '".$now."',
                        0,
                        1
                      ) AS allowPasswordGeneration
                    FROM ".TABLES['users']." u
                WHERE mail = '".$mail."'";

        $userInfo = $database->fetchResultArray($sql);

        if(count($userInfo) == 0) {
            $data[] = 'NO_USER';
        }

        foreach ($userInfo as $info) {
            if($info['allowPasswordGeneration'] == 1) {
                $messageData = UtilsModel::generateTokenForUserPassword(json_encode($info));

                $data[] = 'SIGN_IN';
            } else {
                $data[] = 'CHANGE_LATER';
            }
        }

        return $data;
    }
}
