<?php

class PancekManagementUserController extends BaseController
{
    public function list()
    {
        $this->checkOneOfScopes(['pancek.admin', 'admin.view']);

        $params = $this->request->get();

        $params['id_role'] = array_key_exists('id_role', $params) ? $params['id_role'] : null;
        $params['id_instansi'] = array_key_exists('id_instansi', $params) ? $params['id_instansi'] : null;
        $params['keyword'] = array_key_exists('keyword', $params) ? $params['keyword'] : '';
        $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']) : 'um_id';
        $params['order_direction'] = array_key_exists('order_direction', $params) ? strtolower($params['order_direction']) : 'asc';
        $params['is_active'] = array_key_exists('is_active', $params) ? $params['is_active'] : 0;

        $allowed_order = [
            'um_id',
            'um_user_name',
            'nama',
            'instansi_name'
        ];
        $allowed_order_direction = ['asc', 'desc'];
        $bind_order = [
            'um_id' => 'uu.um_id',
            'um_user_name' => 'uu.um_user_name',
            'nama' => 'uu.nama',
            'instansi_name' => 'i.nama'
        ];

        $validatedParams = $this->validate(
            $params,
            [
                'id_role' => ['validators' => ['digit']],
                'id_instansi' => ['validators' => ['digit']],
                'keyword' => ['validators' => []],
                'is_active' => ['validators' => ['digit']],
                '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
                ]]]
            ]
        );

        $conditions = [];
        $bind = [];

        $conditions[] = '(uu.um_user_name ILIKE :keyword OR uu.nama ILIKE :keyword OR uu.email ILIKE :keyword)';
        $bind['keyword'] = '%' . $validatedParams['keyword'] . '%';

        if ($validatedParams['id_role'] != null) {
            $conditions[] = 'r.id = :id_role';
            $bind['id_role'] = $validatedParams['id_role'];
        }

        if ($validatedParams['id_instansi'] != null) {
            $conditions[] = 'uu.id_instansi = :id_instansi';
            $bind['id_instansi'] = $validatedParams['id_instansi'];
        }

        $isActiveParam = intval($validatedParams['is_active']);
        switch ($isActiveParam) {
            case(1):
                $conditions[] = '(pua.end_date >= now() OR pua.end_date IS NULL OR pua.deleted_at IS NOT NULL)';
                break;
            case(2):
                $conditions[] = '(pua.end_date < now() AND pua.deleted_at IS NULL)';
                break;
            default:
                break;
        }

        $where = 'WHERE ' . implode(' AND ', $conditions);
        $orderBy = $bind_order[$params['order_by']] . ' ' . $params['order_direction'];
        $limit = $params['limit'];
        $offset = $params['offset'];

        $q = "
            WITH rolePancekCTE AS (
            	SELECT * FROM jaga.roles WHERE name IN ('verifikator_pancek', 'observer_pancek', 'admin_kppbc_pancek', 'verifikator_kppbc_pancek', 'assignor_kppbc_pancek', 'verifikator_bk_pancek', 'assignor_bk_pancek') AND deleted_at IS NULL
            ), 
            
            roleAdminCTE AS (
            	SELECT * FROM jaga.roles WHERE name IN ('admin_pancek', 'admin_sistem') AND deleted_at IS NULL
            ),
            
            userRoleCTE AS (
            	SELECT * FROM jaga.users_roles ur JOIN rolePancekCTE rc ON ur.role_id = rc.id 
            ),
            
            finalUserRoleCTE AS (
            	SELECT ur.*
            	FROM userRoleCTE ur
            	WHERE ur.user_id NOT IN (
                SELECT ur2.user_id
                FROM jaga.users_roles ur2
                JOIN roleAdminCTE rc2 ON ur2.role_id = rc2.id
            	)
            )
            
            SELECT 
            	uu.um_id,
            	uu.um_user_name,
            	uu.nama,
            	uu.email,
            	uu.id_instansi,
            	i.nama AS instansi_name,
            	STRING_AGG(CAST(r.id AS TEXT), '|') AS role_ids,
            	STRING_AGG(r.name, '|') AS role_names,
            	STRING_AGG(r.label, '|') AS role_labels,
            	pua.start_date,
                pua.end_date
            FROM jaga.um_user uu
            LEFT JOIN jaga.instansi i ON uu.id_instansi = i.id
            LEFT JOIN jaga.users_roles ur ON uu.um_id = ur.user_id
            LEFT JOIN rolePancekCTE r ON ur.role_id = r.id
            JOIN finalUserRoleCTE urc ON uu.um_id = urc.user_id
            LEFT JOIN jaga.pancek_user_access pua ON pua.user_id = urc.user_id AND pua.role_id = urc.role_id
            $where
            GROUP BY
            	uu.um_id,
            	uu.um_user_name,
            	uu.nama,
            	uu.email,
            	uu.id_instansi,
            	i.nama,
            	pua.id 
            ORDER BY $orderBy
            LIMIT $limit OFFSET $offset;
        ";

        $qCount = "
            WITH rolePancekCTE AS (
            	SELECT * FROM jaga.roles WHERE name IN ('verifikator_pancek', 'observer_pancek', 'admin_kppbc_pancek', 'verifikator_kppbc_pancek', 'assignor_kppbc_pancek', 'verifikator_bk_pancek', 'assignor_bk_pancek') AND deleted_at IS NULL
            ), 
            
            roleAdminCTE AS (
            	SELECT * FROM jaga.roles WHERE name IN ('admin_pancek', 'admin_sistem') AND deleted_at IS NULL
            ),
            
            userRoleCTE AS (
            	SELECT * FROM jaga.users_roles ur JOIN rolePancekCTE rc ON ur.role_id = rc.id 
            ),
            
            finalUserRoleCTE AS (
            	SELECT ur.*
            	FROM userRoleCTE ur
            	WHERE ur.user_id NOT IN (
                SELECT ur2.user_id
                FROM jaga.users_roles ur2
                JOIN roleAdminCTE rc2 ON ur2.role_id = rc2.id
            	)
            )
            
            SELECT 
            	COUNT(DISTINCT uu.um_id) AS total
            FROM jaga.um_user uu
            LEFT JOIN jaga.instansi i ON uu.id_instansi = i.id
            LEFT JOIN jaga.users_roles ur ON uu.um_id = ur.user_id
            LEFT JOIN rolePancekCTE r ON ur.role_id = r.id
            JOIN finalUserRoleCTE urc ON uu.um_id = urc.user_id
            LEFT JOIN jaga.pancek_user_access pua ON pua.user_id = urc.user_id AND pua.role_id = urc.role_id
            $where
        ";

        $data = $this->rawQuery($q, $bind);
        $result = array_map(function ($row) {
            $roleIds = explode('|', $row['role_ids']);
            $roleNames = explode('|', $row['role_names']);
            $roleLabels = explode('|', $row['role_labels']);

            $roles = array_map(function ($id, $name, $label) {
                return ['id' => $id, 'name' => $name, 'label' => $label];
            }, $roleIds, $roleNames, $roleLabels);

            $isActive = true;
            if (!empty($row['start_date']) && !empty($row['end_date'])) {
                $time = strtotime($row['end_date']);
                $endDate = date('Y-m-d', $time);
                $isActive = $endDate >= date('Y-m-d');
            }

            return [
                'um_id' => $row['um_id'],
                'um_user_name' => $row['um_user_name'],
                'nama' => $row['nama'],
                'email' => $row['email'],
                'id_instansi' => $row['id_instansi'],
                'instansi_name' => $row['instansi_name'],
                'roles' => $roles,
                'is_active' => $isActive,
                'start_date' => $row['start_date'],
                'end_date' => $row['end_date'],
            ];
        }, $data);


        $total = $this->rawQuery($qCount,
            $bind
        )[0]['total'];

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

    public function detail($id)
    {
        $this->checkOneOfScopes(['pancek.admin', 'admin.view']);

        $data = UmUser::findFirst($id);

        if (!$data) {
            throw new CustomErrorException(BaseResponse::DATA_NOT_FOUND, "Data KPPBC tidak ditemukan");
        }
        $result = $data->toArray(['um_id', 'um_user_name', 'nama', 'email', 'id_instansi']);
        $result['instansi'] = $data->getInstansi();

        $result['roles'] = UsersRoles::query()
            ->columns("
                Roles.id,
                Roles.name,
                Roles.label
            ")
            ->leftJoin("Roles", "UsersRoles.role_id = Roles.id")
            ->where("UsersRoles.user_id = :user_id: AND Roles.name IN ('verifikator_pancek', 'observer_pancek', 'admin_kppbc_pancek', 'verifikator_kppbc_pancek', 'assignor_kppbc_pancek', 'verifikator_bk_pancek', 'assignor_bk_pancek') AND UsersRoles.deleted_at IS NULL AND Roles.deleted_at IS NULL")
            ->bind(['user_id' => $id])
            ->execute();

        return new SimpleResponse($result);
    }

    public function create()
    {
        $this->checkOneOfScopes(['pancek.admin', 'admin.view']);

        $rawBody = $this->request->getPost();

        $validatedRequest = $this->validate(
            $rawBody,
            [
                'username' => ['validators' => ['required']],
                'email' => ['validators' => ['required', 'email']],
                'password' => ['validators' => []],
                'h_password' => ['validators' => []],
                'nama' => ['validators' => ['required']],
                'phone' => ['validators' => ['required', 'digit']],
                'jenis_kelamin' => ['validators' => []],
                'id_instansi' => ['validators' => []]
            ]
        );

        $pass = AuthHelper::decryptPassword($validatedRequest); //um_user_password & um_user_new_password (bcrypt)
        AuthHelper::validatePassword($pass);
        $nama = $rawBody['nama']; //nama
        $tmpLahir = '-';
        $email = $rawBody['email']; //um_user_name & email
        $phone = $rawBody['phone']; //phone
        $jk = $validatedRequest['jenis_kelamin'];
        $wso2Id = '';
        $oauthId = '';
        $twitterId = '';
        $facebookId = '';
        $googleId = '';
        $noKtp = '-'; //no_ktp_sim
        $hashed_password = base64_encode(hash("sha256", $pass, true));
        $foto = array_key_exists('image', $rawBody) ? $rawBody['image'] : null;
        $rawRoles = array_key_exists('roles', $rawBody) ? $rawBody['roles'] : '';
        $roles = explode(',', $rawRoles);

        //Cek User and phone
        $errMsgs = [];
        if ($this->email_exists($email)) {
            $errMsgs[0] = array(
                'field' => 'email',
                'message' => 'Email has been used.'
            );
        }

        if ($this->phone_exists($phone)) {
            $errMsgs[count($errMsgs)] = array(
                'field' => 'phone',
                'message' => 'Phone has been used.'
            );
        }

        if (empty($roles)) {
            $errMsgs[count($errMsgs)] = array(
                'field' => 'roles',
                'message' => 'Roles must present'
            );
        }

        $allowedRoleIds = $this->getAllowedRoleIds();

        $isRoleAllowed = true;
        foreach ($roles as $role) {
            if (!in_array($role, $allowedRoleIds)) {
                $isRoleAllowed = false;
                break;
            }
        }
        if (!$isRoleAllowed) {
            $errMsgs[] = array(
                'field' => 'roles',
                'message' => 'Only Pancek Roles are allowed'
            );
        }

        // validasi ketersediaan username
        $validateUsername = UserHelper::validateUsername($rawBody["username"]);
        if ($validateUsername == 1) {
            $errMsgs[] = [
                'field' => 'username',
                'message' => 'Username length must be more than 6 characters.'
            ];
        }

        if ($validateUsername == 2) {
            $errMsgs[] = [
                'field' => 'username',
                'message' => 'Username must be consist of Alphanumeric, underscore, dash or dot only.'
            ];
        }

        if ($validateUsername == 3) {
            $errMsgs[] = [
                'field' => 'username',
                'message' => 'Username has been used.'
            ];
        }

        if(!empty($validatedRequest['id_instansi'])) {
            $allowedInstansiIds = $this->getAllowedInstansiIds();

            if(!in_array($validatedRequest['id_instansi'], $allowedInstansiIds)) {
                $errMsgs[] = array(
                    'field' => 'instansi',
                    'message' => 'Only Instansi for Pancek are allowed'
                );
            }
        }

        if (count($errMsgs) > 0) {
            throw new CustomErrorException(BaseResponse::REGISTRATION_ERROR, "Registrasi User Gagal", $errMsgs);
        }

        $this->checkUsername($rawBody["username"]);

        try {
            $this->db->begin();
            $foto = $this->uploadProfilePicture($this->request);

            $roles[] = Roles::getRoleIdPengguna();

            $user = new UmUser();
            $user->um_changed_time = date("Y-m-d H:i:s");
            $user->um_tenant_id = -1234;
            $user->um_user_new_password = $this->security->hash($pass);
            $user->um_user_password = $hashed_password;
            $user->nama = $nama;
            $user->tempat_lahir = $tmpLahir;
            $user->email = $email;
            $user->um_user_name = $rawBody["username"];
            $user->foto = $foto;
            $user->phone = $phone;
            $user->wso2_id = $wso2Id;
            $user->oauth_id = $oauthId;
            $user->twitter_id = $twitterId;
            $user->facebook_id = $facebookId;
            $user->google_id = $googleId;
            $user->no_ktp_sim = $noKtp;
            $user->jenis_kelamin = $jk;
            if(!empty($validatedRequest['id_instansi']) && $validatedRequest['id_instansi'] != null) $user->id_instansi = $validatedRequest['id_instansi'];
            $user->id_ins = $jk;
            $user->save();

            $new_user = UmUser::findFirstByEmail($email);

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

            $new_user->setRoles2($roles);

            PancekUserAccess::insertForRoleObserver($user->um_id, $roles);

            $this->db->commit();

            $data = new stdClass();
            $data->message = 'Success';
            return new ObjectResponse([
                "success" => true,
                "message" => "Berhasil mendaftarkan user"
            ]);
        } catch (\Exception $e) {
            $this->db->rollback();
            throw $e;
        }
    }

    private function email_exists($email)
    {
        $result = UmUser::query()
            ->where('lower(email) = :email: and is_active = true')
            ->bind(['email' => strtolower($email)])
            ->limit(1)
            ->execute();
        return $result->count() == 1;
    }

    private function phone_exists($phone)
    {
        $result = UmUser::query()
            ->where('phone = :phone: and is_active = true')
            ->bind(['phone' => $phone])
            ->limit(1)
            ->execute();
        return $result->count() == 1;
    }

    private function checkUsername($value)
    {
        $blacklistedUsername = [
            'JAGA',
            'JAGA.ID',
            'JAGA-ID',
            'PENJAGA',
            'PEN-JAGA',
            'PEN.JAGA',
            'ADMINJAGA',
            'ADMIN-JAGA ',
            'ADMIN.JAGA',
            'WWW.JAGA.ID',
            'WWW-JAGA-ID',
            'WWWJAGAID'
        ];

        foreach ($blacklistedUsername as $username) {
            if (strtolower(trim($username)) == strtolower(trim($value))) {
                throw new CustomErrorException(BaseResponse::INVALID_REQUEST, 'Invalid username');
            }
        }
    }

    private function uploadProfilePicture($request): string
    {
        $fotoFilename = '';

        if ($request->hasFiles()) {
            $uploadedFiles = $request->getUploadedFiles();
            UserHelper::validateProfilePicture($uploadedFiles);

            foreach ($uploadedFiles as $file) {
                if (substr($file->getRealType(), 0, 5) !== "image") {
                    throw new CustomErrorException(BaseResponse::INVALID_REQUEST, "Registrasi User Gagal", [[
                        "field" => "image",
                        "filename" => $file->getName(),
                        "message" => "Tipe file tidak diizinkan"
                    ]]);
                }
            }

            $random = new \Phalcon\Security\Random();

            if (getenv("FILESYSTEM_CLOUD") == FileSystemCloud::LOCAL) {
                foreach ($uploadedFiles as $file) {
                    $filename = getenv("USER_IMAGE_FOLDER");
                    if (!file_exists($filename) && !is_dir($filename)) {
                        mkdir($filename, 0770, true);
                    }

                    $extension = pathinfo($file->getName(), PATHINFO_EXTENSION);
                    if (empty($extension)) {
                        $extension = "." . $extension;
                    } else {
                        $extension = "";
                    }
                    $shortName = $random->uuid() . $extension;
                    $fotoFilename = $shortName;
                    $filename .=  $shortName;
                    $file->moveTo($filename);
                }
            } elseif (getenv("FILESYSTEM_CLOUD") == FileSystemCloud::MINIO) {
                foreach ($uploadedFiles as $file) {
                    $extension = pathinfo($file->getName(), PATHINFO_EXTENSION);
                    if (empty($extension)) {
                        $extension = "." . $extension;
                    } else {
                        $extension = "";
                    }
                    $shortName = $random->uuid() . $extension;

                    /** @var MinioService $minioService */
                    $minioService = $this->minio;
                    if ($minioService->isSecondaryS3Available()) {
                        $minioService->baseUploadUsingSecondaryS3(
                            $file->getTempName(),
                            $file->getRealType(),
                            false,
                            'users_profpic' . DIRECTORY_SEPARATOR . $shortName
                        );
                    } else {
                        $minioService->base_upload_attachment(
                            $file->getTempName(),
                            $file->getRealType(),
                            false,
                            'users_profpic' . DIRECTORY_SEPARATOR . $shortName
                        );
                    }

                    $fotoFilename = $shortName;
                }
            }
        }

        return $fotoFilename;
    }

    public function update()
    {
        $this->checkOneOfScopes(['pancek.admin', 'admin.view']);

        $rawBody = $this->request->getPost();

        $validatedRequest = $this->validate(
            $rawBody,
            [
                'id' => ['validators' => ['required']],
                'nama' => ['validators' => ['required']],
                'email' => ['validators' => ['required', 'email']],
                'password' => ['validators' => []],
                'h_password' => ['validators' => []],
                'id_instansi' => ['validators' => []],
                'roles' => ['validators' => ['required']],
                'notify' => ['validators' => []],
            ]
        );

        try {
            $this->db->begin();
            $has_password = !empty($validatedRequest['password']) || !empty($validatedRequest['h_password']);
            $has_id_instansi = !empty($validatedRequest['id_instansi']);

            if ($has_password) {
                $pass = AuthHelper::decryptPassword($validatedRequest);
                AuthHelper::validatePassword($pass);
            }

            if ($has_id_instansi) {
                $instansi = Instansi::findFirst($validatedRequest['id_instansi']);

                if (empty($instansi)) {
                    throw new CustomErrorException(BaseResponse::INVALID_REQUEST, "Instansi tidak ditemukan");
                }
            }

            $user = UmUser::findFirstByUmId($validatedRequest['id']);

            if (empty($user)) {
                throw new CustomErrorException(BaseResponse::INVALID_REQUEST, "User tidak ditemukan");
            }
            $oldData = LogModeration::extractUmUser($validatedRequest['id']);

            $isUserDataChanged = (
                $user->nama != $validatedRequest['nama']
                || $user->email != strtolower(trim($validatedRequest['email']))
                || $user->id_instansi != $validatedRequest['id_instansi']
            );

            $allowedRoleIds = $this->getAllowedRoleIds();
            foreach ($validatedRequest['roles'] as $role) {
                if (!in_array($role, $allowedRoleIds)) {
                    throw new CustomErrorException(BaseResponse::INVALID_REQUEST, "Invalid Role");
                }
            }

            $userRoles = $user->roles;
            $currentAllowedRoleIds = [];
            foreach ($userRoles as $userRole) {
                if(in_array($userRole->id, $allowedRoleIds)) {
                    $currentAllowedRoleIds[] = $userRole->id;
                }
            }
            $hasDifferentRole = false;
            foreach ($validatedRequest['roles'] as $roleId) {
                if (!in_array($roleId, $currentAllowedRoleIds)) {
                    $hasDifferentRole = true;
                }
            }

            if (!$isUserDataChanged && $hasDifferentRole) {
                $isUserDataChanged = true;
            }

            $email = strtolower(trim($validatedRequest['email']));

            // Check email availability
            if (strtolower(trim($user->email)) != $email) {
                $duplicate_users = UmUser::find([
                    'conditions' => 'email ilike :email:',
                    'bind' => [
                        'email' => $email
                    ]
                ]);

                if (count($duplicate_users) > 0) {
                    throw new CustomErrorException(BaseResponse::INVALID_REQUEST, "User dengan email $email telah terdaftar");
                }
            }

            $user->email = $email;
            $user->nama = $validatedRequest['nama'];
            if ($has_password) {
                $user->um_user_new_password = $this->security->hash($pass);
            }
            if ($has_id_instansi) {
                $user->id_instansi = $validatedRequest['id_instansi'];
            }
            $user->migration_status = UmUser::MIGRATION_STATUS_PASS_CHANGED;
            $status = $user->save();

            if($hasDifferentRole) {
                $this->resetPancekRoles($validatedRequest, $allowedRoleIds);
            }

            if (!$status) {
                $errors = $user->getMessages();
                $error_messages = [];

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

                throw new ValidationException($error_messages);
            } else {
                if(array_key_exists('notify', $validatedRequest)) {
                    if (!empty($validatedRequest['notify'])) {
                        $subject = "";
                        $message = "";

                        if($isUserDataChanged && $has_password) {
                            $subject = "[JAGA] Perubahan Data dan Password Akun JAGA";
                            $message = "Hi Sahabat JAGA, terima kasih telah menggunakan Aplikasi JAGA."
                                . "\n\nKamu menerima email ini karena adanya perubahan data pada akun JAGA kamu. Berikut informasi perubahan data pada Akun kamu :"
                                . "\n\n-nama: " . $user->nama
                                . "\n-tanggal lahir: " . $user->tgl_lahir
                                . "\n-no tlp: " . $user->phone
                                . "\n-email: " . $user->email
                                . "\n\ndan melakukan permintaan perubahan password pada akun JAGA kamu. Berikut akses yang dapat digunakan untuk login ke Aplikasi JAGA:"
                                . "\n\n-Username: " . $user->um_user_name
                                . "\n-Password: " . $pass
                                . "\n\natau klik link berikut " . $this->config["web_base_url"]
                                . "\n\nJika ada ketidaksesuaian pada data di atas, kamu dapat mengubah data kamu pada halaman profil."
                                . "\nJangan khawatir, semua data kamu aman di JAGA."
                                . "\nAkses JAGA dan bantu kami untuk Cegah Korupsi dari Ujung Jari."
                                . "\n\nPerhatian: E-mail ini (termasuk seluruh lampirannya, bila ada) dikirim otomatis oleh aplikasi dan hanya ditujukan kepada penerima yang tercantum di atas. Jika Anda bukan penerima yang dituju, maka Anda tidak diperkenankan untuk memanfaatkan, menyebarkan, mendistribusikan, atau menggandakan e-mail ini beserta seluruh lampirannya. Mohon untuk tidak membalas email ini."
                            ;
                        }
                        else if ($isUserDataChanged) {
                            $subject = "[JAGA] Perubahan Data Akun JAGA";
                            $message = "Hi Sahabat JAGA, terima kasih telah menggunakan Aplikasi JAGA."
                                . "\n\nKamu menerima email ini karena adanya perubahan data pada akun JAGA kamu. Berikut informasi perubahan data pada Akun kamu :"
                                . "\n\n-nama: " . $user->nama
                                . "\n-tanggal lahir: " . $user->tgl_lahir
                                . "\n-no tlp: " . $user->phone
                                . "\n-email: " . $user->email
                                . "\n\nJika ada ketidaksesuaian pada data di atas, kamu dapat mengubah data kamu pada halaman profil."
                                . "\nJangan khawatir, semua data kamu aman di JAGA."
                                . "\nAkses JAGA dan bantu kami untuk Cegah Korupsi dari Ujung Jari."
                                . "\n\nPerhatian: E-mail ini (termasuk seluruh lampirannya, bila ada) dikirim otomatis oleh aplikasi dan hanya ditujukan kepada penerima yang tercantum di atas. Jika Anda bukan penerima yang dituju, maka Anda tidak diperkenankan untuk memanfaatkan, menyebarkan, mendistribusikan, atau menggandakan e-mail ini beserta seluruh lampirannya. Mohon untuk tidak membalas email ini."
                            ;
                        } else if($has_password) {
                            $subject = "[JAGA] Permintaan Perubahan Password diterima.";
                            $message = "Hi Sahabat JAGA, terima kasih telah menggunakan Aplikasi JAGA."
                                . "\n\nKamu menerima email ini karena telah melakukan permintaan perubahan password pada akun JAGA kamu. Berikut akses yang dapat digunakan untuk login ke Aplikasi JAGA:"
                                . "\n\n-Username: " . $user->um_user_name
                                . "\n-Password: " . $pass
                                . "\n\natau klik link berikut " . $this->config["web_base_url"]
                                . "\n\nJangan khawatir. Tingkatkan keamanan akun JAGA-mu dengan mengganti password setelah berhasil melakukan login. Perubahan password dapat dilakukan pada halaman profil."
                                . "\nAkses JAGA dan bantu kami untuk Cegah Korupsi dari Ujung Jari."
                                . "\n\nPerhatian: E-mail ini (termasuk seluruh lampirannya, bila ada) dikirim otomatis oleh aplikasi dan hanya ditujukan kepada penerima yang tercantum di atas. Jika Anda bukan penerima yang dituju, maka Anda tidak diperkenankan untuk memanfaatkan, menyebarkan, mendistribusikan, atau menggandakan e-mail ini beserta seluruh lampirannya. Mohon untuk tidak membalas email ini."
                            ;
                        }

                        if($isUserDataChanged || $has_password) {
                            $mail = new MailHelper($this->config["mail_config"]);
                            $mail->queue($rawBody["email"], $subject, $message, false, $this->shared->user->um_id);
                        }
                    }
                }

                $newData = LogModeration::extractUmUser($validatedRequest['id']);
                $action = [LogModeration::ACTION_UPDATE_UMUSER];
                if ($has_password) $action[] = LogModeration::ACTION_CHANGEPWD_UMUSER;
                LogModeration::loggedUmUser(
                    $action,
                    LogModeration::ACTIONMENU_ADMINISTRASI_PANCEK_USER_PANCEK,
                    $user->um_id,
                    $oldData,
                    $newData,
                    $this->shared->user->um_id
                );

                $result = $user->baseToArray();
                $instansi = $user->instansi;
                $result['instansi'] = empty($instansi) ? null : $instansi;

                $this->db->commit();
                return new CollectionPaginatedResponse([$result]);
            }
        } catch(\Exception $e) {
            $this->db->rollback();
            throw $e;
        }
    }

    private function getAllowedRoleIds()
    {
        $allowedRoleNames = ['verifikator_pancek', 'observer_pancek', 'admin_kppbc_pancek', 'verifikator_kppbc_pancek', 'assignor_kppbc_pancek', 'verifikator_bk_pancek', 'assignor_bk_pancek'];
        $allowedRoles = Roles::find([
            'conditions' => 'name IN ({role_names:array})',
            'bind' => [
                'role_names' => $allowedRoleNames
            ]
        ]);
        return array_column($allowedRoles->toArray(), 'id');
    }

    private function getAllowedInstansiIds()
    {
        $allowedInstansi = Instansi::find([
            'conditions' => 'nama IN ({nama_arr:array})',
            'bind' => [
                'nama_arr' => [Instansi::DITJEN_BEA_CUKAI, Instansi::DITJEN_BINA_KONSTRUKSI_PUPR]
            ]
        ]);
        return array_column($allowedInstansi->toArray(), 'id');
    }

    private function resetPancekRoles($validatedRequest, array $allowedRoleIds)
    {
        $currentAllowedUsersRoles = UsersRoles::find([
            'conditions' => 'user_id = :um_id: AND role_id IN ({roles:array})',
            'bind' => [
                'um_id' => $validatedRequest['id'],
                'roles' => $allowedRoleIds
            ]
        ]);
        foreach ($currentAllowedUsersRoles as $currentAllowedUsersRole) {
            $currentAllowedUsersRole->delete();
        }
        foreach ($validatedRequest['roles'] as $roleId) {
            $usersRoles = new UsersRoles();
            $usersRoles->user_id = $validatedRequest['id'];
            $usersRoles->role_id = $roleId;
            $usersRoles->save();
        }
    }

    public function accessObserver()
    {
        $this->checkOneOfScopes(['pancek.admin', 'admin.view']);

        $rawBody = $this->request->getJsonRawBody(true);

        $validatedRequest = $this->validate(
            $rawBody,
            [
                'user_id' => ['validators' => ['required']],
                'role_ids' => ['validators' => ['required']],
                'access' => ['validators' => [
                    [
                        'name' => 'inclusion_in',
                        'params' => [
                            PancekUserAccess::ACCESS_EXTEND,
                            PancekUserAccess::ACCESS_INVALIDATE
                        ],
                    ]
                ]]
            ]
        );
        $idRoleObserver = Roles::getIdByRole(PancekUserAccess::ROLE_OBSERVER);
        $roleIds = explode(',', $rawBody['role_ids']);
        foreach ($roleIds as $roleId) {
            if ($roleId == $idRoleObserver) {
                $pua = PancekUserAccess::findFirst([
                    'conditions' => 'user_id = :user_id: AND role_id = :role_id: AND deleted_at IS NULL',
                    'bind' => [
                        'user_id' => $validatedRequest['user_id'],
                        'role_id' => $roleId
                    ]
                ]);
                if ($pua) {
                    $pua->updated_at = date('Y-m-d');
                    switch ($validatedRequest['access']) {
                        case PancekUserAccess::ACCESS_EXTEND:
                            $pua->end_date = date('Y-m-d', strtotime('+3 months'));
                            break;
                        case PancekUserAccess::ACCESS_INVALIDATE:
                            $pua->end_date = date('Y-m-d', strtotime('-1 day'));
                            break;
                        default:
                            break;
                    }
                    if (!$pua->save())
                    {
                        $error_messages = $pua->getErrorMessages();
                        throw new CustomErrorException(BaseResponse::SQL_ERROR, $error_messages);
                    }
                }
            }
        }

        return new SimpleResponse(1);
    }

    public function accessVerifikatorBK()
    {
        $this->checkOneOfScopes(['pancek.admin', 'admin.view']);
        $idRoleVerifikator = Roles::getIdByRole(PancekUserAccess::ROLE_VERIFIKATOR_BK);
        $this->accessVerifikator($idRoleVerifikator);
        return new SimpleResponse(1);
    }

    public function accessVerifikatorBC()
    {
        $this->checkOneOfScopes(['pancek.admin', 'admin.view', 'pancek.kppbc.admin']);
        $idRoleVerifikator = Roles::getIdByRole(PancekUserAccess::ROLE_VERIFIKATOR_BC);
        $this->accessVerifikator($idRoleVerifikator);
        return new SimpleResponse(1);
    }

    public function initAccessVerifikatorBK()
    {
        $this->checkOneOfScopes(['pancek.bk.verify']);
        $roleId = Roles::getIdByRole(PancekUserAccess::ROLE_VERIFIKATOR_BK);
        $this->initAccessVerifikator($roleId);
        return new SimpleResponse(1);
    }

    public function initAccessVerifikatorBC()
    {
        $this->checkOneOfScopes(['pancek.kppbc.verify']);
        $roleId = Roles::getIdByRole(PancekUserAccess::ROLE_VERIFIKATOR_BC);
        $this->initAccessVerifikator($roleId);
        return new SimpleResponse(1);
    }

    public function infoAccessVerifikatorBK()
    {
        $this->checkOneOfScopes(['pancek.bk.verify']);
        $roleId = Roles::getIdByRole(PancekUserAccess::ROLE_VERIFIKATOR_BK);
        $result = $this->infoAccessVerifikator($roleId);
        return new SimpleResponse($result);
    }

    public function infoAccessVerifikatorBC()
    {
        $this->checkOneOfScopes(['pancek.kppbc.verify']);
        $roleId = Roles::getIdByRole(PancekUserAccess::ROLE_VERIFIKATOR_BC);
        $result = $this->infoAccessVerifikator($roleId);
        return new SimpleResponse($result);
    }

    private function accessVerifikator($idRoleVerifikator)
    {
        $rawBody = $this->request->getJsonRawBody(true);

        $validatedRequest = $this->validate(
            $rawBody,
            [
                'user_id' => ['validators' => ['required']],
                'role_ids' => ['validators' => ['required']],
                'access' => ['validators' => [
                    [
                        'name' => 'inclusion_in',
                        'params' => [
                            PancekUserAccess::ACCESS_INVALIDATE,
                            PancekUserAccess::ACCESS_RESET,
                        ],
                    ]
                ]]
            ]
        );
        $roleIds = explode(',', $rawBody['role_ids']);
        foreach ($roleIds as $roleId) {
            if ($roleId == $idRoleVerifikator) {
                $pua = PancekUserAccess::findFirst([
                    'conditions' => 'user_id = :user_id: AND role_id = :role_id: AND deleted_at IS NULL',
                    'bind' => [
                        'user_id' => $validatedRequest['user_id'],
                        'role_id' => $roleId
                    ]
                ]);
                if ($pua) {
                    $pua->updated_at = date('Y-m-d');
                    switch ($validatedRequest['access']) {
                        case PancekUserAccess::ACCESS_INVALIDATE:
                            $pua->end_date = date('Y-m-d', strtotime('-1 day'));
                            if (!$pua->save())
                            {
                                $error_messages = $pua->getErrorMessages();
                                throw new CustomErrorException(BaseResponse::SQL_ERROR, $error_messages);
                            }
                            break;
                        case PancekUserAccess::ACCESS_RESET:
                            if (!$pua->forceDelete())
                            {
                                $error_messages = $pua->getErrorMessages();
                                throw new CustomErrorException(BaseResponse::SQL_ERROR, $error_messages);
                            }
                            break;
                        default:
                            break;
                    }
                }
            }
        }
    }

    private function initAccessVerifikator($roleId) {
        $userId = $this->shared->user->um_id;
        $pua = PancekUserAccess::findFirst([
            'conditions' => 'user_id = :user_id: AND role_id = :role_id: AND deleted_at IS NULL',
            'bind' => ['user_id' => $userId, 'role_id' => $roleId]
        ]);
        if (!$pua) {
            $pua = new PancekUserAccess();
            $pua->user_id = $userId;
            $pua->role_id = $roleId;
        }
        if (is_null($pua->end_date)) {
            $days = AppConfig::getValueByKode('VERIFIKATOR_BK_ACCESS');
            $pua->start_date = date('Y-m-d');
            $pua->end_date = date('Y-m-d', strtotime("+$days days"));
            $pua->save();
        }
    }

    private function infoAccessVerifikator($roleId) {
        $userId = $this->shared->user->um_id;
        $pua = PancekUserAccess::findFirst([
            'conditions' => 'user_id = :user_id: AND role_id = :role_id: AND deleted_at IS NULL',
            'bind' => ['user_id' => $userId, 'role_id' => $roleId]
        ]);

        $result = [
            "start_date" => null,
            "end_date" => null
        ];
        if ($pua) {
            $result["start_date"] = $pua->start_date;
            $result["end_date"] = $pua->end_date;
        }

        return $result;
    }
}