<?php

namespace App\Models;

use App\Entities\BaseEntity;
use CodeIgniter\Model;

abstract class BaseModel extends Model
{
    protected $DBGroup          = "default";
    protected $table            = "default";
    protected $primaryKey       = "uid";
    protected $useAutoIncrement = true;
    protected $insertID         = 0;
    protected $returnType       = "array"; //object,array,entity명::class
    protected $useSoftDeletes   = false;
    protected $protectFields    = true;
    protected $allowedFields    = [];

    // Dates
    protected $useTimestamps = true;
    protected $dateFormat    = "datetime";
    protected $createdField  = "created_at";
    protected $updatedField  = "updated_at";
    protected $deletedField  = "deleted_at";

    protected $validationRules      = [];
    protected $validationMessages   = [];
    protected $skipValidation       = false;
    protected $cleanValidationRules = true;

    // Callbacks
    protected $allowCallbacks = true;
    protected $beforeInsert   = [];
    protected $afterInsert    = [];
    protected $beforeUpdate   = [];
    protected $afterUpdate    = [];
    protected $beforeFind     = [];
    protected $afterFind      = [];
    protected $beforeDelete   = [];
    protected $afterDelete    = [];

    //추가변수선언
    private $_className = null;
    private $_userModel = null;
    private $_categoryModel = null;
    protected $_user_options = null;
    protected $_category_options = null;
    protected $_session = null;
    protected $_validation = null;
    protected function __construct(string $className)
    {
        $this->_className = $className;
        parent::__construct();
        $this->allowedFields = ["uid", "updated_at", "created_at"];
        if (!$this->useAutoIncrement) {
            array_push($this->allowedFields, $this->primaryKey);
        }
        $this->validationRules = [];
        $this->_session = \Config\Services::session();
        $this->_validation = \Config\Services::validation();
    }
    abstract public function getTitleField(): string;
    abstract public function getEntity($conditions): BaseEntity;

    final protected function getClassName(): string
    {
        return $this->_className;
    }
    final protected function getUserModel(): UserModel
    {
        if (is_null($this->_userModel)) {
            $this->_userModel = new UserModel();
        }
        return $this->_userModel;
    }
    final protected function getCategoryModel(): CategoryModel
    {
        if (is_null($this->_categoryModel)) {
            $this->_categoryModel = new CategoryModel();
        }
        return $this->_categoryModel;
    }
    final public function getPrimaryKey()
    {
        return $this->primaryKey;
    }
    public function getEntitys(array $conditions = array(), array $orderBys = array()): array
    {
        //dd($orderBys);
        foreach ($orderBys as $orderBy) {
            $this->orderBy($orderBy);
        }
        return $this->where($conditions)->findAll();
    }
    final public function getUUID()
    {
        $randomBytes = bin2hex(random_bytes(32));
        return sprintf(
            '%08s-%04s-%04x-%04x-%12s',
            substr($randomBytes, 0, 8),
            substr($randomBytes, 8, 4),
            substr($randomBytes, 12, 4),
            substr($randomBytes, 16, 4),
            substr($randomBytes, 20, 12)
        );
    }
    final protected function getUUIDFieldRule($condition = 'required'): string
    {
        return "{$condition}|regex_match[/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/]";
    }
    public function getFieldRule(string $field, array $rules, string $action = ""): array
    {
        switch ($field) {
            case $this->primaryKey:
                //수동입력인경우
                if (!$this->useAutoIncrement) {
                    $rules[$field] = $this->getUUIDFieldRule();
                    $rules[$field] .= $action == "insert" ? "|is_unique[{$this->table}.{$field}]" : "";
                }
                break;
            case "user_uid":
                $rules[$field] = $this->getUUIDFieldRule('if_exist');
                break;
            case "passwd":
                $rules[$field] = "if_exist|trim|string";
                if ($action != "") {
                    $rules["confirmpassword"] = "if_exist|trim|string|matches[passwd]";
                }
                break;
            case "view_cnt":
                $rules[$field] = "if_exist|numeric";
                break;
            case "updated_at":
            case "created_at":
            case "deleted_at":
                $rules[$field] = "if_exist|valid_date";
                break;
            default:
                $rules[$field] = "if_exist|string";
                break;
        }
        return $rules;
    }
    final public function getFieldRules(array $fields, string $action = ""): array
    {
        $rules = array();
        foreach ($fields as $field) {
            $rules = $this->getFieldRule($field, $rules, $action);
        }
        return $rules;
    }
    //추후 VersionUP용
    final public function getValidation(array $fields, string $action = "")
    {
        $this->_validation->reset();
        foreach ($fields as $field) {
            $this->_validation->setRule(
                $field,
                lang($this->getClassName() . '.label.' . $field),
                $this->getValidationFieldRule($field, $action)
            );
        }
        return $this->_validation;
    }
    //Field별 Form Option용
    public function getOptions(array $conditions = array(), $options = array()): array
    {
        foreach ($this->getEntitys($conditions) as $entity) {
            $options[$entity->getPrimaryKey()] = $entity->getTitle();
        }
        return $options;
    }
    public function getFieldFormOption(string $field): array
    {
        switch ($field) {
            case 'user_uid':
                if (is_null($this->_user_options)) {
                    $this->_user_options = $this->getUserModel()->getOptions();
                }
                $options = $this->_user_options;
                break;
            case 'category_uid':
                if (is_null($this->_category_options)) {
                    $this->_category_options = $this->getCategoryModel()->getOptions();
                }
                $options = $this->_category_options;
                break;
            default:
                $options = lang($this->getClassName() . '.' . strtoupper($field));
                break;
        }
        if (!is_array($options)) {
            throw new \Exception(__FUNCTION__ . "에서 {$this->getClassName()}의 Field:{$field}의 FormOptionData가 array가 아닙니다.\n" . var_export($options, true));
        }
        return $options;
    }

    //create , modify 직전 작업용 작업
    protected function changeFormData(string $action, string $field, array $formDatas, $entity): BaseEntity
    {
        switch ($field) {
            case $this->primaryKey:
                //primaryKey가 수동입력이고 추가(Reply도 결국엔 create_process를 상용)시에만
                if ($action == 'create' && !$this->useAutoIncrement) {
                    $pk = $this->primaryKey;
                    $entity->$pk = $this->getUUID();
                }
                break;
            case "user_uid": //입력데이터로 있을시 관리툴에서 (사용자,등)추가, 없을시는 입력의 경우에만 자동(장바구니,등)으로 추가
                if (array_key_exists($field, $formDatas) && !is_null($formDatas[$field])) {
                    //관리툴 USERSNS에서 사용자 연동 시 추가기능등에 사용
                    $entity->$field = $formDatas[$field];
                } elseif ($action == 'create' && $this->_session->get(SESSION_NAMES["ISLOGIN"])) {
                    //Front에서 장바구니,게시판등에 추가시 로그온한경우 자동 추가기능등에 사용
                    $auth = $this->_session->get(SESSION_NAMES["AUTH"]);
                    $entity->$field =  $auth[AUTH_FIELDS["ID"]];
                }
                break;
            case "passwd":
                if (array_key_exists($field, $formDatas) && $formDatas[$field]) {
                    $entity->$field = password_hash($formDatas[$field], PASSWORD_DEFAULT);
                }
                break;
            case "content":
                if (array_key_exists($field, $formDatas) && $formDatas[$field]) {
                    $entity->$field = htmlentities($formDatas[$field]);
                }
                break;
            default:
                if (array_key_exists($field, $formDatas) && $formDatas[$field]) {
                    $entity->$field = $formDatas[$field];
                }
                break;
        }
        return $entity;
    }

    final protected function save_process($entity)
    {
        // echo var_export($entity, true);
        // exit;
        if ($entity->hasChanged()) {
            if (!$this->save($entity)) {
                log_message("error", __FUNCTION__ . "에서 호출:" . $this->getLastQuery());
                log_message("error", implode("\n", $this->errors()));
                throw new \Exception(__FUNCTION__ . " 오류 발생.\n" . $this->getLastQuery() . "\n" . var_export($this->errors(), true));
            }
        } else {
            throw new \Exception(__FUNCTION__ . " 오류 발생.\n 기존정보와 동일하여 수정되지 않았습니다.");
        }
        return $entity;
    }
    protected function create_process($entity, array $formDatas)
    {
        foreach ($this->allowedFields as $field) {
            $entity = $this->changeFormData('create', $field, $formDatas, $entity);
        }
        $entity = $this->save_process($entity);
        //primaryKey가 자동입력이면
        if ($this->useAutoIncrement) {
            $pk = $this->primaryKey;
            $entity->$pk = $this->insertID();
        }
        return $entity;
    }
    final protected function modify_process($entity, array $formDatas)
    {
        foreach ($this->allowedFields as $field) {
            $entity = $this->changeFormData('modify', $field, $formDatas, $entity);
        }
        $entity->updated_at = time();
        return $this->save_process($entity);
    }

    //Index관련
    public function setIndexFieldFilter(string $field, $value): void
    {
        if ($value !== DEFAULTS['EMPTY']) {
            $this->where($field, $value);
        }
    }
    public function setIndexWordFilter(string $word): void
    {
        if ($word !== DEFAULTS['EMPTY']) {
            $this->orLike($this->getTitleField(), $word, "both");
        }
    }
    public function setIndexDateFilter($start, $end): void
    {
        if ($start !== DEFAULTS['EMPTY'] && $end !== DEFAULTS['EMPTY']) {
            $this->where(["created_at >={$start}", "created_at <={$end}"]);
        }
    }
    public function setIndexOrderBy(string $field, string $order): void
    {
        $this->orderBy($field, $order);
    }
}
