<?php

namespace App\Models;

use CodeIgniter\Model;

abstract class CommonModel extends Model
{
    protected $table            = '';
    protected $primaryKey       = '';
    protected $useAutoIncrement = true;
    protected $returnType       = 'array';

    protected $useSoftDeletes   = false;
    protected $protectFields    = true;
    protected $allowedFields    = [];

    protected bool $allowEmptyInserts = false;
    protected bool $updateOnlyChanged = true;

    protected array $casts = [];
    protected array $castHandlers = [];

    // Dates
    protected $useTimestamps = false;
    protected $dateFormat    = 'datetime';
    protected $createdField  = 'created_at';
    protected $updatedField  = 'updated_at';
    protected $deletedField  = 'deleted_at';

    // Validation
    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 $_action = DB_ACTION["CREATE"];
    protected function __construct()
    {
        parent::__construct();
    }
    abstract public function getTitleField(): string;
    final public function getTable(): string
    {
        return $this->table;
    }
    final public function getPKField(): string
    {
        return $this->primaryKey;
    }
    final public function getAction(): string
    {
        return $this->_action;
    }
    final public function setAction(string $action): string
    {
        return $this->_action = $action;
    }
    final public function getFields(string $key = "", array $fields = []): array
    {
        $allowedFields = array_filter(
            $this->allowedFields,
            function ($value) use (&$key, &$fields) {
                if ($key == 'except') {
                    return !in_array($value, $fields);
                } elseif ($key == 'only') {
                    return in_array($value, $fields);
                } else {
                    return $value;
                }
            }
        );
        return $allowedFields;
    }
    final public function getFieldRules(array $fields, array $rules = []): array
    {
        foreach ($fields as $field) {
            $rules = $this->getFieldRule($field, $rules);
        }
        return $rules;
    }
    public function getFieldRule(string $field, array $rules): array
    {
        switch ($field) {
            case $this->getPKField():
                //수동입력인경우
                if (!$this->useAutoIncrement) {
                    $rules[$field] = "required|regex_match[/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/]";
                    $rules[$field] .= $this->getAction() == DB_ACTION["CREATE"] ? "|is_unique[{$this->table}.{$field}]" : "";
                } else {
                    $rules[$field] = "required|numeric";
                };
                break;
            case $this->getTitleField():
                $rules[$field] =  "required|string";
                break;
            case "passwd":
                $rules[$field] = $this->getAction() == DB_ACTION["CREATE"] ? "required" : "if_exist" . "|trim|string";
                break;
            case "confirmpassword":
                $rules["confirmpassword"] = $this->getAction() == DB_ACTION["CREATE"] ? "required" : "if_exist" . "|trim|string|matches[passwd]";
                break;
            case "email":
                $rules[$field] =  "if_exist|trim|valid_email";
                break;
            case 'image':
                $rules[$field] =  "is_image[{$field}]|mime_in[{$field},image/jpg,image/jpeg,image/gif,image/png,image/webp]|max_size[{$field},300]|max_dims[{$field},2048,768]";
                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 getEntity(): array|object|null
    {
        return $this->asObject($this->returnType)->first();
    }
    final public function getEntitys(): array
    {
        return $this->asObject($this->returnType)->findAll();
    }

    //create , modify 직전 작업용 작업
    final protected function convertEntityData(string $field, array $formDatas): string|int
    {
        if ($formDatas[$field] === null) {
            throw new \Exception(
                sprintf(
                    "\n-------%s FormDatas 오류--------\n%s\n-----------------------\n",
                    __FUNCTION__,
                    var_export($formDatas, true)
                )
            );
        }
        switch ($field) {
            case $this->getPKField():
                //$formDatas에 전달된 값이 없는경우
                if (!array_key_exists($field, $formDatas)) {
                    $randomBytes = bin2hex(random_bytes(32));
                    $value = 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)
                    );
                } else {
                    $value = $formDatas[$field];
                }
                break;
            case "passwd":
                $value = password_hash($formDatas[$field], PASSWORD_DEFAULT);
                break;
            case "confirmpassword":
                $value = password_hash($formDatas[$field], PASSWORD_DEFAULT);
                break;
            case "content":
                $value = htmlentities($formDatas[$field], ENT_QUOTES);
                break;
            case "updated_at":
            case "created_at":
            case "deleted_at":
                $rules[$field] = "if_exist|valid_date";
                break;
            default:
                $value = $formDatas[$field];
                break;
        }
        return $value;
    }

    private function save_process($entity): mixed
    {
        //최종 변경사항이 있으면 저장
        if ($entity->hasChanged()) {
            if (!$this->save($entity)) {
                throw new \Exception(sprintf(
                    "\n------%s SQL오류-----\n%s\n%s\n------------------------------\n",
                    __FUNCTION__,
                    $this->getLastQuery(),
                    var_export($this->errors(), true)
                ));
            }
        }
        return $entity;
    }
    final protected function create_process($entity, array $formDatas): mixed
    {
        //Field에 맞는 Validation Rule 재정의
        $this->setAction(DB_ACTION['CREATE']);
        $this->setValidationRules($this->getFieldRules($this->allowedFields));

        //저장하기 전에 데이터 값 변경이 필요한 Field 
        foreach (array_keys($formDatas) as $field) {
            $entity->$field = $this->convertEntityData($field, $formDatas);
        }
        $entity = $this->save_process($entity);
        //primaryKey가 자동입력이면
        if ($this->useAutoIncrement) {
            $pkField = $this->getPKField();
            $entity->$pkField =  $this->getInsertID();
        }
        // log_message("debug", $this->getTable() . " CREATE 작업 완료");
        return $entity;
    }
    final protected function modify_process($entity, array $formDatas): mixed
    {
        //Field에 맞는 Validation Rule 재정의
        $this->setAction(DB_ACTION['MODIFY']);
        $this->setValidationRules($this->getFieldRules($this->allowedFields));

        //저장하기 전에 데이터 값 변경이 필요한 Field 
        foreach (array_keys($formDatas) as $field) {
            $entity->$field = $this->convertEntityData($field, $formDatas);
        }
        $this->save_process($entity);
        // log_message("debug", $this->getTable() . " MODIFY 작업 완료");
        return $entity;
    }
}
