<?php

/**
 * Escape a single value for INSERT ... VALUES (PDO MySQL).
 */
function db_backup_sql_escape_value(PDO $pdo, $value): string
{
    if ($value === null) {
        return 'NULL';
    }
    if ($value === false) {
        return "'0'";
    }
    if ($value === true) {
        return "'1'";
    }
    $q = $pdo->quote((string) $value);
    return $q === false ? "''" : $q;
}

/**
 * Build a unique backup path under $backupDir (avoids two cron hits in the same second clobbering the same file).
 */
function db_backup_alloc_filepath(string $backupDir): string
{
    $dir = rtrim($backupDir, '/\\') . DIRECTORY_SEPARATOR;
    for ($i = 0; $i < 8; $i++) {
        $name = 'backup_' . date('Y-m-d_H-i-s') . '_' . bin2hex(random_bytes(4)) . '.sql';
        $path = $dir . $name;
        if (!file_exists($path)) {
            return $path;
        }
    }
    return $dir . 'backup_' . date('Y-m-d_H-i-s') . '_' . bin2hex(random_bytes(8)) . '.sql';
}

/**
 * Remove abandoned *.sql.part files (e.g. after a crash mid-export).
 */
function db_backup_cleanup_stale_part_files(string $backupDir, int $maxAgeSeconds = 7200): void
{
    $dir = rtrim($backupDir, '/\\') . DIRECTORY_SEPARATOR;
    $files = glob($dir . '*.sql.part');
    if (!is_array($files)) {
        return;
    }
    $cut = time() - max(60, $maxAgeSeconds);
    foreach ($files as $p) {
        if (is_file($p) && @filemtime($p) !== false && filemtime($p) < $cut) {
            @unlink($p);
        }
    }
}

/**
 * Dump the current database (the one $pdo is connected to) to a .sql file without exec/shell.
 * Writes to a temporary ".sql.part" file then renames atomically so no other process sees a half-written .sql.
 *
 * @return array{ok:bool,error?:string}
 */
function db_backup_export_sql(PDO $pdo, string $targetFile): array
{
    $dir = dirname($targetFile);
    if (!is_dir($dir) && !@mkdir($dir, 0755, true)) {
        return ['ok' => false, 'error' => 'ایجاد پوشهٔ پشتیبان ممکن نیست'];
    }

    $partFile = $targetFile . '.part';
    @unlink($partFile);
    $fh = @fopen($partFile, 'wb');
    if ($fh === false) {
        return ['ok' => false, 'error' => 'نوشتن فایل پشتیبان ممکن نیست'];
    }

    fwrite($fh, "-- SQL backup (PHP, no exec/mysqldump)\n");
    fwrite($fh, '-- Generated: ' . gmdate('Y-m-d H:i:s') . " UTC\n\n");
    fwrite($fh, "SET NAMES utf8mb4;\n");
    fwrite($fh, "SET sql_mode = '';\n");
    fwrite($fh, "SET FOREIGN_KEY_CHECKS=0;\n\n");

    try {
        $stmt = $pdo->query('SHOW TABLES');
        $tables = $stmt ? $stmt->fetchAll(PDO::FETCH_COLUMN) : [];
        if (!is_array($tables)) {
            $tables = [];
        }

        foreach ($tables as $table) {
            $t = str_replace('`', '``', (string) $table);
            $stmt = $pdo->query("SHOW CREATE TABLE `$t`");
            $row = $stmt ? $stmt->fetch(PDO::FETCH_ASSOC) : false;
            if (!$row || empty($row['Create Table'])) {
                continue;
            }

            fwrite($fh, "-- Table `$t`\n");
            fwrite($fh, "DROP TABLE IF EXISTS `$t`;\n");
            fwrite($fh, $row['Create Table'] . ";\n\n");

            $batchSize = 500;
            $offset = 0;
            while (true) {
                $sql = 'SELECT * FROM `' . $t . '` LIMIT ' . (int) $batchSize . ' OFFSET ' . (int) $offset;
                $q = $pdo->query($sql);
                $rows = $q ? $q->fetchAll(PDO::FETCH_ASSOC) : [];
                if ($rows === [] || $rows === false) {
                    break;
                }
                foreach ($rows as $r) {
                    $cols = [];
                    foreach (array_keys($r) as $c) {
                        $cols[] = '`' . str_replace('`', '``', (string) $c) . '`';
                    }
                    $vals = [];
                    foreach (array_values($r) as $v) {
                        $vals[] = db_backup_sql_escape_value($pdo, $v);
                    }
                    fwrite($fh, 'INSERT INTO `' . $t . '` (' . implode(',', $cols) . ') VALUES (' . implode(',', $vals) . ");\n");
                }
                if (count($rows) < $batchSize) {
                    break;
                }
                $offset += $batchSize;
            }
            fwrite($fh, "\n");
        }

        fwrite($fh, "SET FOREIGN_KEY_CHECKS=1;\n");
        fclose($fh);

        if (file_exists($targetFile)) {
            @unlink($targetFile);
        }
        if (!@rename($partFile, $targetFile)) {
            if (@copy($partFile, $targetFile)) {
                @unlink($partFile);
            } else {
                @unlink($partFile);
                return ['ok' => false, 'error' => 'تکمیل فایل پشتیبان (rename) ناموفق بود'];
            }
        }

        return ['ok' => true];
    } catch (Throwable $e) {
        if (isset($fh)) {
            @fclose($fh);
        }
        @unlink($partFile);
        @unlink($targetFile);

        return ['ok' => false, 'error' => $e->getMessage()];
    }
}

/**
 * Keep only the $keep newest backup_*.sql files in $backupDir (by mtime), delete the rest.
 */
function db_backup_rotate_files(string $backupDir, int $keep = 50): void
{
    $keep = max(0, $keep);
    if ($keep === 0 || !is_dir($backupDir)) {
        return;
    }
    $dir = rtrim($backupDir, '/\\');
    $pattern = $dir . DIRECTORY_SEPARATOR . 'backup_*.sql';
    $files = glob($pattern);
    if (!is_array($files) || $files === []) {
        return;
    }
    usort($files, static function ($a, $b) {
        $ma = @filemtime($a) ?: 0;
        $mb = @filemtime($b) ?: 0;
        return $mb <=> $ma;
    });
    foreach (array_slice($files, $keep) as $path) {
        if (is_file($path)) {
            @unlink($path);
        }
    }
}
