<?php

namespace apexl\Vault\operators;
use apexl\Vault\interfaces\operators;

abstract class pdo implements operators {

    protected $database;
    protected $host;
    protected $user;
    protected $pass;
    protected $schema;

    protected $sql = '';
    protected $queryFields = [];
    protected $table; //The current primary table

    protected $options;
    protected $statement;
    protected $connectionDetails;
    protected $loggedInUserId;
    public $statementError;
    protected $queryLogging = true;

    /** @var PDO */
    protected $pdo;

    public function __construct($connectionDetails)
    {
        $this->connectionDetails = $connectionDetails;
        $this->setHost($connectionDetails->dbhost ?? '')
            ->setDatabase($connectionDetails->dbname ?? '')
            ->setUser($connectionDetails->dbuser ?? '')
            ->setPass($connectionDetails->dbpass ?? '')
            ->setSchema($connectionDetails->driver ?? '')
            ->setOptions($connectionDetails->options ?? []);
    }

    public function setHost($host)
    {
        $this->host = $host;
        return $this;
    }

    /**
     * Function to set the name of the database we're connecting to
     * @param $name
     * @return $this
     */
    public function setDatabase($name = NULL)
    {
        $this->database = $name;
        return $this;
    }

    /**
     * Function to set the username to connect with
     * @param $user
     * @return $this
     */
    public function setUser($user = NULL)
    {
        $this->user = $user;
        return $this;
    }

    public function setPass($pass = NULL)
    {
        $this->pass = $pass;
        return $this;
    }

    public function setSchema($schema = NULL)
    {
        $this->schema = $schema;
        return $this;
    }

    public function setOptions($options = NULL)
    {
        $this->options = $options;
        return $this;
    }

    public function getDatabase()
    {
        return $this->database;
    }

    public function getUser()
    {
        return $this->user;
    }

    public function getPass()
    {
        return $this->pass;
    }

    public function getTable()
    {
        return isset($this->table) ? $this->table : null;
    }

    /**
     * Initiate the connection to the database.
     * @param bool $test
     * @return $this | boolean
     */
    protected function connection($test = FALSE)
    {
        $dsn = $this->schema . ':host=' . $this->host;
        //allow us to connect to a mysql server, but without a database if one isn't specified.
        if($this->database){
            $dsn .= ';dbname=' . $this->database;
        }
        //don't store the PDO connection, we just want to know if we can open a connection or not.
        if($test){
            $pdo = new \PDO($dsn, $this->user, $this->pass, $this->options);
            //if the above doesn't fatal, close the connection and return TRUE since we could connect.
            $pdo = null;
            return TRUE;
        } else {
            $this->pdo = new \PDO($dsn, $this->user, $this->pass, $this->options);
        }
        return $this;
    }

    /**
     * Function to execute the query;
     * @param array $fields
     * @param bool $empty
     * @return $this
     */
    public function execute(){
        //Allow us to connect as late as possible.
        if(empty($this->pdo)){
            $this->connection();
        }
        $prepare = $this->pdo->prepare($this->sql);
        if(!$prepare){
            $error = $this->pdo->errorInfo();
        }
        $this->statement = $prepare;
        $this->statement->execute($this->queryFields);
        $this->statementError = $this->statement->errorInfo();
        //echo "\n\n\n\n".$this->sql . ' ';
        //print_r($this->queryFields);
        //if (strpos($this->sql, "BillingDataId") !== false) header('last-sql:'.$this->sql);
        if ($this->queryLogging) $this->logQuery($this->sql, $this->queryFields, $this->statementError);
        if($this->statementError[0] != '00000'){
            echo $this->sql . ' ';
            error_log($this->sql);
            throw new \Exception(implode(', ', $this->statementError));
        }
        return $this;
    }

    // This should be used only with migrations,  otherwise for safety execute should be used
    public function executeRaw($sql)
    {
        $this->sql = $sql;
        $this->queryFields = [];
        $this->queryLogging = false;
        $this->execute();
        $this->queryLogging = true;

        if ($this->statementError[0] == '00000') {
            return [true, null];
        } else {
            return [false, implode(', ', $this->statementError)];
        }
    }

    public function testConnection(){
        return $this->connection(TRUE);
    }

    /**
     * @return mixed
     */
    public function fetchAll(){
        return $this->statement->fetchAll(\PDO::FETCH_OBJ);
    }

    /**
     * @return array
     */
    public function fetchAssoc(){
        return $this->statement->fetch(\PDO::FETCH_ASSOC);
    }

    /**
     * Kill the DB connection only on destruct.
     * Magic __destruct function
     */
    public function __destruct(){
        $this->pdo = null;
    }

    public function setLoggedInUserId($userId)
    {
        $this->loggedInUserId = $userId;
    }

    public function logQuery($query, $parameters, $error)
    {
        try {
            if ($this->isUpsert($query)) {

                $sql = "INSERT INTO log_entity_changes
                SET user_id= :user_id,
                table_name= :table_name,
                query = :query,
                parameters= :parameters,
                error= :error";
                $params = [
                    'user_id' => $this->loggedInUserId ? $this->loggedInUserId : null,
                    'table_name' => $this->getTable(),
                    'query' => $query,
                    'parameters' => json_encode($parameters),
                    'error' => $error[0] != '00000' ? implode(', ', $error) : null,
                ];

                if (empty($this->pdo)) {
                    $this->connection();
                }
                $prepare = $this->pdo->prepare($sql);
                if (!$prepare) {
                    $error = $this->pdo->errorInfo();
                }
                $statement = $prepare;
                $statement->execute($params);
                $statementError = $statement->errorInfo();
                if ($error[0] != '00000') {
                    echo $sql . ' ';
                    print_r($params);
                    error_log($sql);
                    throw new \Exception(implode(', ', $statementError));
                }
            }
        } catch (\Exception $e) {
            error_log('[VAULT] '.$e->getMessage(). " | File:".$e->getFile(). " | Line: ".$e->getLine());
        }
    }

    protected function isUpsert($sql)
    {
        $keywords = ['INSERT', 'UPDATE'];
        foreach ($keywords as $search) {
            if (strpos($sql, $search) !== false) return true;
        }

        return false;
    }

    public function isCount()
    {
        return stripos($this->sql, "count(") !== false;
    }

}