<?php

namespace Miladmodaresi\Taamito\Base;

use ArrayAccess;
use JsonSerializable;
use wpdb;

/**
 * @property wpdb $db
 */
class Model extends Singleton implements ArrayAccess
{
    protected $isPlural = true;
    protected $table = '';
    protected $prefix = '';
    protected $columns = '*';
    protected $model = null;
    protected $db = null;
    protected $data = [];
    protected $casts = [];
    protected $query = null;

    public function __construct($data = [])
    {
        global $wpdb;
        $this->db = $wpdb;
        $this->resetQuery();
        if (!$this->prefix)
            $this->prefix = $this->db->prefix;

        if (!$this->table && $this->isPlural)
            $this->table = $this->getPluralPrase(basename(strtolower(get_class($this))));
        if (!$this->table)
            $this->table = basename(strtolower(get_class($this)));
        $this->_prepareData($data);
    }

    private function resetQuery()
    {
        $this->query = [
            'where' => ['sql' => '', 'bind_params' => []],
            'limit' => ['sql' => '', 'bind_params' => []],
            'order_by' => ['sql' => '', 'bind_params' => []],
        ];
    }

    private function _prepareData($data)
    {
        if (count($data))
            foreach ($data as $key => $item) {
                $this->__set($key, $item);
            }
    }
    public function getPluralPrase($phrase)
    {
        $plural = '';
        $twoChar = substr($phrase, strlen($phrase) - 2, 2);
        $oneChar = substr($phrase, strlen($phrase) - 1, 1);
        if ($oneChar == 'y')
            $plural = substr($phrase, 0, strlen($phrase) - 1) . 'ies';
        elseif (in_array($twoChar, ['ch', 'sh', 'z']))
            $plural = substr($phrase, 0, strlen($phrase) - 2) . 'es';
        elseif (in_array($oneChar, ['s', 'x', 'z']))
            $plural = substr($phrase, 0, strlen($phrase) - 1) . 'es';
        else
            $plural = $phrase . 's';

        return $plural;
    }
    public function find($id)
    {
        $this->where('id', $id);
        return $this->first();
    }

    public function findBy($name, $value)
    {
        $this->where($name, $value);
        return $this->first();
    }

    public function first()
    {
        $this->limit(0, 1);
        $this->data = $this->db->get_row($this->getSelectQuery(), ARRAY_A);
        $this->resetQuery();
        return $this;
    }

    public function get()
    {
        $data = $this->db->get_results($this->getSelectQuery(), ARRAY_A);

        foreach ($data as $item) {
            $this->data[] = new self($item);
        }
        $this->resetQuery();
        return $this;
    }


    public function update($data = [])
    {
        if (count($data)) {
            $this->_prepareData($data);
            $this->where('id', $this->data['id']);
            $query = $this->getUpdateQuery();
            $this->db->query($query);
            $this->resetQuery();
            return $this;
        }
        $this->where('id', $this->data['id']);
        $query = $this->getUpdateQuery();
        $this->db->query($query);
        $this->resetQuery();
        return $this;
    }

    public function delete($id = null)
    {
        if ($id) {
            $this->where('id', $this->data['id']);
            $query = $this->getDeleteQuery();
            $this->db->query($query);
            $this->resetQuery();
            return $this;
        }
        $query = $this->getDeleteQuery();
        $this->db->query($query);
        $this->resetQuery();
        return $this;
    }

    public function create($data = [])
    {
        if (count($data )) {
            $this->_prepareData($data);
            $query = $this->getCreateQuery();
            $this->db->query($query);
            $this->resetQuery();
            return $this->getNewInstance();
        }
        $query = $this->getCreateQuery();
        $this->db->query($query);
        $this->resetQuery();
        return $this->getNewInstance();
    }

    private function getNewInstance()
    {
        $result = null;
        $class = get_class($this);
        $class = new $class;
        if ($this->db->insert_id) {
            $result = $class->find($this->db->insert_id);
        }
        return $result;
    }

    private function getSelectQuery()
    {
        $query = "SELECT {$this->columns} FROM {$this->prefix}{$this->table}";
        if (!$this->query['where']['sql'] && !$this->query['order_by']['sql'] && !$this->query['limit']['sql'])
            return $query;
        $bindParams = [];
        if ($this->query['where']['sql']) {
            $query .= ' WHERE ' . $this->query['where']['sql'];
            $bindParams = $this->query['where']['bind_params'];
        }
        // if ($this->query['order_by']['sql']) {
        //     $query .= ' order_by ' . $this->query['order_by']['sql'];
        //     $bindParams = array_merge($this->query['order_by']['bind_params'], $bindParams);
        // }
        if ($this->query['limit']['sql']) {
            $query .= ' limit ' . $this->query['limit']['sql'];
            $bindParams = array_merge($bindParams, $this->query['limit']['bind_params']);
        }

        return  $this->db->prepare($query, $bindParams);
    }

    private function getDeleteQuery()
    {
        $query = "DELETE FROM {$this->prefix}{$this->table}";
        $bindParams = [];
        if ($this->query['where']['sql']) {
            $query .= ' WHERE ' . $this->query['where']['sql'];
            $bindParams = $this->query['where']['bind_params'];
        }
        return  $this->db->prepare($query, $bindParams);
    }

    private function getUpdateQuery()
    {
        $query = '';
        $bindParams = [];

        foreach ($this->data as $key => $item) {
            $query = $query . (!$query ? "`$key` = %s" : ",`$key` = %s");
            $bindParams[] = $item;
        }

        $query = "UPDATE {$this->prefix}{$this->table} SET {$query}";

        if ($this->query['where']['sql']) {
            $query .= ' WHERE ' . $this->query['where']['sql'];
            $bindParams = array_merge($bindParams, $this->query['where']['bind_params']);
        }
        return  $this->db->prepare($query, $bindParams);
    }

    private function getCreateQuery()
    {
        $columnsQuery = '';
        $valuesQuery = '';
        $bindParams = [];

        foreach ($this->data as $key => $item) {
            $columnsQuery = $columnsQuery . (!$columnsQuery ? "(`$key`" : ",`$key`");
            $valuesQuery = $valuesQuery . (!$valuesQuery ? "%s" : ",%s");
            $bindParams[] = $item;
        }
        $columnsQuery .= ')';

        $query = "INSERT INTO {$this->prefix}{$this->table}{$columnsQuery} VALUES({$valuesQuery})";

        return  $this->db->prepare($query, $bindParams);
    }

    public function where($name, $value, $operation = '=')
    {
        $identifier = '';
        if (is_string($value))
            $identifier = '%s';
        else if (is_float($value))
            $identifier = '%f';
        else if (is_int($value))
            $identifier = '%d';

        if (!$this->query['where']['sql']) {
            $this->query['where']['sql'] = "$name $operation $identifier";
            $this->query['where']['bind_params'] = [$value];
            return $this;
        }
        $this->query['where']['sql'] .= " AND $name $operation $identifier";
        $this->query['where']['bind_params'][] = $value;
        return $this;
    }

    public function limit($start = 0, $limit = 1)
    {
        $this->query['limit']['sql'] = "%d,%d";
        $this->query['limit']['bind_params'] = [$start, $limit];
        return $this;
    }

    public function __get($name)
    {
        if (isset($this->casts[$name]))
            return (new $this->casts[$name])->get($this->data[$name]);

        return $this->data[$name];
    }
    public function __set($name, $value)
    {
        if (isset($this->casts[$name])) {
            $this->data[$name] = (new $this->casts[$name])->set($value);
            return $this;
        }
        $this->data[$name] = $value;
        return $this;
    }

    public function exists()
    {
        if ($this->data && count($this->data))
            return true;
        $this->first();
        if ($this->data && count($this->data))
            return true;
        return false;
    }

    public function toArray()
    {
        $results = [];
        if (array_is_list($this->data))
            foreach ($this->data as $key => $item) {
                $results[$key] = $item->toArray();
            }
        else
            foreach ($this->data as $key => $item) {
                $results[$key] = $this->__get($key);
            }
        return $results;
    }

    public function pluck($key, $value = null)
    {
        $results = [];
        if (!$value)
            $value = $key;

        foreach ($this->toArray() as $key2 => $item) {
            $results[$item[$key]] = $item[$value ?? $key];
        }
        return $results;
    }

    public function offsetSet($offset, $value): void
    {
        $this->__set($offset, $value);
    }

    public function offsetExists($offset): bool
    {
        return isset($this->data[$offset]);
    }

    public function offsetUnset($offset): void
    {
        unset($this->data[$offset]);
    }

    public function offsetGet($offset): mixed
    {
        return $this->__get($offset);
    }
}
