<?php

class BannerController extends BaseController
{
    #region admin
    public function index()
    {
        $this->checkScopes(['admin.view']);
        $rawParams = $this->request->get();
        $rawParams['limit'] = $this->getDefaultLimit($rawParams);
        $rawParams['offset'] = array_key_exists('offset', $rawParams) ? $rawParams['offset'] : 0;

        $validatedParams = $this->validate($rawParams, [
            'show_start' => $this->fieldsValidator('show_start'),
            'show_end' => $this->fieldsValidator('show_end'),
            'is_active' => $this->fieldsValidator('is_active'),
            'mode' => ['validators' => [
                [
                    'name' => 'inclusion_in',
                    'params' => array_merge([null], Banner::OPTION_MODE)
                ]
            ]],
            'keyword' => ['validators' => [
                [
                    'name' => 'max_string',
                    'params' => 100
                ]
            ]],
            'limit' => ['validators' => [
                'required', 'digit'
            ]],
            'offset' => ['validators' => [
                'required', 'digit'
            ]]
        ]);

        $conditions = "(filename ILIKE :keyword: OR click_url ILIKE :keyword: OR keyword ILIKE :keyword:)";
        $bind = [
            'keyword' => '%' . $validatedParams['keyword'] . '%'
        ];

        if (!empty($validatedParams['show_start'])) {
            $conditions .= " AND (:show_start: >= show_start OR show_start IS NULL)";
            $bind["show_start"] = $validatedParams['show_start'];
        }

        if (!empty($validatedParams['show_end'])) {
            $conditions .= " AND (:show_end: <= show_end OR show_end IS NULL)";
            $bind["show_end"] = $validatedParams['show_end'];
        }

        if (strlen($validatedParams['is_active']) > 0) {
            $conditions .= " AND is_active = :is_active:";
            $bind["is_active"] = $validatedParams['is_active'];
        }

        $keyOrder = Banner::keyOrderByMode($validatedParams["mode"]);
        if (!empty($validatedParams['mode'])) {
            $queryMode = ["mode = :mode1:"];
            $bind["mode1"] = $validatedParams["mode"];
            if ($validatedParams["mode"] == Banner::MODE_WEB || $validatedParams["mode"] == Banner::MODE_MOBILE) {
                $queryMode[] = "mode = :mode2:";
                $bind["mode2"] = Banner::MODE_BOTH;
            } elseif ($validatedParams["mode"] == Banner::MODE_WEB_EN || $validatedParams["mode"] == Banner::MODE_MOBILE_EN) {
                $queryMode[] = "mode = :mode2:";
                $bind["mode2"] = Banner::MODE_BOTH_EN;
            }
            $conditions .= " AND (" . implode(" OR ", $queryMode) . ")";
        }

        $query = [
            'conditions' => $conditions,
            'bind' => $bind
        ];
        $total = Banner::count($query);
        $query['limit'] = $validatedParams['limit'];
        $query['offset'] = $validatedParams['offset'];
        if (!is_null($keyOrder)) {
            $query["order"] = $keyOrder . " asc";
        }
        $rows = Banner::find($query);

        return new PaginatedResponse($rows, $total, $query['limit'], $query['offset']);
    }

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

        $postParams = $this->request->getPost();
        $post = $this->validate($postParams, $this->fieldsValidator());

        // validasi objek param_url. harus berupa objek dalam bentuk json string
        if (!empty($post['param_url'])) {
            try{
                if (!is_object(json_decode($post['param_url'])) && !is_array(json_decode($post['param_url']))) {
                    throw new CustomErrorException(BaseResponse::INVALID_REQUEST, "URL parameter is invalid");
                }
            } catch (\Exception $e) {
                throw new CustomErrorException(BaseResponse::INVALID_REQUEST, "URL paremeter is invalid");
            }
        }

        // validasi terkait file
        // jika ada file, maka nilai attachment_id && filename akan di override
        // file boleh kosong dengan catatan filename harus terisi
        if (empty($post['filename']) && !$this->request->hasFiles()) {
            throw new CustomErrorException(BaseResponse::INVALID_REQUEST, "File parameter is empty");
        } else if ($this->request->hasFiles()){
            $validationConfig = [
                'allowed-types' => [
                    "image/jpg",
                    "image/jpeg",
                    "image/png",
                    "image/x-windows-bmp",
                    "image/x-ms-bmp",
                    "image/bmp",
                    "image/gif",
                    "image/tiff",
                ],
                'max-file-count' => 1,
                'max-size-kb' => 5000
            ];
            /** @var MinioService $minioService */
            $minioService = $this->minio;
            $service = new AttachmentService($this->shared, $this->minio);
            if ($minioService->isSecondaryS3Available()) {
                $attachments = $service->uploadUsingSecondaryS3(
                    getenv("FILESYSTEM_CLOUD"),
                    $validationConfig,
                    ['tipe' => Attachments::TIPE_PUBLIC],
                    $this->request,
                    '/',
                    Banner::S3_DIRECTORY
                );
            } else {
                $attachments = $service->upload(
                    getenv("FILESYSTEM_CLOUD"),
                    $validationConfig,
                    ['tipe' => Attachments::TIPE_PUBLIC],
                    $this->request,
                    '/',
                    Banner::S3_DIRECTORY
                );
            }

            /** @var Attachments $attachment */
            $attachment = $attachments[0];
            $post['attachment_id'] = $attachment->id;
            $post['filename'] = $attachment->uuid;
        }

        $banner = new Banner();
        $banner->assign($post);
        $banner->created_by = $this->shared->user->um_id;
        if (!$banner->save()) {
            throw new CustomErrorException(BaseResponse::SQL_ERROR, "Gagal menyimpan data");
        }

        return new SimpleResponse($banner);
    }

    public function update($id) {
        $this->checkScopes(['admin.view']);

        /** @var Banner $banner */
        $banner = Banner::findFirst($id);
        if (!$banner) {
            throw new CustomErrorException(BaseResponse::DATA_NOT_FOUND, "Data tidak ditemukan");
        }

        $postParams = $this->request->getPost();
        $post = $this->validate($postParams, $this->fieldsValidator([
            'filename',
            'click_url',
            'param_url',
            'view_order',
            'view_order_mobile',
            'show_start',
            'show_end',
            'is_active',
            'mode',
            'keyword',
        ]));

        // validasi objek param_url. harus berupa objek dalam bentuk json string
        if (!empty($post['param_url'])) {
            try{
                if (!is_object(json_decode($post['param_url'])) && !is_array(json_decode($post['param_url']))) {
                    throw new CustomErrorException(BaseResponse::INVALID_REQUEST, "URL parameter is invalid");
                }
            } catch (\Exception $e) {
                throw new CustomErrorException(BaseResponse::INVALID_REQUEST, "URL paremeter is invalid");
            }
        }

        // validasi terkait file
        // jika ada file, maka nilai attachment_id && filename akan di override
        // file boleh kosong dengan catatan filename harus terisi
        if (empty($post['filename']) && !$this->request->hasFiles()) {
            throw new CustomErrorException(BaseResponse::INVALID_REQUEST, "File parameter is empty");
        } else if ($this->request->hasFiles()){
            $validationConfig = [
                'allowed-types' => [
                    "image/jpg",
                    "image/jpeg",
                    "image/png",
                    "image/x-windows-bmp",
                    "image/x-ms-bmp",
                    "image/bmp",
                    "image/gif",
                    "image/tiff",
                ],
                'max-file-count' => 1,
                'max-size-kb' => 5000
            ];

            /** @var MinioService $minioService */
            $minioService = $this->minio;
            $service = new AttachmentService($this->shared, $this->minio);
            if ($minioService->isSecondaryS3Available()) {
                $attachments = $service->uploadUsingSecondaryS3(
                    getenv("FILESYSTEM_CLOUD"),
                    $validationConfig,
                    ['tipe' => Attachments::TIPE_PUBLIC],
                    $this->request,
                    '/',
                    Banner::S3_DIRECTORY
                );
            } else {
                $attachments = $service->upload(
                    getenv("FILESYSTEM_CLOUD"),
                    $validationConfig,
                    ['tipe' => Attachments::TIPE_PUBLIC],
                    $this->request,
                    '/',
                    Banner::S3_DIRECTORY
                );
            }

            /** @var Attachments $attachment */
            $attachment = $attachments[0];
            $post['attachment_id'] = $attachment->id;
            $post['filename'] = $attachment->uuid;
        }

        $banner->assign($post);
        $banner->updated_at = DateHelper::getNowTimezoned();
        $banner->updated_by = $this->shared->user->um_id;
        if (!$banner->save()) {
            throw new CustomErrorException(BaseResponse::SQL_ERROR, "Gagal menyimpan data");
        }

        return new SimpleResponse($banner);
    }

    public function detail($id)
    {
        $this->checkScopes(['admin.view']);
        /** @var Banner $row */
        $row = Banner::findFirst($id);
        if (!$row) {
            throw new CustomErrorException(BaseResponse::DATA_NOT_FOUND, "Data tidak ditemukan");
        }

        return new SimpleResponse($row->toDetail());
    }

    public function activate()
    {
        $this->checkScopes(['admin.view']);
        $params = $this->request->getJsonRawBody(true);
        $params = $this->validate($params, [
           'id' => ['validators' => ['required']]
        ]);
        $id = $params['id'];
        /** @var Banner|bool $banner */
        $banner = Banner::findFirst($id);
        if (!$banner) {
            throw new CustomErrorException(BaseResponse::DATA_NOT_FOUND, "Data tidak ditemukan");
        }
        if ($banner->is_active) {
            throw new CustomErrorException(BaseResponse::INVALID_REQUEST, "Data telah aktif sebelumnya");
        }

        $banner->is_active = true;
        $banner->updated_at = DateHelper::getNowTimezoned();
        $banner->updated_by = $this->shared->user->um_id;
        $banner->save();

        return new SimpleResponse($banner);
    }

    public function deactivate()
    {
        $this->checkScopes(['admin.view']);
        $params = $this->request->getJsonRawBody(true);
        $params = $this->validate($params, [
            'id' => ['validators' => ['required']]
        ]);
        $id = $params['id'];
        /** @var Banner|bool $banner */
        $banner = Banner::findFirst($id);
        if (!$banner) {
            throw new CustomErrorException(BaseResponse::DATA_NOT_FOUND, "Data tidak ditemukan");
        }
        if (!$banner->is_active) {
            throw new CustomErrorException(BaseResponse::INVALID_REQUEST, "Data belum aktif sebelumnya");
        }

        $banner->is_active = false;
        $banner->updated_at = DateHelper::getNowTimezoned();
        $banner->updated_by = $this->shared->user->um_id;
        $banner->save();

        return new SimpleResponse($banner);
    }

    public function delete() {
        $this->checkScopes(['admin.view']);
        $params = $this->request->getJsonRawBody(true);
        $params = $this->validate($params, [
            'id' => ['validators' => ['required']],
            'soft_delete' => ['validators' => ['required', ['name' => 'inclusion_in', 'params' => [0,1]]]]
        ]);
        $id = $params['id'];
        /** @var Banner|bool $banner */
        $banner = Banner::findFirst($id);
        if (!$banner) {
            throw new CustomErrorException(BaseResponse::DATA_NOT_FOUND, "Data tidak ditemukan");
        }

        try {
            $this->db->begin();
            if ($params['soft_delete']) {
                if ($banner->attachment_id) {
                    $attachment = Attachments::findFirst(['conditions' => 'id = :id:', 'bind' => ['id' => $banner->attachment_id]]);
                    if ($attachment) {
                        AttachmentHelper::delete($attachment);
                    }
                }
                $banner->deleted_at = DateHelper::getNowTimezoned();
                $banner->deleted_by = $this->shared->user->um_id;
                $banner->save();
            } else {
                if ($banner->attachment_id) {
                    $attachment = Attachments::findFirst(['conditions' => 'id = :id:', 'bind' => ['id' => $banner->attachment_id]]);
                    if ($attachment) {
                        AttachmentHelper::delete($attachment, false, $this->minio, Banner::S3_DIRECTORY);
                    }
                }
                $banner->delete();
            }

            $this->db->commit();
            return new SimpleResponse([]);
        } catch (\Exception $e) {
            $this->db->rollback();
            throw new CustomErrorException(BaseResponse::UNEXPECTED_ERROR, $e->getMessage());
        }
    }

    public function restore() {
        $this->checkScopes(['admin.view']);
        $params = $this->request->getJsonRawBody(true);
        $params = $this->validate($params, [
            'id' => ['validators' => ['required']]
        ]);
        $id = $params['id'];
        /** @var Banner|bool $banner */
        $banner = Banner::findFirst($id);
        if (!$banner) {
            throw new CustomErrorException(BaseResponse::DATA_NOT_FOUND, "Data tidak ditemukan");
        }

        try {
            $this->db->begin();
            if ($banner->attachment_id) {
                $attachment = Attachments::findFirst(['conditions' => 'id = :id:', 'bind' => ['id' => $banner->attachment_id]]);
                if ($attachment) {
                    AttachmentHelper::restore($attachment);
                }
            }
            $banner->updated_at = DateHelper::getNowTimezoned();
            $banner->updated_by = $this->shared->user->um_id;
            $banner->deleted_at = null;
            $banner->deleted_by = null;
            $banner->save();

            $this->db->commit();
            return new SimpleResponse([]);
        } catch (\Exception $e) {
            $this->db->rollback();
            throw new CustomErrorException(BaseResponse::UNEXPECTED_ERROR, $e->getMessage());
        }
    }

    public function sort() {
        $this->checkScopes(['admin.view']);
        $params = $this->request->getJsonRawBody(true);
        $this->validate($params, [
            "mode" => ["validators" => ["required", ['name' => 'inclusion_in', 'params' => Banner::OPTION_MODE]]],
            "items" => ["validators" => ["required"]]
        ]);
        if (!is_array($params["items"])) {
            throw new CustomErrorException(BaseResponse::INVALID_REQUEST, "Payload tidak valid!");
        }
        $keyOrder = Banner::keyOrderByMode($params["mode"]);
        foreach ($params["items"] as $item) {
            $this->validate($item, [
                'id' => ['validators' => ['required','digit']],
                $keyOrder => ['validators' => ['required','digit']]
            ]);
        }
        try {
            $this->db->begin();
            foreach ($params["items"] as $item) {
                /** @var Banner $banner */
                $banner = Banner::findFirst(["conditions" => "id = :id:", "bind" => ["id" => $item["id"]]]);
                if (!$banner) {
                    throw new CustomErrorException(BaseResponse::DATA_NOT_FOUND, "Banner [" . $params["id"] . "] tidak ditemukan!");
                }
                $banner->$keyOrder = $item[$keyOrder];
                $banner->save();
            }
            $this->db->commit();
            return new SimpleResponse(1);
        } catch (\Exception $e) {
            $this->db->rollback();
            throw $e;
        }
    }
    #endregion

    #region public
    public function showingList()
    {
        $params = $this->request->get();
        $params = $this->validate($params, $this->fieldsValidator(['mode']));
        $keyOrder = Banner::keyOrderByMode($params["mode"]);
        $mode = $params['mode'];
        $list = Banner::find([
            'conditions' => '(show_start <= :now: OR show_start IS NULL) AND (show_end >= :now: OR show_end IS NULL) AND is_active = true AND deleted_at IS NULL AND (mode = :mode: OR mode = :both:)',
            'bind' => [
                'now' => DateHelper::getNowTimezoned(),
                'mode' => $mode,
                'both' => Banner::keyBothByMode($mode)
            ],
            'order' => $keyOrder.', id'
        ]);

        return new CollectionPaginatedResponse($list);
    }

    public function publicView() {
        $getParams = $this->request->get();

        $validatedRequest = $this->validate(
            $getParams,
            [
                'filename' => ['validators' => ['required']],
                'sub' => ['validators' => []],
            ]
        );
        $filename = $validatedRequest['filename'];

        if (getenv("FILESYSTEM_CLOUD") == FileSystemCloud::MINIO) {
            /** @var MinioService $minioService */
            $minioService = $this->minio;
            $root = Banner::S3_DIRECTORY;
            if (!empty($validatedRequest['sub'])) {
                $root .= DIRECTORY_SEPARATOR . $validatedRequest['sub'];
            }
            if ($minioService->isSecondaryS3Available()) {
                $url = $minioService->basePublicUrlUsingSecondaryS3($root . DIRECTORY_SEPARATOR . $filename, $filename, false);
            } else {
                $url = $minioService->base_get_public_url($root . DIRECTORY_SEPARATOR . $filename, $filename, false);
            }

            $response = new \Phalcon\Http\Response();
            return $response->redirect($url, true, 302);
        } elseif (getenv("FILESYSTEM_CLOUD") == FileSystemCloud::LOCAL) {
            $url = $this->config["profile_picture_base_url"] . rawurlencode($filename);

            $response = new \Phalcon\Http\Response();
            return $response->redirect($url, true, 302);
        }
    }
    #endregion

    #region private
    private function fieldsValidator($fields = null) {
        $validator = [
            'attachment_id' => ['validators' => ['digit']],
            'filename' => ['validators' => [['name' => 'max_string', 'params' => 255]]],
            'keyword' => ['validators' => [['name' => 'max_string', 'params' => 100]]],
            'click_url' => ['validators' => [['name' => 'max_string', 'params' => 100]]],
            'param_url' => ['validators' => []],
            'view_order' => ['validators' => ['digit']],
            'view_order_mobile' => ['validators' => ['digit']],
            'show_start' => ['validators' => ['datetime']],
            'show_end' => ['validators' => ['datetime']],
            'is_active' => ['validators' => [['name' => 'inclusion_in', 'params' => [0,1]]]],
            'mode' => ['validators' => ['required', ['name' => 'inclusion_in', 'params' => Banner::OPTION_MODE]]]
        ];

        if (is_array($fields) && count($fields) > 0) {
            $ret = [];
            foreach ($fields as $field) {
                $ret[$field] = $validator[$field];
            }
            return $ret;
        } else if (is_string($fields)) {
            return $validator[$fields];
        } else {
            return $validator;
        }
    }
    #endregion
}
