<?php

namespace Hura8\System\Model;

use Hura8\Database\iConnectDB;
use Hura8\Interfaces\AppResponse;
use Hura8\Interfaces\EntityType;
use Hura8\Interfaces\iEntityLanguageModel;
use Hura8\Interfaces\iEntityModel;
use Hura8\System\Config;
use Hura8\System\Controller\UrlManagerController;
use Hura8\System\ModuleManager;

abstract class EntityLanguageModel implements iEntityLanguageModel
{

    /* @var iConnectDB $db */
    protected  $db;

    protected  $entity_type = '';
    protected  $tb_entity = "";

    protected  $tb_entity_lang_template  = "tb_entity_lang_template";
    protected  $tb_track_entity_lang     = "tb_track_entity_lang";

    // these fields are set in config file at: config/client/language_fields.php
    protected $language_fields = [
        //"title",
        //"summary",
    ];

    protected  $tb_entity_lang = "";

    protected  $language = ''; // to be set by controllers

    protected $allow_richtext_fields = []; // only table's column fields in this list allowed to retain html tags, else strip all


    public function __construct($entity_type, $tb_entity = "", $allow_richtext_fields = [])
    {
        $this->entity_type      = $entity_type;
        $this->tb_entity        = $this->inferTbEntity($tb_entity);
        $this->tb_entity_lang   = $this->tb_entity."_lang";

        $this->language_fields  = (Config::getEntityLanguageFields())[$this->tb_entity] ?? [];
        if(!sizeof($this->language_fields)) {
            die("No language_fields found for ".$this->tb_entity." on file: config/client/language_fields.php");
        }

        $this->allow_richtext_fields = $allow_richtext_fields;
        $this->db = get_db('', ENABLE_DB_DEBUG);
    }


    protected function inferTbEntity(string $tb_entity = '') : string
    {
        if(!$tb_entity) {
            // auto infer
            $tb_name = str_replace("-", "_", preg_replace("/[^a-z0-9_\-]/i", "", $this->entity_type));
            return "tb_".strtolower($tb_name);
        }

        return $tb_entity;
    }


    // set language to be used by model
    public function setLanguage($language) : bool
    {
        $this->language = $language;
        return true;
    }


    public function createTableLang(): AppResponse {
        $result = $this->db->checkTableExist($this->tb_entity_lang);

        // check if table exist
        if(!$result) {
            $this->db->runQuery("CREATE TABLE `".$this->tb_entity_lang."` LIKE `".$this->tb_entity_lang_template."` ");

            // recheck
            $result = $this->db->checkTableExist($this->tb_entity_lang);
        }

        return new AppResponse($result ? 'ok': 'error');
    }


    public function recreateTableLang() {
        if($this->db->checkTableExist($this->tb_entity_lang)) {
            $this->db->runQuery("DROP TABLE `".$this->tb_entity_lang."` ");
        }
        $this->db->runQuery("CREATE TABLE `".$this->tb_entity_lang."` LIKE `".$this->tb_entity_lang_template."` ");
    }


    // any fields to have language: title, summary, description, price, etc...
    public function setLanguageFields(array $language_fields) {
        $this->language_fields = $language_fields;
    }


    public function getLanguageFields() : array {
        return $this->language_fields;
    }


    protected function beforeCreateItem(array $input_info) {
        $info = [];
        foreach ($input_info as $key => $value) {

            if(!in_array($key, $this->language_fields)) continue;

            $info[$key] = $value;
        }

        return (sizeof($info) > 0) ? $info : false;
    }

    protected function beforeUpdateItem($current_item_info, $new_input_info) {
        $info = [];
        $has_change = false;
        foreach ($new_input_info as $key => $value) {
            if(!in_array($key, $this->language_fields)) continue;

            if(!isset($current_item_info[$key]) || $current_item_info[$key] != $value) {
                $has_change = true;
            }

            $info[$key] = $value;
        }


        return ($has_change) ? $info : false;
    }

    public function getEntityType() : string
    {
        return $this->entity_type;
    }

    public function getTranslatedIds(): array
    {
        $query = $this->db->runQuery(
            "SELECT `item_id` FROM `".$this->tb_track_entity_lang."` WHERE `item_type` = ? AND `lang` = ? LIMIT 50000",
            ['s', 's'],
            [ $this->entity_type, $this->language]
        );

        return array_map(function ($item) {
            return $item['item_id'];
        }, $this->db->fetchAll($query));

    }

    /**
     * @description prevent xss attack by stripping all html tags from un-permitted fields (which mostly are)
     * - only fields in ::allow_richtext_fields can keep html tags. Example: description
     * - if value is array (example data=['title' => '', 'description' => html tags ]) ...
    then to keep `description`, both `data` and `description` must be in the ::allow_richtext_fields
    because this method checks on array value recursively
     * @param array $input_info
     * @return array
     */
    protected function cleanRichtextFields(array $input_info) : array {
        $cleaned_info = [];
        foreach ($input_info as $key => $value) {
            if(in_array($key, $this->allow_richtext_fields)) {
                $cleaned_info[$key] = (is_array($value)) ? $this->cleanRichtextFields($value) : $value;
            }else{
                $cleaned_info[$key] = (is_array($value)) ? $this->cleanRichtextFields($value) : strip_tags($value);
            }
        }

        return $cleaned_info;
    }

    public function update($id, array $new_input_info, $search_keyword = "") : AppResponse
    {

        $new_input_info = $this->cleanRichtextFields($new_input_info);

        // clean url_index if any
        if(isset($new_input_info['url_index']) && $new_input_info['url_index']) {
            $new_input_info['url_index'] = UrlManagerController::create_url_index($new_input_info['url_index']);

            // create url for this language if url_index setup
            if(in_array('url_index', $this->language_fields) && isset($new_input_info['url_index'])) {
                $request_path = $this->updateUrl($id, $new_input_info['url_index']);
                $new_input_info['request_path'] = $request_path;

                // request_path must be auto allowed in language_fields if url_index in there. If not, update the language_fields
                // so the beforeCreateItem method can accept it
                // request_path is needed for PublicEntityBaseControllerTraits to build public urls for items in when view the language
                if(!in_array('request_path', $this->language_fields)){
                    $this->setLanguageFields(array_merge($this->language_fields, ['request_path']));
                }
            }
        }


        $current_info = $this->getInfo($id);

        if(!$current_info) {

            $data = $this->beforeCreateItem($new_input_info);
            if(!$data) {
                //debug_var($new_input_info);
                //die("EntityLanguageModel ".$this->entity_type.": beforeCreateItem no data");
                return new AppResponse("error", "EntityLanguageModel ".$this->entity_type.": beforeCreateItem no data");
            }

           // create
            $this->db->insert(
                $this->tb_entity_lang,
                array(
                    "id"  => $id,
                    "lang"  => $this->language,
                    "data" => $data,
                    "create_time"   => CURRENT_TIME,
                    "create_by"     => ADMIN_NAME,
                    "last_update"   => CURRENT_TIME,
                    "last_update_by"     => ADMIN_NAME,
                )
            );

            // track item
            $this->db->insert(
                $this->tb_track_entity_lang,
                array(
                    "item_type"     => $this->entity_type,
                    "item_id"       => $id,
                    "lang"          => $this->language,
                    "create_time"   => CURRENT_TIME,
                    "create_by"     => ADMIN_NAME,
                )
            );

            return new AppResponse("ok");
        }

        // update
        $new_change_info = $this->beforeUpdateItem($current_info, $new_input_info);
        if(!$new_change_info) {
            return new AppResponse("error", "EntityLanguageModel ".$this->entity_type.": Nothing changed");
        }


        $updated_info = [
            "data"               => array_merge($current_info, $new_change_info), // make sure update 1 key won't affect the other keys in the language
            "last_update"        => CURRENT_TIME,
            "last_update_by"     => ADMIN_NAME,
        ];

        $result = $this->db->update(
            $this->tb_entity_lang,
            $updated_info,
            array(
                "id"  => $id,
                "lang"  => $this->language,
            )
        );

        return new AppResponse("ok", null, $result);
    }


    protected function updateUrl($id, $lang_url_index) {

        /*
         Steps:
            - infer entity model -> get default url setting (url_type, request_path, ...)
            - find RequestPathConfig for entity
            - create RequestPath for this language
            - insert/update in tb-url

        'url_type'      => $url_type,
            'request_path'  => $request_path ,
            'id_path'       => $id_path,
            'redirect_code' => $redirect_code,
         */

        $baseModel = $this->createEntityBaseModelInstance();
        $base_info = $baseModel->getInfo($id);

        if(!$base_info || !$base_info['request_path']) {
            return false;
        }

        //debug_var($base_info['request_path']);

        $objUrlManager = new UrlManagerController();
        $item_url_info = $objUrlManager->getUrlInfoByRequestPath($base_info['request_path']);

        //debug_var($item_url_info);

        $url_module = $item_url_info['module'] ;
        $url_view   = $item_url_info['view'] ;

        $module_routing = ModuleManager::getModuleRouting($url_module);
        $request_path_config = isset($module_routing[$url_view]) ? $module_routing[$url_view]['url_manager']['request_path'] : '';

        if(!$request_path_config) {
            return false;
        }

        $request_path  = UrlManagerController::translateRequestPathConfig($request_path_config, $id, $lang_url_index);

        if($request_path == $item_url_info['request_path']) {
            return false;
        }

        $id_path = $item_url_info['id_path']."/_l:".$this->language;

        $objUrlManager->createUrl($item_url_info['url_type'], $request_path, $id_path, 0);

        return $request_path;
    }


    protected function createEntityBaseModelInstance() : iEntityModel
    {
        $class = EntityType::getModelClass($this->entity_type);
        if(class_exists($class)) {
            return _init_class($class);
        }

        die($class." not exist!");
    }


    // delete all languages for items
    public function deleteAll($id): AppResponse
    {

        $this->db->runQuery(
            "DELETE FROM `".$this->tb_entity_lang."` WHERE `id` = ? ",
            ['d'],
            [$id]
        );

        $this->db->runQuery(
            "DELETE FROM `".$this->tb_track_entity_lang."` WHERE `item_type` = ? AND `item_id` = ? ",
            ['s', 's'],
            [ $this->entity_type, $id]
        );

        return new AppResponse("ok");
    }


    public function delete($id): AppResponse
    {

        $result = $this->db->runQuery(
            "DELETE FROM `".$this->tb_entity_lang."` WHERE `id` = ? AND `lang` = ? LIMIT 1 ",
            ['d', 's'],
            [$id, $this->language], true
        );

        if($result) {
            $this->db->runQuery(
                "DELETE FROM `".$this->tb_track_entity_lang."` WHERE `item_type` = ? AND `item_id` = ? AND `lang` = ? LIMIT 1 ",
                ['s', 's', 's'],
                [ $this->entity_type ,$id, $this->language],
                true
            );
        }

        return new AppResponse("ok", null, $result);
    }


    public function getListByIds(array $list_id): array
    {

        if(!sizeof($list_id)) return array();

        list($parameterized_ids, $bind_types) = create_bind_sql_parameter_from_value_list($list_id, 'int');

        $bind_types[] = "s";
        $bind_values = $list_id;
        $bind_values[] = $this->language;

        $query = $this->db->runQuery(
            " SELECT * FROM ".$this->tb_entity_lang."  WHERE `id` IN (".$parameterized_ids.") AND `lang` = ? ",
            $bind_types,
            $bind_values
        );

        $item_list = [];
        foreach ($this->db->fetchAll($query) as $item) {
            $item_list[$item['id']] = $this->formatItemInfo($item);
        }

        return $item_list;
    }


    public function getInfo($id) : ?array
    {
        $query = $this->db->runQuery(
            "SELECT * FROM `".$this->tb_entity_lang."` WHERE `id` = ? AND `lang` = ?  LIMIT 1 ",
            ['d', 's'],
            [$id, $this->language]
        ) ;

        if( $item_info = $this->db->fetchAssoc($query)){
            return $this->formatItemInfo($item_info);
        }

        return null;
    }


    protected function formatItemInfo(array $item_info) : array
    {
        return ($item_info['data']) ? \json_decode($item_info['data'], true) : [];
    }


    public function getTotal(array $condition): int
    {
        $query = $this->db->runQuery(
            " SELECT COUNT(*) AS total FROM `".$this->tb_entity_lang."` WHERE `lang` = ? " ,
            ['s'], [ $this->language ]
        );
        $total = 0;
        if ($rs = $this->db->fetchAssoc($query)) {
            $total = $rs['total'];
        }

        return $total;
    }


    // get empty/default item for form
    public function getEmptyInfo() : array {
        $empty_info = [
            "id"            => 0,
            "lang"          => $this->language,
            "create_time"   => 0,
            "create_by"     => "",
            "last_update"   => 0,
            "last_update_by" => "",
        ];
        foreach ($this->language_fields as $field) {
            $empty_info[$field] = '';
        }

        return $empty_info;
    }


}
