<?php

namespace App\Http\Controllers;

use App;
use App\Http\ViewHelpers\ViewFeeder;
use App\Http\Requests\UserRequest;
use App\Permission;
use App\Role;
use App\User;
use Auth;
use Carbon\Carbon;
use DB;
use Hash;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Lang;
use Redirect;
use Session;
use Validator;

/**
 * Class UserController
 * @package App\Http\Controllers
 */
class UserController extends Controller implements ViewFeederAwareController
{
    const COLUMNS = [
        'id',
        'human_name',
        'email',
        'role',
        'created_at',
        'updated_at'
    ];

    /**
     * Create a new controller instance.
     */
    public function __construct()
    {
        $this->middleware('auth');
    }

    /**
     * Display a listing of the resource.
     * @param Request $request
     * @return \Illuminate\Http\Response
     * @internal param Request $request
     */
    public function index(Request $request)
    {
        $this->validate($request, [
            'sort' => Rule::in(self::COLUMNS),
            'ordering' => 'in:asc,desc',
            'count' => 'integer|min:5|max:100'
        ]);

        $sort = $request->get('sort', 'human_name');
        $ordering = $request->get('ordering', 'asc');
        $count = intval($request->get('count', 10));;

        $entityClasses = DB::table('users as u')
            ->leftJoin('role_user as ru', 'u.id', '=', 'ru.user_id')
            ->leftJoin('roles as r', 'ru.role_id', '=', 'r.id')
            ->select([
                'u.id as id',
                'u.name as human_name',
                DB::raw('GROUP_CONCAT(`r`.`name` SEPARATOR \', \') as `role`'),
                'u.email as email',
                'u.created_at as created_at',
                'u.updated_at as updated_at'
            ])
            ->groupBy('u.id')
            ->orderBy($sort, $ordering)
            ->orderBy('id', 'desc')
            ->paginate($count);

        $viewFeeder = new ViewFeeder($this, ViewFeeder::LIST);

        return view('results.list', $viewFeeder
            ->results($entityClasses)
            ->sorting($sort, $ordering)
            ->permissions([
                'canEdit' => Permission::EDIT_USER,
                'canDelete' => Permission::DELETE_USER
            ])
            ->size(12)
            ->feed()
        );
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        return view('user.create');
    }

    /**
     * Display the specified resource.
     *
     * @param User $user
     * @return \Illuminate\Http\Response
     * @internal param int $id
     */
    public function show(User $user)
    {
        $feeder = new ViewFeeder($this, ViewFeeder::SHOW);

        $user['human_name'] = $user->name;
        $userRole = $user->roles()->first();

        $user['role'] = $userRole ? $userRole->name : '-';


        return view('user.show', $feeder
            ->result($user)
            ->permissions([
                'canEdit' => Permission::EDIT_USER,
                'canDelete' => Permission::DELETE_USER
            ])
            ->size(6)
            ->feed());
    }

    public function store(Request $request)
    {
        $data = $request->all();

        Validator::make($data, [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:6|confirmed',
            'role' => [
                'string',
                'exists:roles,id'
            ]
        ])->validate();

        $user = User::create([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => bcrypt($data['password']),
        ]);

        if (!$user) {
            Session::flash('error', 'Failed to create this user!');
            return Redirect::back();
        }

        if (isset($data['role'])) {
            $user->attachRole($data['role']);
        }

        Session::flash('success', Lang::get('user.success_create', ['name' => $data['name']]));
        return Redirect::route('users.show', $user->id);
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param User $user
     * @return \Illuminate\Http\Response
     * @internal param int $id
     */
    public function edit(User $user)
    {
        $viewFeeder = new ViewFeeder($this, ViewFeeder::EDIT);

        $user['human_name'] = $user->name;

        $userRoles = $user->roles()->get();

        $user['roles'] = $userRoles;

        return view('user.edit', $viewFeeder
            ->result($user)
            ->allRoles()
            ->size(6)
            ->feed());
    }

    /**
     * @param Request $request
     * @param User $user
     * @return array
     */
    protected function getChangedFields(Request $request, User $user)
    {
        $changedFields = [];

        $password = $request->get('password');
        if ($password !== null)
            if (!Hash::check($password, $user->password)) {
                $changedFields[] = 'password';
            }

        if ($request->get('name') !== $user->name) {
            $changedFields[] = 'name';
        }

        if ($request->get('email') !== $user->email) {
            $changedFields[] = 'email';
        }

        $manipulatingHimself = $user->id === Auth::getUser()->id;

        if (Auth::getUser()->can(Permission::MANIPULATE_USER_ROLES) && ! $manipulatingHimself) {
            $userRole = $user->roles()->first();
            $newRole = $request->get('role');

            if ($userRole === null || $userRole->id != $newRole)
                $changedFields[] = 'role';
        }

        return $changedFields;
    }

    /**
     * Update the specified resource in storage.
     *
     * @param Request $request
     * @param User $user
     * @return \Illuminate\Http\RedirectResponse
     * @internal param int $id
     */
    public function update(Request $request, User $user)
    {
        $changedFields = $this->getChangedFields($request, $user);
        if (count($changedFields) === 0) {
            Session::flash('error', Lang::get('validation.unchanged'));
            return Redirect::back();
        }

        if ($request->has('id')) {
            Session::flash('error', Lang::get('validation.id_change_forbidden'));
            return Redirect::back();
        }

        $canManipulateUserRoles = Auth::getUser()->can(Permission::MANIPULATE_USER_ROLES);

        $manipulatingHimself = $user->id === Auth::getUser()->id;

        $this->validate($request, [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users,email,' . $user->id,
            'password' => 'nullable|string|min:6|confirmed',
            'role' => [
                $canManipulateUserRoles && !$manipulatingHimself ? 'required' : 'nullable',
                'integer',
                'exists:roles,id' // Value must exist in roles table
            ]
        ]);

        $changingHimself = $user->id === Auth::getUser()->id;

        $desiredRole = $request->get('role');
        if ($desiredRole !== null) {
            if (!$canManipulateUserRoles) {
                Session::flash('error', 'You have no permission to manipulate user roles!');
                return Redirect::back();

            } elseif ($changingHimself && $desiredRole !== $user->roles()->first()->id) {
                Session::flash('error', 'You cannot modify role for yourself!');
                return Redirect::back();
            }
        }

        foreach ($changedFields as $changedField) {
            $newValue = $request->get($changedField);

            if ($changedField === 'role') {
                $user->detachRoles();
                $user->attachRole($newValue);
            } elseif ($changedField === 'password')
                $user->$changedField = bcrypt($newValue);
            else
                $user->$changedField = $newValue;
        }

        $user->updated_at = Carbon::now();
        $user->save();

        Session::flash('success', Lang::get('user.update_success'));

        if ($changingHimself)
            return Redirect::route('user.show');

        return Redirect::route(self::getRouterName() . '.show', $user->id);
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param User $user
     * @return \Illuminate\Http\RedirectResponse
     * @internal param int $id
     */
    public
    function destroy(User $user)
    {
        if ($user->id === Auth::user()->id) {

            Session::flash('error', Lang::get('user.cannot_delete_yourself'));

        } else {

            $deleted = $user->delete();

            if ($deleted)
                Session::flash('success', Lang::trans('user.delete_success', ['id' => $user->id]));
            else
                Session::flash('error', Lang::trans('user.delete_failed', ['id' => $user->id]));
        }

        return Redirect::back();
    }

    /**
     * Gets the name of the router to be used from the view.
     *
     * @return string
     */
    public static function getRouterName(): string
    {
        return 'users';
    }

    /**
     * Returns singular of object controller is working with.
     *
     * @return string
     */
    public
    function getSingular(): string
    {
        return 'User';
    }

    /**
     * Returns plural of object controller is working with.
     *
     * @return string
     */
    public
    function getPlural(): string
    {
        return 'Users';
    }

    /**
     * Returns array of columns to be shown when listing objects controller is working with.
     *
     * @return array
     */
    public
    function getColumns(): array
    {
        return self::COLUMNS;
    }
}
