<?php

namespace App\Packages\v4\User\Repository;

use App\Packages\v4\Core\Repository\AbstractRepository;
use App\Packages\User\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Laravel\Socialite\Two\User as SocialUser;
use Carbon\Carbon;
use DB;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection;

/**
 * Class UserRepository
 * @package App\Packages\User\Repository\v4
 *
 * @method User firstById($id)
 *
 * @property User $model
 */
class UserRepository extends AbstractRepository
{

    public static function init(){
        return new UserRepository(User::class);
    }

    /**
     * @param string $nickname
     * @return bool
     */
    public function uniqueNickname($nickname)
    {
        $nickname = mb_strtolower($nickname);

        $res = $this->query()
            ->where(DB::raw('LOWER(nickname)'), $nickname)
            ->where('is_registered', 1)
            ->first();

        if ( $res ) return false;
        return true;
    }

	/**
     * @return Collection|User[]
     */
    public function getRegisteredUsers()
    {
        return $this->query()
            ->where('is_registered', 1)
            ->get();
    }

    /**
     * @param $phoneCode
     * @param $phoneNumber
     *
     * @return object|User|null
     */
    public function firstRegisteredByPhoneNumber($phoneCode, $phoneNumber)
    {
        return $this->query()
            ->where('phone_code', '=', $phoneCode)
            ->where('phone_number', '=', $phoneNumber)
            ->where('is_registered', '=', true)
            ->first();
    }

	/**
	 * @param string $nickname
	 *
	 * @return User|null|object
	 */
	public function findByNickname($nickname)
	{
	    $nickname = mb_strtolower($nickname);

		return $this->query()
            ->whereNotNull('nickname')
            ->where('nickname', '!=', '')
            ->where('nickname', 'ilike', '%' . $nickname . '%')->first();
	}

    /**
     * @param string $nickname
     *
     * @return User|null|object
     */
	public function firstByNickname($nickname)
    {
        $nickname = mb_strtolower($nickname);

        return $this->query()
            ->whereNotNull('nickname')
            ->where('nickname', '!=', '')
            ->where('is_registered', true)
            ->where(DB::raw('LOWER(nickname)'), $nickname)
            ->first();
    }

	/**
	 * @param $email
	 *
	 * @return User|null|object
	 */
	public function firstByEmail($email)
	{
	    $email = mb_strtolower($email);

		return $this->query()
			->where(DB::raw('LOWER(email)'), '=', $email)
			->first();
	}

    /**
     * @param $email
     * @return User|null|object
     */
	public function firstByEmailWithPassword($email)
    {
        $email = mb_strtolower($email);

        return $this->query()
            ->where(DB::raw('LOWER(email)'), '=', $email)
            ->where('password', '!=', '')
            ->first();
    }

	/**
	 * @param $uuid
	 *
	 * @return User|null|object
	 */
	public function firstByUuid($uuid)
	{
		return $this->query()
			->where('uuid', '=', $uuid)
			->first();
	}

    /**
     * @param $phoneHash
     *
     * @return User|null|object
     */
    public function firstByPhoneHash($phoneHash)
    {
        return $this->query()
            ->where('phone_hash', '=', $phoneHash)
            ->first();
    }

    /**
     * @param SocialUser $response
     * @return User|bool|object|null
     */
    public function firstByFacebookResponse($response)
    {
        $user = $this->firstByFacebookId($response->getId());

        if ( $user ) return $user;

        if ( ! $response->getEmail() ) return false;

        $user = $this->firstByEmail($response->getEmail());

        if ( ! $user ) return false;

        $user->fb_id = $response->getId();
        $user->save();

        return $user;
    }

    /**
     * @param string $id
     * @return User|object|null
     */
    public function firstByFacebookId($id)
    {
        return $this->query()
            ->where(User::FACEBOOK_ID, $id)
            ->first();
    }

    /**
     * @param SocialUser $response
     * @return User|bool|object|null
     */
    public function firstByGoogleResponse($response)
    {
        $user = $this->firstByGoogleId($response->getId());

        if ( $user ) return $user;

        if ( ! $response->getEmail() ) return false;

        $user = $this->firstByEmail($response->getEmail());

        if ( ! $user ) return false;

        $user->google_id = $response->getId();
        $user->save();

        return $user;
    }

    /**
     * @param string $id
     * @return User|object|null
     */
    public function firstByGoogleId($id)
    {
        return $this->query()
            ->where(User::GOOGLE_ID, $id)
            ->first();
    }

    /**
     * @param SocialUser $response
     * @return User|bool|object|null
     */
    public function firstByAppleResponse($response)
    {
        $user = $this->firstByAppleId($response->getId());

        if ( $user ) return $user;

        if ( ! $response->getEmail() ) return false;

        $user = $this->firstByEmail($response->getEmail());

        if ( ! $user ) return false;

        $user->apple_id = $response->getId();
        $user->save();

        return $user;
    }

    /**
     * @param $id
     * @return User|object|null
     */
    public function firstByAppleId($id)
    {
        return $this->query()
            ->where(User::APPLE_ID, $id)
            ->first();
    }

    /**
     * @return Collection|static[]|User[]
     */
    public function trashedUsers()
    {
        return $this->query()
            ->onlyTrashed()
            ->get();
    }

    /**
     * @param Carbon $date
     *
     * @return int
     */
    public function countByMonth($date)
    {
        $year = $date->format('Y');
        $month = $date->format('m');

        return $this->query()
            ->where('is_registered', true)
            ->whereRaw("date_part('year', created_at) = '{$year}' AND date_part('month', created_at) = '{$month}'")
            ->count();
    }

    /**
     * @param Carbon $from
     * @param Carbon $to
     *
     * @return int
     */
    public function countByDateRange($from, $to)
    {
        $from = $from->format('Y-m-d');
        $to = $to->format('Y-m-d');

        return $this->query()
            ->where('is_registered', true)
            ->whereRaw("created_at between '{$from}' AND '{$to}'")
            ->count();
    }

    /**
     * @param int $user_id
     * @param array $ignore
     * @param int $limit
     * @return User[]|Collection
     */
    public function getSuggestedUsersForSearch($user_id, $ignore = [], $limit = 10)
    {
        $query = $this->query()
            ->select('table_user.*')
            ->whereNotNull('table_user.nickname')
            ->where('table_user.nickname', '!=', '')
            ->where('table_user.is_registered', true)
            ->where('table_user.is_suggested', true)
            ->where('table_user.id', '!=', $user_id)
            ->orderBy('table_user.suggested_position', 'DESC')
            ->limit($limit);

        if ( ! empty($ignore) ) {
            $query->whereNotIn('table_user.id', $ignore);
        }

        return $query->get();
    }

    /**
     * @param int|bool $user_id
     * @param array $ignore
     * @param int $limit
     * @return User[]|Collection
     */
    public function getPopularUsersForSearch($user_id = false, $ignore = [], $limit = 10)
    {
        $query = $this->query()
            ->select('table_user.*')
            ->whereNotNull('table_user.nickname')
            ->where('table_user.nickname', '!=', '')
            ->where('table_user.is_registered', true)
            ->where('table_user.is_suggested', false)
            ->where('table_user.id', '!=', $user_id)
            ->orderBy('table_user.search_position', 'DESC')
            ->limit($limit);

        if ( ! empty($ignore) ) {
            $query->whereNotIn('table_user.id', $ignore);
        }

        return $query->get();
    }

    /**
     * @param bool|int $user_id
     * @param array $ignore
     * @return Builder
     */
    private function querySuggestedAndPopularUsersForSearch($user_id = false, $ignore = [])
    {
        $query = $this->query()
            ->select('table_user.*')
            ->whereNotNull('table_user.nickname')
            ->where('table_user.nickname', '!=', '')
            ->where('table_user.is_registered', true)
            ->where('table_user.id', '!=', $user_id)
            ->orderBy(\DB::raw("
                CASE WHEN \"table_user\".\"is_suggested\" = true 
                THEN \"table_user\".\"suggested_position\" 
                ELSE 0 END
            "), 'DESC')
            ->orderBy('table_user.search_position', 'DESC');

        if ( ! empty($ignore) ) {
            $query->whereNotIn('table_user.id', $ignore);
        }

        return $query;
    }

    /**
     * @param bool|int $user_id
     * @param array $ignore
     * @param int $offset
     * @return int
     */
    public function countSuggestedAndPopularUsersForSearch($user_id = false, $ignore = [], $offset = 0)
    {
        $query = $this->querySuggestedAndPopularUsersForSearch($user_id, $ignore);

        if ($offset > 0) $query->skip($offset);

        return $query->count();
    }

    /**
     * @param bool|int $user_id
     * @param array $ignore
     * @param int $limit
     * @param int $offset
     * @return User[]|Collection
     */
    public function getSuggestedAndPopularUsersForSearch($user_id = false, $ignore = [], $limit = 10, $offset = 0)
    {
        $query = $this->querySuggestedAndPopularUsersForSearch($user_id, $ignore);

        if ($limit > 0) $query->limit($limit);
        if ($offset > 0) $query->skip($offset);

        return $query->get();
    }

    public function getLastSearches($user_id, $limit = 5)
    {
        $query = $this->query()
            ->select('table_user.*')
            ->leftJoin('table_user_search', function(JoinClause $join) use ($user_id) {
                $join->on('table_user_search.user_id', '=', 'table_user.id')
                    ->on('table_user_search.owner_id', '=', DB::raw($user_id));
            })
            ->whereNotNull('table_user_search.id')
            ->whereNotNull('table_user.nickname')
            ->where('table_user.nickname', '!=', '')
            ->where('table_user.is_registered', true)
            ->orderBy('last_search', 'DESC')
            ->limit($limit);

        return $query->get();
    }

    /**
     * @param int $user_id
     * @param string $keyword
     * @return Builder
     */
    private function querySearchUsersByKeyword($user_id, $keyword)
    {
        $keyword = mb_strtolower($keyword);

        return $this->query()
            ->whereNotNull('nickname')
            ->where('nickname', '!=', '')
            ->where('is_registered', true)
            ->where('id', '!=', $user_id)
            ->whereRaw("LOWER(nickname) like '{$keyword}%'")
            ->orderBy('search_position', 'DESC');
    }

    /**
     * @param int $user_id
     * @param string $keyword
     * @return int
     */
    public function countSearchUsersByKeyword($user_id, $keyword, $offset = 0)
    {
        $query = $this->querySearchUsersByKeyword($user_id, $keyword);

        if ($offset > 0) $query->skip($offset);

        return $query->count();
    }

    /**
     * @param int $user_id
     * @param string $keyword
     * @param int $limit
     * @param int $offset
     * @return User[]|Collection
     */
    public function getSearchUsersByKeyword($user_id, $keyword, $limit = 10, $offset = 0)
    {
        $query = $this->querySearchUsersByKeyword($user_id, $keyword);

        if ($limit > 0) $query->limit($limit);
        if ($offset > 0) $query->skip($offset);

        return $query->get();
    }
}
