<?php

/* --------------------------------------------------------------------

  This file is part of Chevereto Free.
  https://chevereto.com/free

  (c) Rodolfo Berrios <rodolfo@chevereto.com>

  For the full copyright and license information, please view the LICENSE
  file that was distributed with this source code.

  --------------------------------------------------------------------- */

namespace CHV;

use G;
use Exception;

class User
{
    public static function getSingle($var, $by = 'id', $pretty = true)
    {
        try {
            $user_db = DB::get('users', [$by => $var], 'AND', null, 1);
            if (!$user_db) {
                return null;
            }
            $logins = Login::get(['user_id' => $user_db['user_id']]);
            $logins_aux = [];
            foreach ($logins as $k => $v) {
                $logins_aux[$v['type']][] = $v;
            }
            $user_db['user_login'] = $logins_aux;
            $user_db['user_login_rows'] = count($logins);
            // Count labels
            foreach (['user_image_count', 'user_album_count'] as $v) {
                if (is_null($user_db[$v]) or $user_db[$v] < 0) {
                    $user_db[$v] = 0;
                }
            }
            // Content manager?
            $user_db['user_is_content_manager'] = $user_db['user_is_admin'] || $user_db['user_is_manager'];
            if (!array_key_exists('user_following', $user_db)) {
                $user_db['user_following'] = 0;
            }
            if (!array_key_exists('user_followers', $user_db)) {
                $user_db['user_followers'] = 0;
            }
            // Remove any unwanted tag from user_name
            $user_db['user_name'] = self::sanitizeUserName($user_db['user_name']);
            if ($pretty) {
                $user_db = self::formatArray($user_db);
            }

            return $user_db;
        } catch (Exception $e) {
            throw new UserException($e->getMessage(), 400);
        }
    }

    public static function getPrivate()
    {
        return [
            'name'            => _s('Private profile'),
            'username'        => 'private',
            'name_short'    => _s('Private'),
            'url'            => G\get_base_url(),
            'album_count'     => 0,
            'image_count'     => 0,
            'image_count_label'    => _n('image', 'images', 0),
            'album_count_display' => 0,
            'image_count_display' => 0,
            'is_private'    => true
        ];
    }

    public static function getAlbums($var)
    {
        try {
            $id = is_array($var) ? $var['id'] : $var;

            $user_albums = [];

            // Build the user stream
            $user_stream = self::getStreamAlbum($var);

            if (is_array($user_stream)) {
                $user_albums['stream'] = $user_stream;
            }

            $db = DB::getInstance();
            $db->query('SELECT * FROM ' . DB::getTable('albums') . ' WHERE album_user_id=:image_user_id ORDER BY album_name ASC');
            $db->bind(':image_user_id', $id);
            $user_albums_db = $db->fetchAll();

            if ($user_albums_db) {
                $user_albums += $user_albums_db;
            }

            foreach ($user_albums as $k => &$v) {
                if ($v['album_image_count'] < 0) {
                    $v['album_image_count'] = 0;
                }
                $user_albums[$k] = DB::formatRow($v, 'album');
                Album::fill($user_albums[$k]);
            }

            return $user_albums;
        } catch (Exception $e) {
            throw new UserException($e->getMessage(), 400);
        }
    }

    public static function getStreamAlbum($user)
    {
        try {
            // Need to ask for user?
            if (!is_array($user)) {
                $user = self::getSingle($user, 'id', true);
            }
            if ($user) {
                return array(
                    'album_id'             => null,
                    'album_id_encoded'    => null,
                    'album_name'         => _s("%s's images", $user['name_short']),
                    'album_user_id'     => $user['id'],
                    'album_privacy'        => 'public',
                    'album_url'            => $user['url']
                );
            }
        } catch (Exception $e) {
            throw new UserException($e->getMessage(), 400);
        }
    }

    public static function getUrl($handle)
    {
        $username = is_array($handle) ? $handle[$handle['user_username'] ? 'user_username' : 'username'] : $handle;
        $id = is_array($handle) ? $handle[$handle['user_id'] ? 'user_id' : 'id'] : null;
        $path = getSetting('user_routing') ? null : 'user/';
        $url = $path . $username;
        // Single user mode on
        if (is_array($handle) and getSetting('website_mode') == 'personal' and $id == getSetting('website_mode_personal_uid')) {
            $url = getSetting('website_mode_personal_routing') !== '/' ? getSetting('website_mode_personal_routing') : null;
        }
        return G\get_base_url($url);
    }

    public static function getUrlAlbums($user_url)
    {
        return rtrim($user_url, '/') . '/albums';
    }

    /* Insert user
     * @returns uid
     */
    public static function insert($values)
    {
        try {
            if (!is_array($values)) {
                throw new DBException('Expecting array values, ' . gettype($values) . ' given in ' . __METHOD__, 100);
            }
            if (!$values['date']) {
                $values['date'] = G\datetime();
            }
            if (!$values['date_gmt']) {
                $values['date_gmt'] = G\datetimegmt();
            }
            if (!$values['language']) {
                $values['language'] = getSetting('default_language');
            }
            if (!$values['timezone']) {
                $values['timezone'] = getSetting('default_timezone');
            }
            if (isset($values['name'])) {
                $values['name'] = self::sanitizeUserName($values['name']);
            }
            if (empty($values['registration_ip'])) {
                $values['registration_ip'] = G\get_client_ip();
            }
            if (!array_key_exists('is_dark_mode', $values)) {
                $values['is_dark_mode'] = Settings::get('theme_tone') == 'dark';
            }
            // Detect flood (son 48 horas que hay que aprovechar)
            if (!Login::getUser()['is_admin']) {
                $db = DB::getInstance();
                $db->query('SELECT COUNT(*) c FROM ' . DB::getTable('users') . ' WHERE user_registration_ip=:ip AND user_status != "valid" AND user_date_gmt >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 2 DAY)');
                $db->bind(':ip', $values['registration_ip']);
                if ($db->fetchSingle()['c'] > 5) {
                    throw new Exception('Flood detected', 666);
                }
            }

            $user_id = DB::insert('users', $values);

            // Email notify
            if (!Login::getUser()['is_admin'] && Settings::get('notify_user_signups')) {
                $message = implode('<br>', [
                    'A new user has just signed up %user (%edit)',
                    '',
                    'Username: %username',
                    'Email: %email',
                    'Status: %status',
                    'IP: %registration_ip',
                    'Date (GMT): %date_gmt',
                    '',
                    'You can disable these notifications on %configure'
                ]);
                foreach (['username', 'email', 'status', 'registration_ip', 'date_gmt'] as $k) {
                    $table['%' . $k] = $values[$k];
                }
                $table['%edit'] = '<a href="' . G\get_base_url('dashboard/user/' . $user_id) . '">edit</a>';
                $table['%user'] = '<a href="' . self::getUrl($values['username']) . '">' . $values['username'] . '</a>';
                $table['%configure'] = '<a href="' . G\get_base_url('dashboard/settings/users') . '">dashboard/settings/users</a>';
                system_notification_email([
                    'subject' => sprintf('New user signup %s', $values['username']),
                    'message' => strtr($message, $table),
                ]);
            }
            // Track stats
            Stat::track([
                'action'    => 'insert',
                'table'        => 'users',
                'value'        => '+1',
                'date_gmt'    => $values['date_gmt']
            ]);
            return $user_id;
        } catch (Exception $e) {
            throw new UserException($e->getMessage(), 400);
        }
    }

    public static function update($id, $values)
    {
        try {
            if (isset($values['name'])) {
                $values['name'] = self::sanitizeUserName($values['name']);
            }
            return DB::update('users', $values, ['id' => $id]);
        } catch (Exception $e) {
            throw new UserException($e->getMessage(), 400);
        }
    }

    public static function uploadPicture($user, $type, $source)
    {
        $type = strtolower($type);

        if (!in_array($type, ['background', 'avatar'])) {
            throw new UserException('unexpected upload value', 403);
        }

        if (!is_array($user)) {
            try {
                $user = self::getSingle($user, 'id');
            } catch (Exception $e) {
                throw new UserException($e->getMessage(), 400);
            }
        }

        if (!$user) {
            throw new UserException("target user doesn't exists", 403);
        }

        // Upload file
        try {
            $user_images_path = CHV_PATH_CONTENT_IMAGES_USERS . $user['id_encoded'];
            $image_upload = Image::upload($source, $user_images_path, ($type == 'avatar' ? 'av' : 'bkg') . '_' . strtotime(G\datetimegmt()), ['max_size' => G\get_bytes(Settings::get('user_image_' . $type . '_max_filesize_mb') . ' MB')]);

            if ($type == 'avatar') {
                $max_res = ['width' => 160, 'height' => 160];
                $must_resize = ($image_upload['uploaded']['fileinfo']['width'] !== $max_res['width'] and $image_upload['uploaded']['fileinfo']['height'] !== $max_res['height']);
            } else {
                $max_res = ['width' => 1920];
                $must_resize = $image_upload['uploaded']['fileinfo']['width'] > $max_res['width'];

                // Medium background
                Image::resize($image_upload['uploaded']['file'], null, $image_upload['uploaded']['name'] . '.md', ['width' => 500]);
            }

            if ($must_resize) {
                $resized = Image::resize($image_upload['uploaded']['file'], null, null, $max_res);
            }

            $file_uploaded = $resized ? $resized : $image_upload['uploaded'];
        } catch (Exception $e) {
            throw new UserException($e->getMessage(), 400);
        }

        if ($file_uploaded) {
            try {
                // Ok, now convert this image to compressed JPG
                $convert = new ImageConvert($file_uploaded['file'], 'jpg', $file_uploaded['file'], 90);
                $file_uploaded['file'] = $convert->out;

                // Edit user database

                $user_edit = self::update($user['id'], [$type . '_filename' => $file_uploaded['filename']]);
                if ($user_edit) {
                    // delete any old image (silent)
                    if ($user[$type]['url']) {
                        $image_path = G\url_to_absolute($user[$type]['url']);
                        if ($type == 'background') {
                            $pathinfo = pathinfo($image_path);
                            @unlink(str_replace($pathinfo['basename'], $pathinfo['filename'] . '.md.' . $pathinfo['extension'], $image_path));
                        }
                        @unlink($image_path);
                    }
                    // return the thing
                    return $file_uploaded['fileinfo'];
                }
            } catch (Exception $e) {
                error_log($e);
            }
        }
    }

    public static function deletePicture($user, $deleting)
    {
        $deleting = strtolower($deleting);

        if (!in_array($deleting, ['background', 'avatar'])) {
            throw new UserException('Unexpected delete value', 100);
        }

        if (!is_array($user)) {
            $user = self::getSingle($user, 'id', true);
        }

        if (!$user) {
            throw new UserException("Target user doesn't exists", 101);
        }

        if (!$user[$deleting]) {
            throw new UserException('user ' . $deleting . " doesn't exists", 102);
        }
        $image_path = G\url_to_absolute($user[$deleting]['url']);

        if ($deleting == 'background') {
            $pathinfo = pathinfo($image_path);
            @unlink(str_replace($pathinfo['basename'], $pathinfo['filename'] . '.md.' . $pathinfo['extension'], $image_path));
        }

        @unlink($image_path);

        if (!file_exists($image_path)) {
            try {
                $edited_user = self::update($user['id'], [$deleting . '_filename' => null]);
            } catch (Exception $e) {
            } // Silence
        }

        if (!$edited_user) {
            throw new UserException("Can't delete " . $deleting . " file", 200);
        }

        return true;
    }

    public static function delete($user)
    {
        try {
            if (!is_array($user)) {
                $user = self::getSingle($user, 'id', true);
            }
            // Delete content user image folder
            $user_images_path = CHV_PATH_CONTENT_IMAGES_USERS . $user['id_encoded'];
            if (!@unlink($user_images_path)) {
                $files = glob($user_images_path . '/{,.}*', GLOB_BRACE);
                foreach ($files as $file) {
                    if (is_file($file)) {
                        @unlink($file);
                    }
                }
            }

            // Delete images from disk
            $db = DB::getInstance();
            $db->query('SELECT image_id FROM ' . DB::getTable('images') . ' WHERE image_user_id=:image_user_id');
            $db->bind(':image_user_id', $user['id']);
            $user_images = $db->fetchAll();

            foreach ($user_images as $user_image) {
                Image::delete($user_image['image_id']);
            }

            Stat::track([
                'action'    => 'delete',
                'table'        => 'users',
                'value'        => '-1',
                'user_id'    => $user['id'],
                'date_gmt'    => $user['date_gmt']
            ]);

            // Update affected user_likes count
            $sql = strtr('UPDATE `%table_users` SET user_likes = user_likes - COALESCE((SELECT COUNT(*) FROM `%table_likes` WHERE like_user_id = %user_id AND user_id = like_content_user_id AND like_user_id <> like_content_user_id GROUP BY like_content_user_id),"0");', [
                '%table_users'    => DB::getTable('users'),
                '%table_likes'    => DB::getTable('likes'),
                '%user_id'        => $user['id'],
            ]);
            DB::queryExec($sql);

            // Update affected user_liked count (users who liked content owner by this user)
            // --> Should happen in Image::delete()

            // Update affected user_followers count
            $sql = strtr('UPDATE `%table_users` SET user_followers = user_followers - COALESCE((SELECT 1 FROM `%table_follows` WHERE follow_user_id = %user_id AND user_id = follow_followed_user_id AND follow_user_id <> follow_followed_user_id GROUP BY follow_followed_user_id),"0");', [
                '%table_users'    => DB::getTable('users'),
                '%table_follows' => DB::getTable('follows'),
                '%user_id'        => $user['id'],
            ]);
            DB::queryExec($sql);

            // Update affected user_following count
            $sql = strtr('UPDATE `%table_users` SET user_following = user_following - COALESCE((SELECT 1 FROM `%table_follows` WHERE follow_followed_user_id = %user_id AND user_id = follow_user_id AND follow_user_id <> follow_followed_user_id GROUP BY follow_user_id),"0");', [
                '%table_users'    => DB::getTable('users'),
                '%table_follows' => DB::getTable('follows'),
                '%user_id'        => $user['id'],
            ]);

            DB::queryExec($sql);

            DB::delete('albums', ['user_id' => $user['id']]); // Delete albums DB
            DB::delete('images', ['user_id' => $user['id']]); // Delete images DB
            DB::delete('logins', ['user_id' => $user['id']]); // Delete logins
            DB::delete('likes', ['user_id' => $user['id']]); // Delete user likes
            DB::delete('follows', ['user_id' => $user['id'], 'followed_user_id' => $user['id']], 'OR'); // Delete user's followers and follows
            DB::delete('users', ['id' => $user['id']]); // Delete user DB
        } catch (Exception $e) {
            throw new UserException($e->getMessage(), $e->getCode());
        }
    }

    public static function statusRedirect($status)
    {
        if (isset($status) and $status != null and $status !== 'valid') {
            if ($status == 'awaiting-email') {
                $status = 'email-needed';
            }
            G\redirect('account/' . $status);
        }
    }
    /**
     * Checks if a username string is usable as a valid username or not.
     */
    public static function isValidUsername($string)
    {
        $restricted = [
            'tag', 'tags',
            'categories',
            'profile',
            'messages',
            'map',
            'feed',
            'events',
            'notifications',
            'discover',
            'upload',
            'following', 'followers',
            'flow', 'trending', 'popular', 'fresh', 'upcoming', 'editors', 'profiles',
            'activity', 'upgrade', 'account',
            'affiliates', 'billing',
            'do', 'go', 'redirect',
            'api', 'sdk', 'plugin', 'plugins', 'tools',
            'external',
            'importer',
        ];
        $virtual_routes = ['image', 'album'];
        foreach ($virtual_routes as $k) {
            $restricted[] = getSetting('route_' . $k);
        }
        return preg_match('/' . getSetting('username_pattern') . '/', $string) && !in_array($string, $restricted) && !G\is_route_available($string) && !file_exists(G_ROOT_PATH . $string);
    }

    public static function formatArray($object)
    {
        try {
            if ($object) {
                $output = DB::formatRow($object);
                self::fill($output);
            }
            return $object ? $output : null;
        } catch (Exception $e) {
            throw new UserException($e->getMessage(), 400);
        }
    }

    public static function fill(&$user)
    {
        $user['id_encoded'] = encodeID($user['id']);

        // Abbreviated counts
        $user['image_count_display'] = $user['image_count'] == false ? 0 : G\abbreviate_number($user['image_count']);
        $user['album_count_display'] = $user['album_count'] == false ? 0 : G\abbreviate_number($user['album_count']);

        // Populate user URLs
        $user['url'] = self::getUrl($user);
        $user['url_albums'] = self::getUrlAlbums($user['url']);
        $user['url_liked'] = $user['url'] . '/liked';
        $user['url_following'] = $user['url'] . '/following';
        $user['url_followers'] = $user['url'] . '/followers';

        if (!filter_var($user['website'], FILTER_VALIDATE_URL)) {
            unset($user['website']);
        }

        // Do some safe cleaning
        if (isset($user['website'])) {
            $user['website_safe_html'] = G\safe_html($user['website']);
            $user['website_display'] = $user['is_admin'] ? $user['website_safe_html'] : get_redirect_url($user['website_safe_html']);
        }
        if (isset($user['bio'])) {
            $user['bio_safe_html'] = G\safe_html($user['bio']);
            $user['bio_linkify'] = $user['is_admin'] ? G\linkify($user['bio_safe_html'], ['attr' => ['target' => '_blank']]) : linkify_redirector($user['bio_safe_html']);
        }

        if (empty($user['name'])) {
            $user['name'] = ucfirst($user['username']);
        }

        foreach (['image_count', 'album_count'] as $v) {
            $single = $v == 'image_count' ? 'image' : 'album';
            $plural = $v == 'image_count' ? 'images' : 'albums';
            if (is_callable('_n')) {
                $user[$v . '_label'] = _n($single, $plural, $user[$v]);
            } else {
                $user[$v . '_label'] = $user[$v] == 1 ? $single : $plural;
            }
        }

        // Get first name like "Rodolfo" from "Rodolfo Berrios"
        $name_array = explode(' ', $user['name']);
        $user['firstname'] = mb_strlen($name_array[0]) > 20 ? trim(mb_substr($name_array[0], 0, 20, 'UTF-8')) : $name_array[0];
        $user['firstname_html'] = G\safe_html(strip_tags($user['firstname']));

        // Get short name like Rodolfoverylong Berr from "Rodolfoverylong Berrios"
        $user['name_short'] = mb_strlen($user['name']) > 20 ? $user['firstname'] : $user['name'];
        $user['name_short_html'] = G\safe_html(strip_tags($user['name_short']));

        if ($user['avatar_filename']) {
            $avatar_file = $user['id_encoded'] . '/' . $user['avatar_filename'];
            $avatar_path = CHV_PATH_CONTENT_IMAGES_USERS . $avatar_file;
            if (file_exists($avatar_path)) {
                $user['avatar'] = array(
                    'filename'    => $user['avatar_filename'],
                    'url'        => get_users_image_url($avatar_file)
                );
            }
        }
        unset($user['avatar_filename']);

        if ($user['background_filename']) {
            $background_file = $user['id_encoded'] . '/' . $user['background_filename'];
            $background_path = CHV_PATH_CONTENT_IMAGES_USERS . $background_file;

            $pathinfo = pathinfo($background_path);
            $background_md_file = $user['id_encoded'] . '/' . $pathinfo['filename'] . '.md.' . $pathinfo['extension'];

            if (file_exists($background_path)) {
                $user['background'] = array(
                    'filename'    => $user['background_filename'],
                    'url'        => get_users_image_url($user['id_encoded'] . '/' . $user['background_filename']),
                    'medium'    => [
                        'filename'    => $pathinfo['basename'],
                        'url'         => get_users_image_url($background_md_file)
                    ]
                );
            }
        }
        unset($user['background_filename']);
        unset($user['facebook_username']);

        if ($user['twitter_username']) {
            $user['twitter'] = array(
                'username'    => $user['twitter_username'],
                'url'        => 'http://twitter.com/' . $user['twitter_username']
            );
        }
        unset($user['twitter_username']);

        $user['notifications_unread_display'] = $user['notifications_unread'] > 10 ? '+10' : $user['notifications_unread'];
    }

    public static function sanitizeUserName($name)
    {
        return preg_replace('#<|>#', '', $name);
    }

    // Clean unconfirmed accounts
    public static function cleanUnconfirmed($limit = null)
    {
        $db = DB::getInstance();
        $query = 'SELECT * FROM ' . DB::getTable('users') . ' WHERE user_status IN ("awaiting-confirmation", "awaiting-email") AND user_date_gmt <= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 2 DAY) ORDER BY user_id DESC';
        if (is_int($limit)) {
            $query .= ' LIMIT ' . $limit;
        }
        $db->query($query);
        $users = $db->fetchAll();
        foreach ($users as $user) {
            $user = self::formatArray($user);
            self::delete($user);
        }
    }
}

class UserException extends Exception
{
}
