<?php

namespace Shm\ShmDB\SQLLite;

use PDO;
use PDOException;
use Shm\ShmUtils\ShmInit;

class SQLiteInstance
{
    private static string $defaultFileName = 'shm.sqlite';

    private PDO $pdo;

    /** @var string|null */
    private $lastError = null;

    public function __construct(string $dbname = '')
    {
        $dbPath = self::resolveDbPath($dbname);

        $dir = dirname($dbPath);
        if (!is_dir($dir)) {
            @mkdir($dir, 0775, true);
        }

        try {
            $this->pdo = new PDO(
                'sqlite:' . $dbPath,
                null,
                null,
                [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                    PDO::ATTR_EMULATE_PREPARES => false,
                ]
            );
            $this->lastError = null;
        } catch (PDOException $e) {
            $this->lastError = $e->getMessage();
            $this->reportError();
            echo 'no database connect';
            exit;
        }
    }

    public function reportError($sql = ''): void
    {
        $lastError = $this->getLastError();
        if ($lastError) {
            $message = "Database Error: {$lastError}\nSQL Query: {$sql}";
            try {
                \Sentry\captureException(new \ErrorException($message));
            } catch (\Throwable $e) {
            }
        }
    }

    public function query($sql)
    {
        $this->lastError = null;
        try {
            $stmt = $this->pdo->query($sql);
            $this->reportError($sql);
            return $stmt;
        } catch (PDOException $e) {
            $this->lastError = $e->getMessage();
            $this->reportError($sql);
            return false;
        }
    }

    public function select($fields = "*", $from = "", $where = "", $orderby = "", $limit = "")
    {
        if (!$from) {
            echo 'Ошибка при работе с Базой.';
            exit;
        }

        $table = $from;
        $where = ($where != "") ? "WHERE $where" : "";
        $orderby = ($orderby != "") ? "ORDER BY $orderby " : "";
        $limit = ($limit != "") ? "LIMIT $limit" : "";

        $result = $this->query("select $fields FROM $table $where $orderby $limit");
        $this->reportError("select $fields FROM $table $where $orderby $limit");
        return $result;
    }

    public function insert($fields, $table)
    {
        return $this->insertInternal($fields, $table);
    }

    private function insertInternal($fields, $intotable, $fromfields = "*", $fromtable = "", $where = "", $limit = "")
    {
        if (!$intotable) {
            return false;
        }

        $sql = "";

        if (!is_array($fields)) {
            $flds = $fields;
        } else {
            $keys = array_keys($fields);
            $values = array_values($fields);
            $flds = "(" . implode(",", $keys) . ") " . (!$fromtable && $values ? "VALUES('" . implode("','", $values) . "')" : "");

            if ($fromtable) {
                $where = ($where != "") ? "WHERE $where" : "";
                $limit = ($limit != "") ? "LIMIT $limit" : "";
                $sql = "select $fromfields FROM $fromtable $where $limit";
            }
        }

        $rt = $this->query("INSERT INTO $intotable $flds $sql");
        $lid = $this->getInsertId();
        $this->reportError("INSERT INTO $intotable $flds $sql");

        return $lid ? $lid : $rt;
    }

    public function update($fields, $table, $where = "")
    {
        if (!$table) {
            return false;
        }

        if (!is_array($fields)) {
            $flds = $fields;
        } else {
            $flds = '';
            foreach ($fields as $key => $value) {
                if (!empty($flds)) {
                    $flds .= ",";
                }
                $flds .= "`" . $key . "` =";
                $flds .= "'" . $value . "'";
            }
        }

        $where = ($where != "") ? "WHERE $where" : "";

        $result = $this->query("UPDATE $table SET $flds $where");
        $this->reportError("UPDATE $table SET $flds $where");

        return $result;
    }

    public function delete($table, $where = "")
    {
        if (!$table) {
            return false;
        }

        $where = ($where != "") ? "WHERE $where" : "";

        return $this->query("DELETE FROM $table $where");
    }

    public function getRow($stmt)
    {
        if (!$stmt) {
            return false;
        }

        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        return $row ?: false;
    }

    public function makeArray($stmt)
    {
        if (!$stmt) {
            return false;
        }

        return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }

    public function getColumn($name, $stmt)
    {
        if (!$stmt) {
            return [];
        }

        $col = [];
        while ($row = $this->getRow($stmt)) {
            $col[] = $row[$name] ?? null;
        }
        return $col;
    }

    public function escape($s)
    {
        $s = trim((string)$s);
        $quoted = $this->pdo->quote($s, PDO::PARAM_STR);
        if ($quoted === false) {
            return $s;
        }

        if (strlen($quoted) >= 2) {
            return substr($quoted, 1, -1);
        }

        return $s;
    }

    public function getLastError()
    {
        return $this->lastError;
    }

    public function getInsertId()
    {
        return $this->pdo->lastInsertId();
    }

    public function getRecordCount($stmt)
    {
        if (!$stmt) {
            return 0;
        }

        $rowCount = (int)$stmt->rowCount();
        if ($rowCount > 0) {
            return $rowCount;
        }

        try {
            $queryString = $stmt->queryString ?? '';
            if (!$queryString || !preg_match('/^\s*select\b/i', $queryString)) {
                return 0;
            }

            $countStmt = $this->pdo->query("SELECT COUNT(*) as cnt FROM ($queryString) as t");
            $row = $countStmt->fetch(PDO::FETCH_ASSOC);
            return isset($row['cnt']) ? (int)$row['cnt'] : 0;
        } catch (\Throwable $e) {
            return 0;
        }
    }

    public function getTableMetaData($table)
    {
        $metadata = false;
        if (!empty($table)) {
            $sql = "PRAGMA table_info($table)";
            if ($ds = $this->query($sql)) {
                while ($row = $this->getRow($ds)) {
                    $fieldName = $row['name'];
                    $metadata[$fieldName] = $row;
                }
            }
        }
        return $metadata;
    }

    public function haveField($table, $fields = [])
    {
        $db_info = (array)$this->getTableMetaData($table);
        $have_count = 0;

        foreach ($db_info as $db_info_key => $db_info_item) {
            if (in_array($db_info_key, $fields, true)) {
                $have_count++;
            }
        }

        return $have_count == count($fields);
    }

    private static function resolveDbPath(string $dbname): string
    {
        $rootDir = ShmInit::$rootDir;
        $baseDir = $rootDir ? rtrim($rootDir, DIRECTORY_SEPARATOR) : getcwd();

        if ($dbname === '') {
            return $baseDir . '/storage/' . self::$defaultFileName;
        }

        if (str_contains($dbname, '/') || str_contains($dbname, '\\')) {
            return $dbname;
        }

        $fileName = $dbname;
        if (!preg_match('/\.(sqlite|db)$/i', $fileName)) {
            $fileName .= '.sqlite';
        }

        return $baseDir . '/storage/' . $fileName;
    }
}
