<?php

namespace lib\Core;

use \PDO;
use PDOException;
use PDOStatement;

abstract class Model
{
    private $_db = null;
    private $_debug = false;
    private $_reset = true;
    private $_querys = ["SELECT" => "SELECT *", "JOIN" => "", "ORDERBY" => "", "LIMIT" => ""];
    private $_wheres = [];
    private $_lastQuery = "";
    protected function __construct() {} //

    abstract public function getTable(): string;
    abstract public function getPKField(): string;
    abstract public function getTitleField(): string;
    final public function getConnect(): PDO
    {
        if ($this->_db === null) {
            $debug = $_ENV['DATABASE_QUERY_DEBUG'] ?? $_SERVER['DATABASE_QUERY_DEBUG'] ?? 'false';
            if ($debug === "true") {
                $this->_debug = true;
            }
            echo $this->_debug ? "TRUE" : "FALSE";
            $driver  = $_ENV['DATABASE_DRIVER'] ?? $_SERVER['DATABASE_DRIVER'] ?? 'mysql';
            $host    = $_ENV['DATABASE_HOST'] ?? $_SERVER['DATABASE_HOST'] ?? 'localhost';
            $dbname  = $_ENV['DATABASE_DB'] ?? $_SERVER['DATABASE_DB'] ?? 'test';
            $charset = $_ENV['DATABASE_CHARSET'] ?? $_SERVER['DATABASE_CHARSET'] ?? 'utf8';
            $user    = $_ENV['DATABASE_ID'] ?? $_SERVER['DATABASE_ID'] ?? 'root';
            $pass    = $_ENV['DATABASE_PASSWORD'] ?? $_SERVER['DATABASE_PASSWORD'] ?? '';
            $dsn = sprintf("%s:host=%s;dbname=%s;charset=%s", $driver, $host, $dbname, $charset);
            try {
                $this->_db = new PDO($dsn, $user, $pass);
                $this->_db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
                $this->_db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            } catch (PDOException $e) {
                throw new \Exception("❌ DB 연결 실패: " . $e->getMessage());
            }
        }
        return $this->_db;
    }
    final public function setDebug($debug)
    {
        $this->_debug = $debug;
    }
    final public function getDebug()
    {
        return $this->_debug;
    }
    final public function getLastQuery(): string
    {
        return $this->_lastQuery;
    }
    private function getValue(mixed $values): string|null
    {
        if ($values === null) {
            return $values;
        }
        $temps = [];
        if (is_array($values)) {
            foreach ($values as $value) {
                $temps[] = is_string($value) ? "'{$value}'" : $value;
            }
        } else {
            $temps[] = is_string($values) ? "'{$values}'" : $values;
        }
        return implode(",", $temps);
    }
    final public function makeQuery(mixed $columns, mixed $values = null, $delimeter = ","): string
    {
        $temps = [];
        if (is_array($columns)) {
            foreach ($columns as $column => $value) {
                $value   = $this->getValue($value);
                $temps[] = sprintf("%s%s", $column, $value === null ? "" : "=" . $value);
            }
        } else {
            $value   = $this->getValue($values);
            $temps[] = sprintf("%s%s", $columns, $value === null ? "" : "=" . $value);
        }
        // throw new \Exception("DATA:" . $columns . $value === null ? "NULL" : $value);
        return implode($delimeter . " ", $temps);
    }
    final public function where(mixed $columns, mixed $values = null, string $delimeter = "AND"): void
    {
        $query = $this->makeQuery($columns, $values, $delimeter);
        $this->_wheres[] = count($this->_wheres) ? $delimeter . " " . $query : $query;
    }
    final public function orWhere(mixed $columns, mixed $values = null, string $delimeter = "OR"): void
    {
        $this->where($columns, $values, $delimeter);
    }
    final public function whereIn(string $column, array $values, string $delimeter = "AND", $range = "IN")
    {
        $this->_wheres[] = "{$delimeter} {$column} {$range} (" . $this->getValue($values) . ")";
    }
    final public function whereNotIn(string $column, array $values, string $delimeter = "AND", $range = "NOT IN")
    {
        $this->whereIn($column, $values, $delimeter, $range);
    }
    final public function getWhere(): string
    {
        return count($this->_wheres) ? "WHERE " . implode(" ", $this->_wheres) : "";
    }

    final public function execute(string $query): bool|PDOStatement
    {
        if ($this->_debug) {
            echo "\nQuery:" . $query . "\n";
        }
        $this->_lastQuery = $query;
        $stmt = $this->getConnect()->prepare($query);
        $stmt->execute();
        if ($this->_reset) {
            $this->_wheres = [];
            $this->_querys = ["SELECT" => "SELECT *", "JOIN" => "", "ORDERBY" => "", "LIMIT" => ""];
        }
        return $stmt;
    }

    //CURD문
    final protected function create_process(mixed $columns, mixed $values = null): bool|PDOStatement
    {
        $query = sprintf("INSERT INTO %s VALUES(%s) %s", $this->getTable(), $this->makeQuery($columns, $values), $this->getWhere());
        return $this->execute($query);
    } //
    final protected function modify_process(mixed $columns, mixed $values = null): bool|PDOStatement
    {
        $query = sprintf("UPDATE %s SET %s %s", $this->getTable(), $this->makeQuery($columns, $values), $this->getWhere());
        return $this->execute($query);
    } //
    final protected function delete_process(): bool|PDOStatement
    {
        $query = sprintf("DELETE FROM %s %s", $this->getTable(), $this->getWhere());
        return $this->execute($query);
    } //
    public function select(mixed $columns = "*"): void
    {
        $columns                 = is_array($columns) ? implode(",", $columns) : $columns;
        $this->_querys["SELECT"] = "SELECT {$columns}  FROM {$this->getTable()} WHERE {$this->getWhere()}";
    }
    final public function join(string $table, mixed $match = null, $type = ""): void
    {
        $this->_querys["JOIN"] = " {$type} JOIN {$table} ON {$match}";
    }
    final public function orderBy(string $column, string $direction = "DESC"): void
    {
        $this->_querys["ORDERBY"] = " ORDER BY {$column} {$direction}";
    }
    final public function limit(int $start, int $offset = 0): void
    {
        $offset = $offset > 0 ? ",{$offset}" : "";
        $this->_querys["LIMIT"] = " LIMIT {$start} {$offset}";
    }

    //Result
    protected function getResult($mode = PDO::FETCH_ASSOC): mixed
    {
        $query = "{$this->_querys['SELECT']} FROM {$this->getTable()} {$this->getWhere()} {$this->_querys['JOIN']} {$this->_querys['LIMIT']} {$this->_querys['ORDERBY']}";
        $stmt = $this->execute($query);
        return $stmt->fetch($mode);
    }
    protected function getResults($mode = PDO::FETCH_ASSOC): mixed
    {
        $query = "{$this->_querys["SELECT"]} FROM {$this->getTable()} {$this->getWhere()} {$this->_querys['JOIN']} {$this->_querys['ORDERBY']} {$this->_querys['LIMIT']}";
        $stmt = $this->execute($query);
        return $stmt->fetchAll($mode);
    }
    final public function countAllResults(string $column = "*", $reset = true): int
    {
        $this->_reset = $reset;
        $query        = "SELECT COUNT({$column}) FROM {$this->getTable()} {$this->getWhere()} {$this->_querys['JOIN']}";
        $stmt = $this->execute($query);
        $count = $stmt->fetchColumn(0);
        $this->_reset = true;
        return $count;
    }
} //Class