<?php

namespace App\Controllers;


use CodeIgniter\Controller;
use CodeIgniter\HTTP\CLIRequest;
use CodeIgniter\HTTP\Files\UploadedFile;
use CodeIgniter\HTTP\IncomingRequest;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;

/**
 * Class BaseController
 *
 * BaseController provides a convenient place for loading components
 * and performing functions that are needed by all your controllers.
 * Extend this class in any new controllers:
 *     class Home extends BaseController
 *
 * For security be sure to declare any new methods as protected or private.
 */
abstract class BaseController extends Controller
{
    /**
     * Instance of the main Request object.
     *
     * @var CLIRequest|IncomingRequest
     */
    protected $request;

    /**
     * An array of helpers to be loaded automatically upon
     * class instantiation. These helpers will be available
     * to all other controllers that extend BaseController.
     *
     * @var array
     */
    protected $helpers = ['Common'];

    /**
     * Be sure to declare properties for any property fetch you initialized.
     * The creation of dynamic property is deprecated in PHP 8.2.
     */
    // protected $session;

    /**
     * Constructor.
     */
    protected $_model = null;
    protected $_session = null;
    protected $_viewPath = '';
    protected $_viewDatas = array();
    public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger)
    {
        // Do Not Edit This Line
        parent::initController($request, $response, $logger);
        // Preload any models, libraries, etc, here.
        // E.g.: $this->session = \Config\Services::session();
        $this->_session = \Config\Services::session();
        $this->_viewDatas['layout'] = LAYOUTS['empty'];
        $this->_viewDatas['session'] = $this->_session;
    }

    abstract public function getFields(string $action): array;
    abstract public function getFieldFilters(): array;
    public function getFieldBatchFilters(): array
    {
        return $this->getFieldFilters();
    }
    //Field별 Form Datas 처리용
    protected function getFieldFormData(string $field, $entity = null): array
    {
        switch ($field) {
            default:
                $this->_viewDatas['fieldDatas'][$field] = $this->request->getVar($field);
                break;
        }
        return $this->_viewDatas['fieldDatas'];
    }

    //Upload FIle관련
    protected function upload_file_process(UploadedFile $upfile)
    {
        $fileName = null;
        if ($upfile->isValid() && !$upfile->hasMoved()) {
            $originName = $upfile->getName();
            $upfile->move(WRITEPATH . PATHS['UPLOAD'], $upfile->getRandomName());
            //move시 중복된파일명이 있다면 파일명이 바뀌므로 여기서 한번더 파일명 확인 필요
            $fileName = $originName . DEFAULTS['DELIMITER_FILE'] . $upfile->getName();
        }
        return $fileName;
    }
    protected function single_upload_procedure(string $field, $entity = null)
    {
        if ($upfile = $this->request->getFile($field)) {
            return $this->upload_file_process($upfile);
        }
    }
    protected function multiple_upload_procedure(string $field, $entity = null): array
    {
        //Multiple파일의경우 html에서는 필드명[]를 넣어야하며
        //rule에서 "uploaded[필드명.0]|is_image[필드명]~~" 이런식으로 넣어야함
        $fileNames = array();
        if ($upfiles = $this->request->getFiles()) {
            foreach ($upfiles[$field] as $upfile) {
                if ($upfile->isValid() && !$upfile->hasMoved()) {
                    $fileName = $this->upload_file_process($upfile);
                    array_push($this->_viewDatas['fieldDatas'][$field], $fileName);
                }
            }
        }
        return $fileNames;
    }

    //초기화
    final public function init(string $action, $fields = null)
    {
        switch ($action) {
            case 'insert_form':
                $action = 'insert';
                break;
            case 'update_form':
                $action = 'update';
                break;
        }
        $this->_viewDatas['fields'] = $fields ?: $this->getFields($action);
        $this->_viewDatas['fieldRules'] = $this->_model->getFieldRules($this->_viewDatas['fields'], $action);
        $this->_viewDatas['fieldFilters'] = $this->getFieldFilters();
        $this->_viewDatas['batchjobFilters'] = $this->getFieldBatchFilters();
        $this->_viewDatas['fieldFormOptions'] = $this->_model->getFieldFormOptions($this->_viewDatas['fieldFilters']);
        return $this->_viewDatas;
    }

    //Insert관련
    final public function insert_form()
    {
        try {
            $this->_viewDatas = $this->init(__FUNCTION__);
            $this->_viewDatas['forms'] = ['attributes' => ['method' => "post",], 'hiddens' => []];
            helper(['form']);
            $this->_session->keepFlashdata(SESSION_NAMES['RETURN_URL']);
            return view($this->_viewPath . '/insert', $this->_viewDatas);
        } catch (\Exception $e) {
            log_message("error", $e->getMessage());
            return redirect()->to($this->_session->getFlashdata(SESSION_NAMES['RETURN_URL']))->with('return_message', $e->getMessage());
        }
    }
    protected function insert_validate()
    {
        //fieldData Rule 검사
        //추후 VersionUP용
        // $this->_model->getValidation($this->getFields());
        // if (! $this->_validation->run($user)) {
        //     throw new \Exception("{$this->_viewDatas['title']}의 검증 오류발생\n" . implode("\n", $this->validator->getErrors()));
        // }
        //fieldData Rule 검사
        if (!$this->validate($this->_viewDatas['fieldRules'])) {
            throw new \Exception("{$this->_viewDatas['title']}의 검증 오류발생\n" . implode("\n", $this->validator->getErrors()));
        }
        //fieldData 적용
        $this->_viewDatas['fieldDatas'] = array();
        foreach ($this->_viewDatas['fields'] as $field) {
            $this->_viewDatas['fieldDatas'] = $this->getFieldFormData($field);
        }
    }
    protected function insert_process()
    {
        return $this->_model->create($this->_viewDatas['fieldDatas']);
    }
    public function insert()
    {
        $msg = "";
        try {
            $this->_viewDatas = $this->init(__FUNCTION__);
            $this->insert_validate();
            $entity = $this->insert_process();
            $msg = "{$this->_viewDatas['title']}에서 {$entity->getTitle()}의 " . __FUNCTION__ . " 완료하였습니다.";
            return redirect()->to($this->_session->getFlashdata(SESSION_NAMES['RETURN_URL']));
        } catch (\Exception $e) {
            $msg = "{$this->_viewDatas['title']}에서 " . __FUNCTION__ . " 실패하였습니다.\n" . $e->getMessage();
            log_message("error", $e->getMessage());
            return redirect()->back()->withInput();
        } finally {
            $this->_session->setFlashdata("return_message", $msg);
        }
    }

    //Update관련
    final public function update_form($uid)
    {
        try {
            $this->_viewDatas = $this->init(__FUNCTION__);
            $this->_viewDatas['forms'] = ['attributes' => ['method' => "post",], 'hiddens' => []];
            helper(['form']);
            $this->_session->keepFlashdata(SESSION_NAMES['RETURN_URL']);
            $this->_viewDatas['entity'] = $this->_model->getEntity([$this->_model->getPrimaryKey() => $uid]);
            return view($this->_viewPath . '/update',  $this->_viewDatas);
        } catch (\Exception $e) {
            log_message("error", $e->getMessage());
            return redirect()->to($this->_session->getFlashdata(SESSION_NAMES['RETURN_URL']))->with('return_message', $e->getMessage());
        }
    }
    protected function update_validate($entity)
    {
        //fieldData Rule 검사
        //추후 VersionUP용
        // $this->_model->getValidation($this->getFields());
        // if (! $this->_validation->run($user)) {
        //     throw new \Exception("{$this->_viewDatas['title']}의 검증 오류발생\n" . implode("\n", $this->validator->getErrors()));
        // }
        if (!$this->validate($this->_viewDatas['fieldRules'])) {
            throw new \Exception("{$this->_viewDatas['title']}의 검증 오류발생\n" . implode("\n", $this->validator->getErrors()));
        }
        //fieldData 적용
        $this->_viewDatas['fieldDatas'] = array();
        foreach ($this->_viewDatas['fields'] as $field) {
            $this->_viewDatas['fieldDatas'] = $this->getFieldFormData($field, $entity);
            log_message(
                "info",
                "{$field} : {$entity->$field} => " . var_export($this->_viewDatas['fieldDatas'][$field])
            );
        }
    }
    protected function update_process($entity)
    {
        return $this->_model->modify($entity, $this->_viewDatas['fieldDatas']);
    }
    public function update($uid)
    {
        $msg = "";
        try {
            $this->_viewDatas = $this->init(__FUNCTION__);
            $entity = $this->_model->getEntity([$this->_model->getPrimaryKey() => $uid]);
            $this->update_validate($entity);
            $entity = $this->update_process($entity);
            $msg = "{$this->_viewDatas['title']}에서 {$entity->getTitle()}의 " . __FUNCTION__ . " 완료하였습니다.";
            return redirect()->to($this->_session->getFlashdata(SESSION_NAMES['RETURN_URL']));
        } catch (\Exception $e) {
            $msg = "{$this->_viewDatas['title']}에서 " . __FUNCTION__ . " 실패하였습니다.\n" . $e->getMessage();
            log_message("error", $e->getMessage());
            return redirect()->back()->withInput();
        } finally {
            $this->_session->setFlashdata("return_message", $msg);
        }
    }

    //Reply관련
    final public function reply_form($uid)
    {
        try {
            $this->_viewDatas = $this->init(__FUNCTION__);
            $this->_viewDatas['forms'] = ['attributes' => ['method' => "post",], 'hiddens' => []];
            helper(['form']);
            $this->_session->keepFlashdata(SESSION_NAMES['RETURN_URL']);
            $entity = $this->_model->getEntity([$this->_model->getPrimaryKey() => $uid]);
            $titleField = $this->_model->getTitleField();
            $entity->$titleField = "RE: " . $entity->$titleField;
            $contentField = $this->_model->getContentField();
            $entity->$contentField .= "\n----------------------------------------------\n";
            $this->_viewDatas['entity'] = $entity;
            return view($this->_viewPath . '/reply',  $this->_viewDatas);
        } catch (\Exception $e) {
            log_message("error", $e->getMessage());
            return redirect()->to($this->_session->getFlashdata(SESSION_NAMES['RETURN_URL']))->with('return_message', $e->getMessage());
        }
    }
    protected function reply_validate($entity)
    {
        $this->update_validate($entity);
    }
    protected function reply_process($entity)
    {
        return $this->_model->reply($entity, $this->_viewDatas['fieldDatas']);
    }
    public function reply($uid)
    {
        $msg = "";
        try {
            $this->_viewDatas = $this->init(__FUNCTION__);
            $entity = $this->_model->getEntity([$this->_model->getPrimaryKey() => $uid]);
            $this->reply_validate($entity);
            $entity = $this->reply_process($entity);
            $msg = "{$this->_viewDatas['title']}에서 {$entity->getTitle()}의 " . __FUNCTION__ . " 완료하였습니다.";
            return redirect()->to($this->_session->getFlashdata(SESSION_NAMES['RETURN_URL']));
        } catch (\Exception $e) {
            $msg = "{$this->_viewDatas['title']}에서 " . __FUNCTION__ . " 실패하였습니다.\n" . $e->getMessage();
            log_message("error", $e->getMessage());
            return redirect()->back()->withInput();
        } finally {
            $this->_session->setFlashdata("return_message", $msg);
        }
    }

    //Toggle 관련
    protected function toggle_validate($entity)
    {
        $this->update_validate($entity);
    }
    protected function toggle_process($entity)
    {
        return $this->update_process($entity);
    }
    public function toggle($uid, string $field)
    {
        $msg = "";
        try {
            $this->_viewDatas = $this->init(__FUNCTION__, [$field]);
            $entity = $this->_model->getEntity([$this->_model->getPrimaryKey() => $uid]);
            $this->toggle_validate($entity);
            $entity = $this->toggle_process($entity);
            $msg = "{$this->_viewDatas['title']}에서 {$entity->getTitle()}의 " . __FUNCTION__ . " 완료하였습니다.";
            return redirect()->to($this->_session->getFlashdata(SESSION_NAMES['RETURN_URL']));
        } catch (\Exception $e) {
            $msg = "{$this->_viewDatas['title']}에서 " . __FUNCTION__ . " 실패하였습니다.\n" . $e->getMessage();
            log_message("error", $e->getMessage());
            return redirect()->to($this->_session->getFlashdata(SESSION_NAMES['RETURN_URL']));
        } finally {
            $this->_session->setFlashdata("return_message", $msg);
        }
    }
    //Batchjob 관련
    protected function batchjob_validate($entity)
    {
        $this->update_validate($entity);
    }
    protected function batchjob_process($entity)
    {
        return $this->update_process($entity);
    }
    public function batchjob()
    {
        $msg = "";
        $entitys = array();
        $batchjobs = array();
        try {
            //fields 해당하는 field중 선택된 값이 있는경우만 fields로 정의
            $fields = array();
            foreach ($this->_model->getFieldBatchFilters() as $field) {
                if ($this->request->getVar($field)) {
                    array_push($fields, $field);
                }
            }
            if (!is_array($fields) || count($fields) === 0) {
                throw new \Exception($this->_viewDatas['title'] . '에서 변경할 항목(field)이 선택되지 않았습니다.');
            }
            $this->_viewDatas = $this->init(__FUNCTION__, $fields);
            $uids = $this->request->getVar('batchjob_uids') ?: throw new \Exception($this->_viewDatas['title'] . '에서 변경할 항목(uid)이 선택되지 않았습니다.');
            $cnt = 1;
            //Transaction 시작
            $this->_model->transStart();
            foreach ($uids as $uid) {
                try {
                    $entity = $this->_model->getEntity([$this->_model->getPrimaryKey() => $uid]);
                    $this->batchjob_validate($entity);
                    $entity = $this->batchjob_process($entity);
                    array_push($entitys, $this->_model->modify($entity, $this->_viewDatas['fieldDatas']));
                    array_push($batchjobs, "{$cnt}. {$uid}->{$entity->getTitle()}는 완료.");
                } catch (\Exception $e) {
                    array_push($batchjobs, "{$cnt}. {$uid}는 실패.");
                }
                $cnt++;
            }
            //Transaction Commit
            $this->_model->transComplete();
            $msg = sprintf(
                "%s에서 총:%s개의 %s 완료하였습니다.",
                $this->_viewDatas['title'],
                count($entitys),
                __FUNCTION__
            );
            return redirect()->to($this->_session->getFlashdata(SESSION_NAMES['RETURN_URL']));
        } catch (\Exception $e) {
            //Transaction Rollback
            $this->_model->transRollback();
            log_message('error', sprintf("---------batchjob 작업결과--------\n%s\n", implode("\n", $batchjobs)));
            return sprintf(
                "총:%s개의 작업중 %s개는 성공하였지만 , %s개가 실패하여  %s 취소되었습니다.\n%s",
                count($uids),
                count($entitys),
                count($uids) - count($entitys),
                __FUNCTION__,
            );
            log_message("error", $e->getMessage());
            return redirect()->to($this->_session->getFlashdata(SESSION_NAMES['RETURN_URL']));
        } finally {
            $this->_session->setFlashdata("return_message", $msg);
        }
    }

    //Delete 관련
    protected function delete_process($entity)
    {
        if (!$this->_model->delete($entity->getPrimaryKey())) {
            log_message("error", __FUNCTION__ . "에서 호출:" . $this->_model->getLastQuery());
            log_message("error", implode("\n", $this->_model->errors()));
            throw new \Exception(__FUNCTION__ . " 오류 발생.\n" . var_export($this->_model->errors(), true));
        }
        return $entity;
    }
    public function delete($uid)
    {
        $msg = "";
        try {
            $entity = $this->_model->getEntity([$this->_model->getPrimaryKey() => $uid]);
            $this->delete_process($entity);
            $msg = "{$this->_viewDatas['title']}에서 {$entity->getTitle()}의 " . __FUNCTION__ . " 완료하였습니다.";
            return redirect()->to($this->_session->getFlashdata(SESSION_NAMES['RETURN_URL']));
        } catch (\Exception $e) {
            $msg = "{$this->_viewDatas['title']}에서 " . __FUNCTION__ . " 실패하였습니다.\n" . $e->getMessage();
            log_message("error", $e->getMessage());
            return redirect()->to($this->_session->getFlashdata(SESSION_NAMES['RETURN_URL']));
        } finally {
            $this->_session->setFlashdata("return_message", $msg);
        }
    }

    //View 관련
    protected function view_process($entity)
    {
        return $entity;
    }
    public function view($uid)
    {
        try {
            $this->_viewDatas = $this->init(__FUNCTION__);
            helper(['form']);
            $this->_viewDatas['forms'] = ['attributes' => ['method' => "post",], 'hiddens' => []];
            $this->_viewDatas['entity'] = $this->_model->getEntity([$this->_model->getPrimaryKey() => $uid]);
            $this->_session->keepFlashdata(SESSION_NAMES['RETURN_URL']);
            return view($this->_viewPath . '/view', $this->_viewDatas);
        } catch (\Exception $e) {
            return redirect()->to($this->_session->getFlashdata(SESSION_NAMES['RETURN_URL']))->with('return_message', $e->getMessage());
        }
    }

    //Index 관련
    protected function index_setCondition()
    {
        //조건절 처리
        $filterFields = array();
        foreach ($this->_viewDatas['fieldFilters'] as $field) {
            if (!is_null($this->request->getVar($field))) {
                $filterFields[$field] = $this->request->getVar($field);
            }
        }
        $word = $this->request->getVar('word');
        $start = $this->request->getVar('start');
        $end = $this->request->getVar('end');
        $order_field = $this->request->getVar('order_field');
        $order_value = $this->request->getVar('order_value');
        $this->_model->setCondition($filterFields, $word, $start, $end, $order_field, $order_value);
    }
    private function index_getPagination($pager_group = 'default', int $segment = 0, $template = 'bootstrap_full'): string
    {
        // 1.Views/Pagers/에 bootstrap_full.php,bootstrap_simple.php 생성
        // 2.app/Config/Pager.php/$templates에 'bootstrap_full => 'Pagers\bootstrap_full',
        // 'bootstrap_simple' => 'Pagers\bootstrap_simple', 추가
        $pager = \Config\Services::pager();
        // $this->_model->paginate($this->_viewDatas['per_page'], $pager_group, $this->_viewDatas['page'], $segment);
        $pager->makeLinks(
            $this->_viewDatas['page'],
            $this->_viewDatas['per_page'],
            $this->_viewDatas['total_count'],
            $template,
            $segment,
            $pager_group
        );
        $this->_viewDatas['page'] = $pager->getCurrentPage($pager_group);
        $this->_viewDatas['total_page'] = $pager->getPageCount($pager_group);
        return $pager->links($pager_group, $template);
    }
    private function index_getEntitys(): array
    {
        $this->index_setCondition();
        return $this->_viewDatas['page'] ? $this->_model->findAll(
            $this->_viewDatas['per_page'],
            $this->_viewDatas['page'] * $this->_viewDatas['per_page'] - $this->_viewDatas['per_page']
        ) : $this->_model->findAll();
    }
    public function index()
    {
        try {
            $this->_viewDatas = $this->init(__FUNCTION__);
            helper(['form']);
            $this->_viewDatas['forms'] = ['attributes' => ['method' => "post",], 'hiddens' => []];
            foreach ($this->_viewDatas['fieldFilters'] as $field) {
                $this->_viewDatas[$field] = $this->request->getVar($field) ?: DEFAULTS['EMPTY'];
            }
            $this->_viewDatas['word'] = $this->request->getVar('word') ?: '';
            $this->_viewDatas['start'] = $this->request->getVar('start') ?: '';
            $this->_viewDatas['end'] = $this->request->getVar('end') ?: '';
            $this->_viewDatas['order_field'] = $this->request->getVar('order_field') ?: 'uid';
            $this->_viewDatas['order_value'] = $this->request->getVar('order_value') ?: 'DESC';
            $this->_viewDatas['page'] = (int)$this->request->getVar('page') ?: 1;
            $this->_viewDatas['per_page'] = (int)$this->request->getVar('per_page') ?: DEFAULTS['PERPAGE'];
            $this->_viewDatas['uri'] = $this->request->getUri();
            //Totalcount 처리
            $this->index_setCondition();
            $this->_viewDatas['total_count'] = $this->_model->countAllResults();
            // log_message("debug", __METHOD__ . "에서 TotalCount 호출:" . $this->_model->getLastQuery());
            //줄수 처리용
            $this->_viewDatas['pageOptions'] = array("" => "줄수선택");
            for ($i = 10; $i <= $this->_viewDatas['total_count'] + $this->_viewDatas['per_page']; $i += 10) {
                $this->_viewDatas['pageOptions'][$i] = $i;
            }
            //pagenation 처리
            $this->_viewDatas['pagination'] = $this->index_getPagination();
            //모델 처리
            $this->_viewDatas['entitys'] = $this->index_getEntitys();
            // echo $this->_model->getLastQuery();
            // log_message("debug", __METHOD__ . "에서 findAll 호출:" . $this->_model->getLastQuery());
            //setting return_url to session flashdata
            $this->_session->setFlashdata(SESSION_NAMES['RETURN_URL'], current_url() . '?' . $this->request->getUri()->getQuery() ?: "");
            return view($this->_viewPath . '/index', $this->_viewDatas);
        } catch (\Exception $e) {
            return redirect()->back()->with('return_message', $e->getMessage());
        }
    }

    //Excel 관련
    private function excel_spreadSheet(array $viewDatas)
    {
        //Excepl 초기화
        $spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
        $sheet = $spreadsheet->getActiveSheet();
        //Header용
        $column = 'A';
        foreach ($viewDatas['fields'] as $field) {
            $sheet->setCellValue($column++ . '1', lang($this->_model->getClassName() . '.label.' . $field));
        }
        //본문용
        $line = 2;
        foreach ($this->index_getEntitys() as $entity) {
            $column = 'A';
            foreach ($viewDatas['fields'] as $field) {
                $value = in_array($field, $viewDatas['fieldFilters']) ? $viewDatas['fieldFormOptions'][$field][$entity->$field] : $entity->$field;
                $sheet->setCellValue($column . $line, $value);
                $column++;
            }
            $line++;
        }
        return $spreadsheet;
    }
    public function excel()
    {
        try {
            $this->_viewDatas = $this->init(__FUNCTION__);
            $fileName = date('Y-m-d_Hm') . '.xlsx';
            $writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($this->excel_spreadSheet($this->_viewDatas), 'Xlsx');
            //결과파일저장
            $writer->save(PATHS['EXCEL'] . '/' . $fileName);
            //Download시
            header("Content-Type: application/vnd.ms-excel");
            header(sprintf("Content-Disposition: attachment; filename=%s", urlencode($fileName)));
            header("Expires: 0");
            header("Cache-Control: must-revalidate");
            header("Pragma: public");
            header("Content-Length:" . filesize(PATHS['EXCEL'] . '/' . $fileName));
            // flush();
            // return readfile(PATHS['EXCEL'] . '/' . $fileName);
            return $writer->save('php://output');
        } catch (\Exception $e) {
            return redirect()->to($this->_session->getFlashdata(SESSION_NAMES['RETURN_URL']))->with('return_message', $e->getMessage());
        }
    }
    //File Download관련
    final public function download(string $field, $uid)
    {
        try {
            $entity = $this->_model->getEntity([$this->_model->getPrimaryKey() => $uid]);
            if (!$entity->$field) {
                throw new \Exception("첨부파일이 확인되지 않습니다.");
            }
            list($origin_filename, $filename) = explode(DEFAULTS['DELIMITER_FILE'], $entity->$field);
            if (is_file(WRITEPATH . PATHS['UPLOAD'] . "/" . $origin_filename)) {
                throw new \Exception("파일이 확인되지 않습니다.");
            }
            return $this->response->download(WRITEPATH . PATHS['UPLOAD'] . "/" . $filename, null)->setFileName(date("YmdHms") . '_' . $origin_filename);
        } catch (\Exception $e) {
            return redirect()->to($this->_session->getFlashdata(SESSION_NAMES['RETURN_URL']))->with('return_message', $e->getMessage());
        }
    }
}
