<?php

namespace Salesloo;

if (!defined('ABSPATH')) {
    exit; // Exit if accessed directly.
}

use Salesloo\Collection;

/**
 * database query builder
 */
class Builder
{
    /**
     * database table
     */
    private $table;

    /**
     * table columns
     */
    private $columns;

    /**
     * table attributes
     */
    private $attributes;

    /**
     * $wpdb instance;
     */
    private $wpdb = null;

    /**
     * where clause;
     */
    private $where = [];

    /**
     * query
     */
    private $query;

    /**
     * order
     */
    private $order;

    /**
     * offset
     */
    private $offset = 0;

    /**
     * limit query / results per page
     */
    private $limit = 20;

    /**
     * parameters
     */
    private $parameters;

    /**
     * select clause
     */
    private $select = '*';

    /**
     * join clause
     */
    private $joins = [];

    /**
     * sql command
     */
    private $sql;

    /**
     * model
     */
    private $model;

    /**
     * contsruct
     */
    public function __construct($table, $columns, $attributes, $model)
    {

        global $wpdb;

        $this->wpdb       = $wpdb;
        $this->table      = $wpdb->prefix . $table;
        $this->columns    = $columns;
        $this->attributes = $attributes;
        $this->model      = $model;

        return $this;
    }

    /**
     * get current table columns
     *
     * @return array
     */
    public function get_columns()
    {
        return $this->columns;
    }

    /**
     * order clause
     */
    public function order(...$parameters)
    {
        $params = [];

        if (is_array($parameters[0])) {
            $params = $parameters;
        } else {
            $params[] = [
                strval($parameters[0]),
                isset($parameters[1]) ? $parameters[1] : 'ASC'
            ];
        }

        $order = [];
        foreach ($params as $param) {
            $field = sanitize_text_field($param[0]);
            $type = isset($param[1]) ? sanitize_text_field($param[1]) : 'ASC';
            $type = in_array($type, ['ASC', 'DESC']) ? $type : 'ASC';
            $order[] = "$field $type";
        }

        $order = implode(',', $order);

        $this->order = " ORDER BY $order";

        return $this;
    }

    /**
     * where clause
     *
     * @param  string $column
     * @param  string $value
     * @param  string $operator
     * @return void
     */
    public function where($column, $value, $operator)
    {
        $this->where = "WHERE $column $operator '$value'";

        return $this;
    }

    /**
     * andWhere
     *
     * @param  string $column
     * @param  string $value
     * @param  string $operator
     * @return void
     */
    public function andWhere($column, $value, $operator)
    {
        if (!$this->where) {
            $this->where .= <<<SQL
                WHERE $column $operator "$value"
            SQL;
        } else {
            $this->where .= <<<SQL
                AND $column $operator "$value"
            SQL;
        }

        return $this;
    }

    /**
     * orWhere
     *
     * @param  string $column
     * @param  string $value
     * @param  string $operator
     * @return void
     */
    public function orWhere($column, $value, $operator)
    {
        if (!$this->where) {
            $this->where .= <<<SQL
                WHERE $column $operator "$value"
            SQL;
        } else {
            $this->where .= <<<SQL
                OR $column $operator "$value"
            SQL;
        }

        return $this;
    }

    /**
     * query sql
     */
    public function query($sql, ...$parameters)
    {
        $this->query .= " $sql";
        $this->parameters = $parameters;

        return $this;
    }

    /**
     * select columns
     */
    public function select(...$selects)
    {
        $array_select = [];
        foreach ($selects as $select) {
            if (is_string($select)) {
                if (substr($select, 0, 8) == 'salesloo') {
                    $array_select[] = $this->wpdb->prefix . $select;
                } else {
                    $array_select[] = $select;
                }
            } else if (is_array($select)) {
                foreach ($select as $s) {
                    if (substr($s, 0, 8) == 'salesloo') {
                        $array_select[] = $this->wpdb->prefix . $s;
                    } else {
                        $array_select[] =  $s;
                    }
                }
            } else {
                continue;
            }
        }

        if ($array_select) {
            $this->select = implode(',', $array_select);
            $this->select = str_replace('{prefix}', $this->wpdb->prefix, $this->select);
        }

        return $this;
    }


    /**
     * join clause
     *
     * @param  string $type
     * @param  string $table
     * @param  array $match
     * @return void
     */
    public function join($type, $table, $match)
    {
        $type       = strtoupper($type);
        $table      = $this->wpdb->prefix . $table;
        $match      = $this->table . '.' . $match[0] . ' ' . $match[2] . ' ' . $table . '.' . $match[1];
        $this->joins[] = "$type JOIN $table ON $match";

        return $this;
    }

    /**
     * left join clause
     *
     * @param  string $join_table
     * @param  array $match
     * @return void
     */
    public function leftJoin($join_table, $match)
    {

        $join_table = $this->wpdb->prefix . $join_table;
        $operator   = isset($match[2]) ? sanitize_text_field($match[2]) : '=';
        $match      = $this->wpdb->prefix . $match[0] . ' ' . $operator . ' ' . $this->wpdb->prefix . $match[1];
        $this->joins[] = "LEFT JOIN $join_table ON $match";

        return $this;
    }

    /**
     * right join clause
     *
     * @param  string $join_table
     * @param  array $match
     * @return void
     */
    public function rightJoin($join_table, $match)
    {
        $join_table = $this->wpdb->prefix . $join_table;
        $operator   = isset($match[2]) ? sanitize_text_field($match[2]) : '=';
        $match      = $this->wpdb->prefix . $match[0] . ' ' . $operator . ' ' . $this->wpdb->prefix . $match[1];
        $this->joins[] = "RIGHT JOIN $join_table ON $match";

        return $this;
    }



    /**
     * query paginate
     *
     * @param  int $limit
     * @param  int $page
     * @return void
     */
    public function paginate($limit, $page = 1)
    {
        $limit        = intval($limit);
        $offset       = $limit * (intval($page) - 1);
        $this->offset = $offset;
        $this->limit  = $limit;

        return $this;
    }

    /**
     * prepare sql
     *
     * @param  bool $only_one
     * @return void
     */
    private function prepare($only_one = false)
    {

        $join = '';
        if ($this->joins) {
            $join = implode(' ', $this->joins);
        }
        if ($only_one) {
            $sql = "SELECT $this->select FROM $this->table $join";
        } else {
            $sql = "SELECT SQL_CALC_FOUND_ROWS $this->select FROM $this->table $join";
        }

        if ($this->where) {
            $sql .= "$this->where";
        }

        if ($this->query) {
            $sql .= "$this->query";
        }

        if ($this->order) {
            $sql .= "$this->order";
        }

        $sql .= " LIMIT $this->limit OFFSET $this->offset";

        if ($this->parameters) {
            $sql = $this->wpdb->prepare($sql, $this->parameters);
        }

        $this->sql = $sql;
    }

    /**
     * get all row
     */
    public function get($debug = false)
    {
        $this->prepare();

        $result = $this->wpdb->get_results($this->sql);

        $items = array_map($this->model, $result);

        $collection                   = new Collection($items);
        $collection->count            = $this->wpdb->num_rows;
        $collection->found            = $this->wpdb->get_var('SELECT FOUND_ROWS()');
        $collection->results_per_page = $this->limit;
        $collection->current_num_page = isset($_GET['pg']) ? intval($_GET['pg']) : 1;
        $collection->max_num_pages    = ceil($collection->found / $this->limit);

        $next = intval($collection->current_num_page) + 1;
        $prev = intval($collection->current_num_page) - 1;

        if ($collection->current_num_page <= 1) {
            $prev = false;
        }

        if ($collection->current_num_page >= $collection->max_num_pages) {
            $next = false;
        }

        $collection->pagination = (object)[
            'show'     => $collection->max_num_pages > 1 ? true : false,
            'next'     => $next,
            'prev'     => $prev,
            'next_url' => salesloo_current_query_args(['pg' => $next]),
            'prev_url' => salesloo_current_query_args(['pg' => $prev])
        ];

        if (true === $debug) {
            __debug($this->sql);
        }

        return $collection;
    }

    /**
     * get first row from query
     */
    public function first($debug = false)
    {
        $this->prepare(true);
        $result = $this->wpdb->get_row($this->sql);

        if (true === $debug) {
            __debug($this->sql);
        }

        return call_user_func($this->model, $result);
    }

    /**
     * get
     */
    public function result($debug = false)
    {
        $this->prepare(true);
        $result = $this->wpdb->get_results($this->sql);

        if (true === $debug) {
            __debug($this->sql);
        }

        return $result;
    }

    /**
     * set data
     */
    public function data($data)
    {

        $inserted_data = [];
        $formats = [];
        foreach ((array) $this->columns as $column => $type) {

            if (!isset($data[$column])) continue;

            $value = $data[$column];

            if ('integer' == $type) {
                $value = intval($value);
            } else if ('array' == $type) {
                $value = \maybe_serialize($value);
            } else if ('price' == $type) {
                $value = floatval($value);
            } else if ('content' == $type) {
                $value = wp_kses_post($value);
                $formats[] = '%s';
            } else {
                if (is_array($value)) {
                    $value = \maybe_serialize($value);
                } else {
                    $value = \sanitize_text_field($value);
                }
            }

            $inserted_data[$column] = $value;
        }

        $this->attributes = $inserted_data;

        return $this;
    }

    /**
     * save data
     */
    public function create()
    {
        if (empty($this->columns)) return false;

        if (empty($this->attributes))
            return new \WP_Error('failed', 'Please provide data to insert');

        $data = [];
        foreach ($this->attributes as $field => $value) {
            $data[$field] = $value === 'NULL' || $value === 'null' ? NULL : $value;
        }

        $insert = $this->wpdb->insert($this->table, $data);

        if (empty($insert))
            return new \WP_Error('failed', 'Failed to insert data');

        return $this->wpdb->insert_id;
    }

    /**
     * update data
     */
    public function update($where = array())
    {

        if (empty($this->columns))
            return new \WP_Error('error', 'Empty database columns');

        if (!is_array($this->attributes))
            return new \WP_Error('error', 'Updated data can\'t be empty');

        if (!is_array($where))
            return new \WP_Error('error', 'Please provide where column');


        /**
         * check if where column is invalid
         */
        foreach ($where as $key => $value) {
            if (!array_key_exists($key, $this->columns)) {
                return new \WP_Error('failed', sprintf('" %s " is invalid column', $key));
            }
        }

        $data = [];
        foreach ($this->attributes as $field => $value) {
            $data[$field] = $value === 'NULL' || $value === 'null' ? NULL : $value;
        }

        $updated = $this->wpdb->update($this->table, $data, $where);

        return $updated;
    }

    /**
     * delete data
     */
    public function delete($where = array())
    {
        if (empty($this->columns)) return false;

        if (empty($where))
            return new \WP_Error('failed', 'Please provide where column');


        /**
         * check if where column is invalid
         */
        foreach ($where as $key => $value) {
            if (!array_key_exists($key, $this->columns)) {
                return new \WP_Error('failed', sprintf('" %s " is invalid column', $key));
            }
        }

        return $this->wpdb->delete($this->table, $where);
    }
}
