<?php

class LitbangUserController extends BaseController
{
    public function index() {
        $this->checkScopes(['litbang.access']);
        $params = $this->request->get();

        $params['username'] = array_key_exists('nama', $params) ? $params['nama'] : '';
        $params['satgas_id'] = array_key_exists('satgas_id', $params) ? $params['satgas_id'] : -1;
        $params['trashed'] = array_key_exists('trashed', $params) ? $params['trashed'] : ($this->hasOneOfScopes(['litbang.manage_user']));
        $params['limit'] = $this->getDefaultLimit($params);
        $params['offset'] = array_key_exists('offset', $params) ? $params['offset'] : 0;
        $params['order_by'] = array_key_exists('order_by', $params) ? strtolower($params['order_by']) : 'id';
        $params['order_direction'] = array_key_exists('order_direction', $params) ? strtolower($params['order_direction']) : 'asc';

        $allowed_order = [
            'id',
            'um_user_name',
            'nama',
            'email',
            'satgas_nama',
            'created_at',
            'updated_at'
        ];
        $allowed_order_direction = ['asc', 'desc'];
        $bind_order = [
            'id' => 'LitbangUser.id',
            'um_user_name' => 'UmUser.um_user_name',
            'nama' => 'UmUser.nama',
            'email' => 'UmUser.email',
            'satgas_nama' => 'LitbangSatgas.nama',
            'created_at' => 'LitbangUser.created_at',
            'updated_at' => 'LitbangUser.updated_at'
        ];

        $this->validate(
            $params,
            [
                'username' => ['validators' => []],
                'satgas_id' => ['validators' => ['digit']],
                'trashed' => ['validators' => []],
                'limit' => ['validators' => ['digit']],
                'offset' => ['validators' => ['digit']],
                'order_by' => ['validators' =>  [[
                    'name' => 'inclusion_in',
                    'params' => $allowed_order
                ]]],
                'order_direction' => ['validators' =>  [[
                    'name' => 'inclusion_in',
                    'params' => $allowed_order_direction
                ]]]
            ]
        );

        if ($this->hasOneOfRoles(['anggota_litbang', 'kasatgas_litbang'])) {
            /** @var $litbangUser LitbangUser */
            $litbangUser = LitbangUser::findOrFail("um_id = " . $this->userID);
            $params['satgas_id'] = $litbangUser->satgas_id;
        }

        $result = LitbangUser::query()
            ->columns("LitbangUser.id, LitbangUser.um_id, LitbangUser.created_at, LitbangUser.updated_at, LitbangUser.deleted_at, UmUser.um_user_name, UmUser.nama, UmUser.email, UmUser.phone, LitbangUser.satgas_id, LitbangSatgas.nama as satgas_nama")
            ->leftJoin('LitbangSatgas', 'LitbangUser.satgas_id = LitbangSatgas.id')
            ->leftJoin('UmUser', 'LitbangUser.um_id = UmUser.um_id')
            ->where(
                "UmUser.um_user_name ILIKE :username: AND UmUser.is_active = true 
                AND (0 > :satgas_id: OR LitbangSatgas.id = :satgas_id:)" .
                ($params['trashed'] ? '' : ' AND LitbangUser.deleted_at IS NULL')
            )
            ->bind([
                "username" => '%' . $params['username'] . '%',
                "satgas_id" => $params['satgas_id']
            ])
            ->orderBy($bind_order[$params['order_by']] . " " . $params['order_direction'])
            ->limit($params['limit'], $params['offset'])
            ->execute()
            ->toArray();
        $total = LitbangUser::query()
            ->columns("LitbangUser.id, LitbangUser.um_id, LitbangUser.created_at, LitbangUser.updated_at, LitbangUser.deleted_at, UmUser.um_user_name, UmUser.nama, UmUser.email, UmUser.phone, LitbangUser.satgas_id, LitbangSatgas.nama as satgas_nama")
            ->leftJoin('LitbangSatgas', 'LitbangUser.satgas_id = LitbangSatgas.id')
            ->leftJoin('UmUser', 'LitbangUser.um_id = UmUser.um_id')
            ->where(
                "UmUser.um_user_name ILIKE :username: AND UmUser.is_active = true 
                AND (0 > :satgas_id: OR LitbangSatgas.id = :satgas_id:)" .
                ($params['trashed'] ? '' : ' AND LitbangUser.deleted_at IS NULL')
            )
            ->bind([
                "username" => '%' . $params['username'] . '%',
                "satgas_id" => $params['satgas_id']
            ])
            ->execute()
            ->count();

        return new PaginatedResponse($result, $total, $params['limit'], $params['offset']);
    }

    public function create() {
        $this->checkScopes(['litbang.manage_user']);
        $user = new UmUser();

        $rawBody = $this->request->getJsonRawBody(true);
        $params = $this->validateStoreRequest($rawBody, $user);
        $umUser = $this->saveUser($user, $params);

        $litbangUser = new LitbangUser();
        $litbangUser->um_id = $umUser['um_id'];
        $litbangUser->satgas_id = (!empty($params['satgas_id']) ? $params['satgas_id'] : null);
        $litbangUser->save();

        return new SimpleResponse($umUser);
    }

    public function update($umId) {
        $um_id = $this->shared->user->um_id;
        // only admin or the user itself can update
        if($this->hasOneOfScopes(['litbang.manage_user']) || ($this->hasOneOfScopes('litbang.access') && $umId == $um_id)) {
            $this->db->begin();
            try {
                /** @var $user UmUser */
                $user = UmUser::findOrFail($umId);
                $rawBody = $this->request->getJsonRawBody(true);
                $params = $this->validateStoreRequest($rawBody, $user);
                $oldData = LogModeration::extractUmUser($umId);
                $umUser = $this->saveUser($user, $params);

                $litbangUser = LitbangUser::findOrFail("um_id = " . $umId);
                $litbangUser->satgas_id = (!empty($params['satgas_id']) ? $params['satgas_id'] : null);
                $litbangUser->updated_at = date('Y-m-d H:i:s');
                if (!$litbangUser->save()) {
                    $errors = $litbangUser->getMessages();
                    $error_messages = [];

                    foreach ($errors as $key => $error) {
                        $error_messages[] = ["field" => $error->getField(), "message" => $error->getMessage()];
                    }

                    throw new ValidationException($error_messages);

                }

                $newData = LogModeration::extractUmUser($umId);
                $action = [LogModeration::ACTION_UPDATE_UMUSER];
                if (array_key_exists('password', $params)) array_push($action, LogModeration::ACTION_CHANGEPWD_UMUSER);
                LogModeration::loggedUmUser(
                    $action,
                    LogModeration::ACTIONMENU_ADMINISTRASI_MONITORING,
                    $umId,
                    $oldData,
                    $newData,
                    $this->shared->user->um_id
                );

                $this->db->commit();
            }
            catch (\Exception $e) {
                $this->db->rollback();
                throw $e;
            }

            return new SimpleResponse($umUser);
        }
        else  {
            throw new CustomErrorException(
                BaseResponse::INVALID_ACTION,
                'User not allowed to perform this action'
            );
        }
    }

    public function detail($umId) {
        $this->checkScopes(['litbang.access']);
        /** @var $record LitbangUser */
        $record = LitbangUser::findOrFail("um_id = " . $umId);

        return $record->getDetail(null, true);
    }

    public function delete($id) {
        $this->checkScopes(['litbang.manage_user']);
        /** @var $umUser UmUser */
        $umUser = UmUser::findOrFail($id);
        $this->checkNotRelatedModuleUser($umUser);

        foreach (LitbangUser::PROTECTED_ROLES as $key => $role) {
            if ($umUser->hasRole($role)) {
                throw new CustomErrorException(
                    BaseResponse::INVALID_ACTION,
                    'User tersebut tidak dapat dinon-aktifkan'
                );
            }
        }

        if ($umUser->is_banned) {
            throw new CustomErrorException(BaseResponse::INVALID_ACTION, 'User tersebut telah dinon-aktifkan');
        }

        $this->db->begin();
        try{
            $oldData = LogModeration::extractUmUser($id);
            $umUser->is_banned = true;
            $saved = $umUser->save();

            if (!$saved) {
                $errors = $umUser->getMessages();
                $error_messages = [];

                foreach ($errors as $key => $error) {
                    $error_messages[] = ["field" => $error->getField(), "message" => $error->getMessage()];
                }

                throw new ValidationException($error_messages);
            }

            /** @var $litbangUser LitbangUser */
            $litbangUser = LitbangUser::findOrFail("um_id = " . $umUser->um_id);
            $litbangUser->deleted_at = date('Y-m-d H:i:s');
            $saved = $litbangUser->save();

            if (!$saved) {
                $errors = $litbangUser->getMessages();
                $error_messages = [];

                foreach ($errors as $key => $error) {
                    $error_messages[] = ["field" => $error->getField(), "message" => $error->getMessage()];
                }

                throw new ValidationException($error_messages);
            }

            $newData = LogModeration::extractUmUser($id);
            LogModeration::loggedUmUser(
                [LogModeration::ACTION_BAN_UMUSER],
                LogModeration::ACTIONMENU_ADMINISTRASI_MONITORING,
                $id,
                $oldData,
                $newData,
                $this->shared->user->um_id
            );

            $this->db->commit();

            return new SimpleResponse($umUser->toArraySimple());
        } catch(\Exception $e) {
            $this->db->rollback();
            throw $e;
        }
    }

    public function restore($id) {
        $this->checkScopes(['litbang.manage_user']);
        /** @var $umUser UmUser */
        $umUser = UmUser::findOrFail($id);
        $this->checkNotRelatedModuleUser($umUser);

        if (!$umUser->is_banned) {
            throw new CustomErrorException(BaseResponse::INVALID_ACTION, 'User tersebut belum dinon-aktifkan');
        }

        $this->db->begin();
        try{
            $oldData = LogModeration::extractUmUser($id);
            $umUser->is_banned = false;
            $saved = $umUser->save();

            if (!$saved) {
                $errors = $umUser->getMessages();
                $error_messages = [];

                foreach ($errors as $key => $error) {
                    $error_messages[] = ["field" => $error->getField(), "message" => $error->getMessage()];
                }

                throw new ValidationException($error_messages);
            }

            /** @var $litbangUser LitbangUser */
            $litbangUser = LitbangUser::findOrFail("um_id = " . $umUser->um_id);
            $litbangUser->deleted_at = null;
            $saved = $litbangUser->save();

            if (!$saved) {
                $errors = $litbangUser->getMessages();
                $error_messages = [];

                foreach ($errors as $key => $error) {
                    $error_messages[] = ["field" => $error->getField(), "message" => $error->getMessage()];
                }

                throw new ValidationException($error_messages);
            }

            $newData = LogModeration::extractUmUser($id);
            LogModeration::loggedUmUser(
                [LogModeration::ACTION_UNBAN_UMUSER],
                LogModeration::ACTIONMENU_ADMINISTRASI_MONITORING,
                $id,
                $oldData,
                $newData,
                $this->shared->user->um_id
            );

            $this->db->commit();

            return new SimpleResponse($umUser->toArraySimple());
        } catch(\Exception $e) {
            $this->db->rollback();
            throw $e;
        }
    }

    public function roles()
    {
        $this->checkScopes(['litbang.manage_user']);

        $conditions = 'name in ({names:array})';
        $bind = ['names' => $this->getAllowedRolesToAccess()];

        $rows = Roles::find([
            'conditions' => $conditions,
            'bind'  => $bind
        ]);

        return new CollectionPaginatedResponse($rows);
    }

    protected function validateStoreRequest($request, $user)
    {
        $allowed_gender = ['L','P','T'];

        $rules = [
            'username' => ['validators' => [
                'required',
                'alpha_dash',
                [
                    'name' => 'min_string',
                    'params' => 6
                ]
            ]],
            'email' => ['validators' => ['required', 'email']],
            'nama' => ['validators' => ['required']],
            'foto' => ['validators' => []],
            'uuid_user' => ['validators' => []],
            'roles' => ['validators' =>  ['required']],
            'phone' => ['validators' => ['required', 'digit']],
            'jenis_kelamin' => ['validators' =>  [[
                'name' => 'inclusion_in',
                'params' => $allowed_gender
            ]]],
        ];

        // if new record, must have a password
        if($user->um_id == null) {
            $rules['password'] = ['validators' => [
                'required',
                'confirm_password',
                [
                    'name' => 'min_string',
                    'params' => 8
                ]
            ]];
            $rules['confirm_password'] = ['validators' => ['required']];
        }
        elseif (array_key_exists('password', $request) ) {
            $rules['password'] = ['validators' => [
                'confirm_password',
                [
                    'name' => 'min_string',
                    'params' => 8
                ]
            ]];
        }

        // validate roles input format
        if (
            array_key_exists('roles', $request)
            && (!is_array($request['roles'])
                || empty($request['roles']))
        ) {
            throw new ValidationException([
                'field' => 'roles',
                'message' => 'Roles tidak boleh kosong. Format roles salah'
            ]);
        } elseif (array_key_exists('roles', $request)) {
            $roles = $request['roles'];
            $allowed_roles = $this->getAllowedRolesToAccess();
            $roles_error_message = 'must be one of ['.implode(',', $allowed_roles).']';
            foreach ($roles as $key => $value) {
                if(!in_array($value, $allowed_roles)) {
                    throw new ValidationException([
                        'field' => 'roles',
                        'message' => $roles_error_message
                    ]);
                }
            }
        }

        // if anggota_litbang/kasatgas_litbang satgas cannot be blank
        if (
            array_key_exists('roles', $request) &&
            (in_array('anggota_litbang', $request['roles']) || in_array('kasatgas_litbang', $request['roles']))
        ) {
            $rules['satgas_id'] = ['validators' => ['required']];
        }

        // check if satgas exist
        if(array_key_exists('satgas_id', $request)) {
            LitbangSatgas::findOrFail($request['satgas_id']);
        }

        $params = $this->filterExpectedRequest($request, $rules);

        \UserHelper::validateUsernameAvailability($params['username'], $user->um_user_name);
        \UserHelper::validateEmailAvailability($params['email'], $user->email);
        \UserHelper::validatePhoneAvailability($params['phone'], $user->phone);

        return $params;
    }

    protected function getAllowedRolesToAccess()
    {
        if($this->hasRole('admin_litbang')) {
            return [
                'admin_litbang',
                'anggota_litbang',
                'kasatgas_litbang',
                'direktur_litbang',
            ];
        }
    }

    /**
     * @param $user UmUser
     * @param $params
     * @return mixed
     */
    protected function saveUser($user, $params)
    {
        $roles = $params['roles'];
        unset($params['roles']);

        $user->assign($params);
        $user->um_user_name = $params['username'];
        $user->um_tenant_id = -1234;
        $user->um_changed_time = date("Y-m-d H:i:s");

        if(array_key_exists('password', $params)) {
            $hashed_password = base64_encode(hash("sha256", $params['password'], true));
            $user->um_user_new_password = $this->security->hash($params['password']);
            $user->um_user_password = $hashed_password;
        }

        $user->save();

        $users_roles = Roles::find([
            'conditions' => 'name in ({roles:array})',
            'columns' => 'id',
            'bind' => [
                'roles' => $roles
            ]
        ])->toArray();

        $user_roles_ids = [];
        foreach ($users_roles as $key => $role) {
            array_push($user_roles_ids, $role['id']);
        }

        $user->syncRoles($user_roles_ids);

        return $this->getUserInfo($user->um_id);
    }

    protected function getUserInfo($userId)
    {
        $user = UmUser::findOrFail($userId);

        $columns = [
            'um_id',
            'um_user_name',
            'email',
            'nama',
            'tempat_lahir',
            'foto',
            'presigned_url',
            'presigned_exp',
            'phone',
            'uuid_user',
            'tgl_lahir',
            'is_banned',
            'jenis_kelamin',
            'id_instansi'
        ];

        $result = $user->toArray($columns);
        $result["profile_picture_url"] = MediaHelper::getPresignedUrlFoto($result, UmUser::class, $this->config, $this->minio);

        return $result;
    }

    protected function checkNotRelatedModuleUser($user)
    {
        $user_roles = $user->roles;
        $user_roles_ids = [];
        foreach ($user_roles as $key => $role) {
            $user_roles_ids[] = $role->id;
        }

        $allowed_roles_ids = $this->getAllowedRolesIdToAccess();

        $intersect = array_intersect($user_roles_ids, $allowed_roles_ids);

        if (empty($intersect)) {
            throw new CustomErrorException(
                BaseResponse::INVALID_ACTION,
                'User tidak dapat menon-aktifkan user yang dipilih'
            );
        }
    }

    protected function getAllowedRolesIdToAccess()
    {
        $rolesName = $this->getAllowedRolesToAccess();

        $roles = Roles::find([
            'columns' => 'id',
            'conditions' => 'name in ({names:array})',
            'bind'  => [
                'names' => $rolesName
            ]
        ]);

        $result = [];
        foreach ($roles as $key => $role) {
            $result[] = $role->id;
        }

        if (empty($result)) {
            throw new CustomErrorException(
                BaseResponse::INVALID_REQUEST,
                "Role terkait tidak ditemukan"
            );
        }

        return $result;
    }
}
