<?php namespace App\Http\Controllers\Admin;

use App\Exceptions\DeviceLimitException;
use Tobuli\Importers\Geofence\GeofenceImportManager;
use Tobuli\Importers\POI\POIImportManager;
use Validator;
use Facades\Repositories\UserRepo;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Input;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Facades\Response;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\View;
use Tobuli\Repositories\EmailTemplate\EmailTemplateRepositoryInterface as EmailTemplate;
use Tobuli\Exceptions\ValidationException;
use Tobuli\Repositories\BillingPlan\BillingPlanRepositoryInterface as BillingPlan;
use Tobuli\Services\PermissionService;
use Tobuli\Validation\ClientFormValidator;
use Tobuli\Repositories\Device\DeviceRepositoryInterface as Device;
use Tobuli\Repositories\TraccarDevice\TraccarDeviceRepositoryInterface as TraccarDevice;
use Tobuli\Repositories\Event\EventRepositoryInterface as Event;
use Tobuli\Repositories\Geofence\GeofenceRepositoryInterface as Geofence;
use Tobuli\Repositories\GeofenceGroup\GeofenceGroupRepositoryInterface as GeofenceGroup;
use Tobuli\Repositories\User\UserRepositoryInterface as User;
use Tobuli\Repositories\UserMapIcon\UserMapIconRepositoryInterface as UserMapIcon;
use ModalHelpers\GeofenceModalHelper;
use ModalHelpers\MapIconModalHelper;
use Facades\Validators\ObjectsListSettingsFormValidator;

class ClientsController extends BaseController
{
    /**
     * @var ClientFormValidator
     */
    private $clientFormValidator;

    private $section = 'clients';
    /**
     * @var Device
     */
    private $device;
    /**
     * @var TraccarDevice
     */
    private $traccarDevice;
    /**
     * @var Event
     */
    private $event;

    private $permissionService;

    function __construct(
        ClientFormValidator $clientFormValidator,
        Device $device,
        TraccarDevice $traccarDevice,
        Event $event,
        EmailTemplate $emailTemplate,
        PermissionService $permissionService
    ) {
        parent::__construct();
        $this->clientFormValidator = $clientFormValidator;
        $this->device = $device;
        $this->traccarDevice = $traccarDevice;
        $this->event = $event;
        $this->emailTemplate = $emailTemplate;
        $this->permissionService = $permissionService;
    }

    public function index()
    {
        $input = Input::all();

        $items = UserRepo::searchAndPaginate($input, 'email');
        $section = $this->section;
        $page = $items->currentPage();
        $total_pages = $items->lastPage();
        $pagination = smartPaginate($items->currentPage(), $total_pages);
        $url_path = $items->resolveCurrentPath();

        return $this->api
            ? $items
            : View::make('admin::' . ucfirst($this->section) . '.' . (Request::ajax() ? 'table' : 'index'))->with(compact('items',
                'input', 'section', 'page', 'total_pages', 'pagination', 'url_path'));
    }

    public function create(BillingPlan $billingPlanRepo)
    {
        $managers = ['0' => '-- ' . trans('admin.select') . ' --'] + UserRepo::getOtherManagers(0)->lists('email',
                'id')->all();

        $maps = getAvailableMaps();

        $plans = [];
        if (settings('main_settings.enable_plans'))
            $plans = ['0' => '-- ' . trans('admin.select') . ' --'] + $billingPlanRepo->getWhere([], 'objects',
                    'asc')->lists('title', 'id')->all();

        $objects_limit = null;
        if (hasLimit()) {
            $objects_limit = Auth::User()->devices_limit - getManagerUsedLimit(Auth::User()->id);
            $objects_limit = $objects_limit < 0 ? 0 : $objects_limit;
        }

        $grouped_permissions = $this->permissionService->group(
            $this->permissionService->getByUserRole()
        );
        $permission_values = $this->permissionService->getUserDefaults();

        $devices = groupDevices($this->user->accessibleDevicesWithGroups()->get(), $this->user);
        $numeric_sensors = config('tobuli.numeric_sensors');
        $settings = UserRepo::getListViewSettings(null);
        $fields = config('tobuli.listview_fields');
        listviewTrans(null, $settings, $fields);

        return View::make('admin::' . ucfirst($this->section) . '.create')->with(compact('managers', 'maps', 'plans',
            'objects_limit', 'grouped_permissions', 'devices', 'fields', 'settings', 'numeric_sensors', 'permission_values'));
    }

    public function store(BillingPlan $billingPlanRepo)
    {
        $input = Input::all();
        unset($input['id']);

        try {
            if (hasLimit())
                $input['enable_devices_limit'] = 1;

            if (isset($input['enable_devices_limit']) && empty($input['devices_limit']))
                throw new ValidationException([
                    'devices_limit' => strtr(trans('validation.required'),
                        [':attribute' => trans('validation.attributes.devices_limit')]),
                ]);

            if (isset($input['enable_expiration_date']) && empty($input['expiration_date']))
                throw new ValidationException([
                    'expiration_date' => strtr(trans('validation.required'),
                        [':attribute' => trans('validation.attributes.expiration_date')]),
                ]);

            $this->clientFormValidator->validate('create', $input);

            if ($input['group_id'] != 2)
                $input['manager_id'] = null;

            if (request()->input('columns', []))
                ObjectsListSettingsFormValidator::validate('update', request()->only(['columns', 'groupby']));

            if (empty($input['manager_id'])) {
                if (isAdmin()) {
                    $input['manager_id'] = null;
                } else {
                    unset($input['manager_id']);
                }
            }

            if (array_key_exists('billing_plan_id', $input)) {
                $plan = $billingPlanRepo->find($input['billing_plan_id']);
                if ( ! empty($plan)) {
                    $input['devices_limit'] = $plan->objects;
                    if (empty($input['subscription_expiration']))
                        $input['subscription_expiration'] = date('Y-m-d H:i:s',
                            strtotime(date('Y-m-d H:i:s') . " + {$plan->duration_value} {$plan->duration_type}"));
                } else {
                    $input['billing_plan_id'] = null;
                }
            }

            if (hasLimit()) {
                $objects_limit = Auth::User()->devices_limit - getManagerUsedLimit(Auth::User()->id);
                if ($objects_limit < $input['devices_limit'])
                    throw new ValidationException(['devices_limit' => trans('front.devices_limit_reached')]);
            }

            $input['active'] = isset($input['active']);
            $input['lang'] = settings('main_settings.default_language');
            $input['unit_of_altitude'] = settings('main_settings.default_unit_of_altitude');
            $input['unit_of_distance'] = settings('main_settings.default_unit_of_distance');
            $input['unit_of_capacity'] = settings('main_settings.default_unit_of_capacity');
            $input['timezone_id'] = settings('main_settings.default_timezone');
            $input['map_id'] = settings('main_settings.default_map');
            $input['devices_limit'] = ! isset($input['enable_devices_limit']) ? null : $input['devices_limit'];
            $input['subscription_expiration'] = ! isset($input['enable_expiration_date']) ? '0000-00-00 00:00:00' : $input['expiration_date'];
            $input['open_device_groups'] = '["0"]';
            $input['open_geofence_groups'] = '["0"]';

            if ( ! empty($input['objects']))
                if ( ! is_null($input['devices_limit']) && $input['devices_limit'] < count($input['objects']))
                    throw new DeviceLimitException();

            $user = UserRepo::create($input);

            if ( ! empty($user)) {
                if ( ! empty($input['objects'])) {
                    $user->devices()->sync($input['objects']);
                }

                if ( ! empty($input['account_created']))
                    $this->notifyUser($input);
            }

            if ( ! array_key_exists('billing_plan_id', $input) || (array_key_exists('billing_plan_id',
                        $input) && empty($plan))) {
                if (array_key_exists('perms', $input)) {
                    $user->setRelation('manager', UserRepo::find(array_get($input, 'manager_id')));

                    $permissions = $this->permissionService->getByUser($user);

                    foreach ($permissions as $key => $val) {
                        if ( ! array_key_exists($key, $input['perms']))
                            continue;

                        DB::table('user_permissions')->insert([
                            'user_id' => $user->id,
                            'name'    => $key,
                            'view'    => $val['view'] && (array_get($input['perms'][$key],
                                    'view') || array_get($input['perms'][$key],
                                    'edit') || array_get($input['perms'][$key], 'remove')) ? 1 : 0,
                            'edit'    => $val['edit'] && array_get($input['perms'][$key], 'edit') ? 1 : 0,
                            'remove'  => $val['remove'] && array_get($input['perms'][$key], 'remove') ? 1 : 0,
                        ]);
                    }
                }
            }

            if (request()->input('columns', []))
                UserRepo::setListViewSettings($user->id, request()->only(['columns', 'groupby']));

            return Response::json($this->api ? ['status' => 1, 'item' => $user] : ['status' => 1]);
        } catch (ValidationException $e) {
            return Response::json(['errors' => $e->getErrors()]);
        }
    }

    public function edit($id = null, BillingPlan $billingPlanRepo)
    {
        $item = UserRepo::find($id);

        if (empty($item))
            return modalError(dontExist('global.user'));

        $managers = ['0' => '-- ' . trans('admin.select') . ' --'] + UserRepo::getOtherManagers($item->id)->lists('email',
                'id')->all();
        $maps = getAvailableMaps();
        $plans = [];
        if (settings('main_settings.enable_plans'))
            $plans = ['0' => '-- ' . trans('admin.select') . ' --'] + $billingPlanRepo->getWhere([], 'objects',
                    'asc')->lists('title', 'id')->all();

        $objects_limit = null;
        if (hasLimit()) {
            $objects_limit = $this->user->devices_limit - getManagerUsedLimit($this->user->id, $item->id);
            $objects_limit = $objects_limit < 0 ? 0 : $objects_limit;
        }

        $numeric_sensors = config('tobuli.numeric_sensors');
        $settings = UserRepo::getListViewSettings($id);
        $fields = config('tobuli.listview_fields');
        listviewTrans($id, $settings, $fields);
        $devices = groupDevices($this->user->accessibleDevicesWithGroups()->get(), $this->user);
        $grouped_permissions = $this->permissionService->group(
            $this->permissionService->getByUser($item)
        );
        $permission_values = $item->getPermissions();

        return View::make('admin::' . ucfirst($this->section) . '.edit')->with(compact('item', 'permission_values', 'managers', 'maps', 'plans', 'objects_limit', 'grouped_permissions', 'devices', 'fields', 'settings', 'numeric_sensors'));
    }

    public function update(BillingPlan $billingPlanRepo)
    {
        $input = Input::all();
        $id = $input['id'];
        $item = UserRepo::find($id);

        if ($_ENV['server'] == 'demo' && $id == 1 && Auth::User()->id != 1)
            return Response::json(['errors' => ['id' => "Can't edit main admin account."]]);

        try {
            if (hasLimit())
                $input['enable_devices_limit'] = 1;

            if (isset($input['enable_devices_limit']) && empty($input['devices_limit']))
                throw new ValidationException([
                    'devices_limit' => strtr(trans('validation.required'),
                        [':attribute' => trans('validation.attributes.devices_limit')]),
                ]);

            if (isset($input['enable_expiration_date']) && empty($input['expiration_date']))
                throw new ValidationException([
                    'expiration_date' => strtr(trans('validation.required'),
                        [':attribute' => trans('validation.attributes.expiration_date')]),
                ]);

            if (isset($input['expiration_date']))
                $input['subscription_expiration'] = $input['expiration_date'];

            $this->clientFormValidator->validate('update', $input, $id);
            if (empty($input['password']))
                unset($input['password']);

            if (empty($input['manager_id'])) {
                if (isAdmin()) {
                    $input['manager_id'] = null;
                } else {
                    unset($input['manager_id']);
                }
            }

            if ($id == Auth::User()->id)
                unset($input['manager_id'], $input['group_id']);

            if ( ! empty($input['manager_id']) && $this->managerInfinity($item, $input['manager_id']))
                throw new ValidationException([
                    'manager_id' => 'Managers infinity loop'
                ]);

            if (request()->input('columns', [])) {
                ObjectsListSettingsFormValidator::validate('update', request()->only(['columns', 'groupby']));

                UserRepo::setListViewSettings($id, request()->only(['columns', 'groupby']));
            }

            DB::table('user_permissions')->where('user_id', '=', $item->id)->delete();
            $plan = null;
            if (array_key_exists('billing_plan_id', $input)) {
                $plan = $billingPlanRepo->find($input['billing_plan_id']);
                if ( ! empty($plan)) {
                    $input['devices_limit'] = $plan->objects;
                    if (empty($input['subscription_expiration']))
                        $input['subscription_expiration'] = date('Y-m-d H:i:s',
                            strtotime(date('Y-m-d H:i:s') . " + {$plan->duration_value} {$plan->duration_type}"));
                }
            }

            if (empty($plan)) {
                $input['billing_plan_id'] = null;
                $input['devices_limit'] = ! isset($input['enable_devices_limit']) ? null : $input['devices_limit'];
                $input['subscription_expiration'] = ! isset($input['enable_expiration_date']) ? '0000-00-00 00:00:00' : $input['expiration_date'];
            }

            if (Auth::User()->isManager() && Auth::User()->id == $item->id) {
                $input['billing_plan_id'] = $item->billing_plan_id;
                $input['devices_limit'] = $item->devices_limit;
                $input['subscription_expiration'] = $item->subscription_expiration;
            } else {
                if (array_key_exists('perms', $input)) {
                    $item->setRelation('manager', UserRepo::find(array_get($input, 'manager_id')));

                    $permissions = $this->permissionService->getByUser($item);

                    foreach ($permissions as $key => $val) {
                        if ( ! array_key_exists($key, $input['perms']))
                            continue;

                        DB::table('user_permissions')->insert([
                            'user_id' => $item->id,
                            'name'    => $key,
                            'view'    => $val['view'] && (array_get($input['perms'][$key],
                                    'view') || array_get($input['perms'][$key],
                                    'edit') || array_get($input['perms'][$key], 'remove')) ? 1 : 0,
                            'edit'    => $val['edit'] && array_get($input['perms'][$key], 'edit') ? 1 : 0,
                            'remove'  => $val['remove'] && array_get($input['perms'][$key], 'remove') ? 1 : 0,
                        ]);
                    }
                }
            }

            if (hasLimit()) {
                $objects_limit = Auth::User()->devices_limit - getManagerUsedLimit(Auth::User()->id, $item->id);
                if ($objects_limit < $input['devices_limit'] && $input['devices_limit'] > $item->devices_limit)
                    throw new ValidationException(['devices_limit' => trans('front.devices_limit_reached')]);
            }

            if ( ! empty($input['objects']))
                if ( ! is_null($input['devices_limit']) && $input['devices_limit'] < count($input['objects']))
                    throw new DeviceLimitException();

            if ( isset($input['objects']) && empty($input['objects'])) {
                $input['objects'] = [];
            }

            $input['active'] = isset($input['active']);

            UserRepo::update($id, $input);

            $user = UserRepo::getWithFirst(['devices'], ['id' => $id]);

            if ( ! empty($user)) {
                if ( isset($input['objects'])) {
                    $user->devices()->sync($input['objects']);
                }
            }

            return Response::json(['status' => 1]);
        } catch (ValidationException $e) {
            return Response::json(['errors' => $e->getErrors()]);
        }
    }


    public function importMapIcon(User $userRepo, MapIconModalHelper $mapIconModalHelper)
    {
        $users = $userRepo->getUsers(Auth::User());

        $icons = $mapIconModalHelper->getIcons();

        return View::make('admin::' . ucfirst($this->section) . '.import_map_icon')->with(compact('users', 'icons'));
    }

    public function importMapIconSet(User $userRepo, POIImportManager $importManager)
    {
        $this->checkException('poi', 'store');

        $validator = Validator::make(request()->all(), [
            'file'       => 'required',
            'map_icon_id'=> 'required',
            'user_id'    => 'required|array',
        ]);

        if ($validator->fails())
            throw new ValidationException($validator->errors());

        $file = request()->file('file');

        if ( ! $file->isValid())
            throw new \Exception(trans('front.unsupported_format'));


        $users = $userRepo->getWhereIn(request()->get('user_id'));

        if (empty($users))
            return response()->json(['status' => 0]);

        foreach ($users as $user) {
            $additionals = [
                'map_icon_id' => request()->get('map_icon_id'),
                'user_id'     => $user->id
            ];
            $importManager->import($file, $additionals);
        }

        return response()->json([
            'status' => 1,
            'message' => trans('front.successfully_saved'),
        ]);
    }

    public function importGeofences(User $userRepo)
    {
        $users = $userRepo->getUsers(Auth::User());

        return View::make('admin::' . ucfirst($this->section) . '.import_geofences')->with(compact('users'));
    }

    public function importGeofencesSet(User $userRepo, GeofenceImportManager $importManager) {
        $this->checkException('poi', 'store');

        $validator = Validator::make(request()->all(), [
            'user_id'    => 'required|array',
            'file'       => 'required',
        ]);

        if ($validator->fails())
            throw new ValidationException($validator->errors());

        $file = request()->file('file');

        if ( ! $file->isValid())
            throw new \Exception(trans('front.unsupported_format'));


        $users = $userRepo->getWhereIn(request()->get('user_id'));

        if (empty($users))
            return response()->json(['status' => 0]);

        foreach ($users as $user) {
            $additionals = [
                'user_id'     => $user->id
            ];
            $importManager->import($file, $additionals);
        }

        return response()->json([
            'status' => 1,
            'message' => trans('front.successfully_saved'),
        ]);
    }

    public function getDevices($id)
    {
        $user = UserRepo::getWithFirst(['devices', 'devices.traccar'], ['id' => $id]);

        $this->checkException('users', 'show', $user);

        $items = $user->devices;

        return View::make('admin::Clients.get_devices')->with(compact('items'));
    }

    public function destroy()
    {
        $ids = Input::get('id');

        if (empty($ids))
            return Response::json(['status' => 1]);

        if ( ! is_array($ids))
            $ids = [$ids];

        $users = \Tobuli\Entities\User::whereIn('id', $ids)->get();

        foreach ($users as $user)
        {
            if ( ! $this->user->can('remove', $user))
                continue;

            $user->delete();
        }

        return Response::json(['status' => 1]);
    }

    public function loginAs($id)
    {
        $item = UserRepo::find($id);

        return View::make('admin::Clients.login_as')->with(compact('item'));
    }

    public function loginAsAgree($id)
    {
        $item = UserRepo::find($id);

        if ($item && ! Auth::User()->can('show', $item)) {
            $item = null;
        }

        if ( ! empty($item)) {
            auth()->loginUsingId($item->id);
        }

        return Redirect::route('home');
    }

    public function getPermissionsTable(BillingPlan $billingPlanRepo, User $userRepo)
    {
        $user = $userRepo->find(request('user_id'));
        $plan = $billingPlanRepo->find(request('id'));

        if ( ! is_null($user)) {
            $permissions = $this->permissionService->getByUser($user);
        } else {
            $permissions = (request()->has('group_id')) ?
                $this->permissionService->getByGroupId(request('group_id')) :
                $this->permissionService->getByUserRole();
        }

        $is_plan_set = ( ! is_null($plan));

        $item = $is_plan_set ? $plan : $user;

        if ( ! is_null($item)) {
            $permission_values = $item->getPermissions();
        } else {
            $permission_values = (request()->has('group_id')) ?
                $this->permissionService->getGroupDefaults(request('group_id')) :
                $this->permissionService->getUserDefaults();
        }

        return view('Admin.Clients._perms')->with([
            'permission_values'  => $permission_values,
            'plan'    => $is_plan_set,
            'grouped_permissions' => $this->permissionService->group($permissions),
        ]);
    }

    private function notifyUser($data)
    {
        $template = $this->emailTemplate->whereName('account_created');

        sendTemplateEmail($data['email'], $template, $data);
    }

    public function setStatus()
    {
        $validator = Validator::make(request()->all(), [
            'id'     => 'required_without:email',
            'email'  => 'required_without:id|email',
            'status' => 'required|in:1,0',
        ]);

        if ($validator->fails())
            throw new ValidationException($validator->messages());

        if (request()->has('id')) {
            $user = \Tobuli\Entities\User::find(request('id'));
        } else {
            $user = \Tobuli\Entities\User::where('email', request('email'))->first();
        }

        $this->checkException(\Tobuli\Entities\User::class, 'edit', $user);

        $user->update(['active' => request('status')]);

        return Response::json(['status' => 1]);
    }

    private function managerInfinity($user, $manager_id, $managers = [])
    {
        // User cant be his own manager
        if ($manager_id == $user->id)
            return true;

        $manager = \Tobuli\Entities\User::find($manager_id);

        if ( ! $manager)
            return false;

        if ( ! $manager->manager_id)
            return false;

        // Managers infinity loop
        if (in_array($manager->id, $managers))
            return true;

        $managers[] = $manager->id;

        return $this->managerInfinity($user, $manager->manager_id, $managers);
    }
}
