<?php

namespace classes;

use Exception;
use mysqli;
use objects\orders_o;

class db
{
    public mysqli $conn;
    private string $host;
    private string $user;
    private string $password;
    private string $database;
    private int $port = 3306;
    private string $ssl_mode = 'DISABLED'; // mysqldump SSL mode (e.g., DISABLED, PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY)

    public function __construct(array $config)
    {
        $this->host = $config['host'];
        $this->user = $config['user'];
        $this->password = $config['password'];
        $this->database = $config['database'];
        if (isset($config['port']) && is_numeric($config['port'])) {
            $this->port = (int)$config['port'];
        }
        if (isset($config['ssl_mode']) && is_string($config['ssl_mode']) && $config['ssl_mode'] !== '') {
            $this->ssl_mode = $config['ssl_mode'];
        }
    }

    public static function getPDO(): \PDO
    {
        global $CONFIG_DB;
        $port = $CONFIG_DB['port'] ?? 3306;
        $dsn = "mysql:host={$CONFIG_DB['host']};port={$port};dbname={$CONFIG_DB['database']};charset=utf8mb4";
        return new \PDO($dsn, $CONFIG_DB['user'], $CONFIG_DB['password'], [
            \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
            \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
            \PDO::ATTR_EMULATE_PREPARES => false,
        ]);
    }

    public function testConnection(): bool
    {
        try {
            $conn = new mysqli($this->host, $this->user, $this->password, $this->database, $this->port);
            if ($conn->connect_error) {
                return false;
            }
            $this->conn = $conn;
            return true;
        } catch (Exception) {
            return false;
        }
    }

    public function connect(): void
    {
        global $response;
        try {
            // Enable error reporting for mysqli to catch connection issues via exceptions
            mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
            $this->conn = new mysqli($this->host, $this->user, $this->password, $this->database, $this->port);
            if ($this->conn->connect_error) {
                throw new Exception($this->conn->connect_error);
            }
        } catch (Exception $e) {
            if ($response) {
                $response->internal_server_error("Database connection failed: " . $e->getMessage());
            } else {
                throw $e;
            }
        }
    }

    public function escape_string(string $string): string
    {
        return $this->conn->real_escape_string($string);
    }

    public function close(): void
    {
        if (!isset($this->conn)) {
            return;
        }

        try {
            $this->conn->close();
        } catch (\Throwable) {
        }
    }

    public function get(string $table, int $id)
    {
        $sql = "SELECT * FROM $table WHERE id = $id";
        $result = $this->query($sql);
        return $this->fetch_assoc($result);
    }

    public function query(string $sql): \mysqli_result|bool
    {
        // If the connection is not established, connect
        return $this->conn->query($sql);
    }

    public function fetch_assoc($result)
    {
        return $result->fetch_assoc();
    }

    public function list_objects(string $table): array
    {
        $sql = "SELECT * FROM $table";
        $result = $this->query($sql);
        return $this->fetch_all($result);
    }

    public function fetch_all($result)
    {
        return $result->fetch_all(MYSQLI_ASSOC);
    }

    public function list_objects_paginated(string $table, int $page, int $limit): array
    {
        $offset = ($page - 1) * $limit;
        $sql = "SELECT * FROM $table LIMIT $limit OFFSET $offset";
        $result = $this->query($sql);
        return $this->fetch_all($result);
    }

    public function count_objects(string $table): int
    {
        $sql = "SELECT COUNT(*) FROM $table";
        $result = $this->query($sql);
        return $result->fetch_row()[0];
    }

    public function insert_id(): int
    {
        return $this->conn->insert_id;
    }

    public function conn(): mysqli
    {
        return $this->conn;
    }

    public function prepare(string $sql): false|\mysqli_stmt
    {
        return $this->conn->prepare($sql);
    }

    public function num_rows(\mysqli_result|bool $result): int|string
    {
        return $result->num_rows;
    }

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

    public function getPassword()
    {
        return $this->password;
    }

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

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

    public function getPort(): int
    {
        return $this->port;
    }

    public function getSslMode(): string
    {
        return $this->ssl_mode;
    }

    public function backupDatabase(string $path): bool
    {
        $mode = strtoupper(trim($this->ssl_mode));
        $sslFlag = '';
        switch ($mode) {
            case 'DISABLED':
                $sslFlag = '--skip-ssl';
                break;
            case 'PREFERRED':
                $sslFlag = '';
                break;
            case 'REQUIRED':
            case 'VERIFY_CA':
            case 'VERIFY_IDENTITY':
            default:
                $sslFlag = '--ssl';
                break;
        }

        $host = escapeshellarg($this->host);
        $user = escapeshellarg($this->user);
        $db   = escapeshellarg($this->database);
        $port = (int)$this->port;
        $sslPart = $sslFlag !== '' ? ($sslFlag . ' ') : '';
        $command = "mysqldump {$sslPart}--single-transaction --quick --routines --triggers --events --hex-blob -h $host -P $port -u $user $db";

        $directory = dirname($path);
        if (!is_dir($directory) && !mkdir($directory, 0770, true) && !is_dir($directory)) {
            return false;
        }

        $environment = array_merge(getenv() ?: [], $_ENV);
        $environment['MYSQL_PWD'] = $this->password;
        $descriptors = [
            0 => ['pipe', 'r'],
            1 => ['file', $path, 'w'],
            2 => ['pipe', 'w'],
        ];

        $process = proc_open($command, $descriptors, $pipes, null, $environment);
        if (!is_resource($process)) {
            return false;
        }

        fclose($pipes[0]);
        $stderr = stream_get_contents($pipes[2]);
        fclose($pipes[2]);
        $return = proc_close($process);

        if ($return !== 0 && is_string($stderr) && $stderr !== '') {
            @file_put_contents($path . '.error.log', $stderr);
        }

        return $return === 0 && is_file($path) && filesize($path) !== false;
    }

    public function getView(string $view): array
    {
        $sql = "SELECT * FROM $view";
        $result = $this->query($sql);
        return $this->fetch_all($result);
    }

    /**
     * Generate a fake ID for a table.
     * Fake ids are not stored in the database, they are generated on every request starting from -1.
     * This is useful for testing purposes.
     * @see orders_o::simulateOrderFromXLVask()
     * @param string $table
     * @return int
     */
    public function getNextFakeId(string $table): int
    {
        // This is a static variable that will be shared across all instances of the class
        static $fakeId = 0; // Start from 0 and decrement on each call
        $fakeId--;
        return $fakeId;
    }


}
