<?php

namespace App\Http\Controllers;

use Illuminate\Support\Facades\DB;

class BackupController extends Controller
{
    protected string $backupPath = 'backups';

    public function index()
    {
        $backups = $this->getBackupFiles();

        return view('pages.backups.index', [
            'title' => 'Backup Database',
            'backups' => $backups,
        ]);
    }

    public function store()
    {
        try {
            $filename = $this->createBackup();

            return redirect()->route('backups.index')
                ->with('success', "Backup berhasil dibuat: {$filename}");
        } catch (\Throwable $e) {
            return redirect()->route('backups.index')
                ->with('error', 'Gagal membuat backup: ' . $e->getMessage());
        }
    }

    public function download(string $filename)
    {
        $path = storage_path("app/{$this->backupPath}/{$filename}");

        if (!file_exists($path)) {
            return redirect()->route('backups.index')
                ->with('error', 'File backup tidak ditemukan.');
        }

        return response()->download($path);
    }

    public function destroy(string $filename)
    {
        $filepath = storage_path("app/{$this->backupPath}/{$filename}");

        if (!file_exists($filepath)) {
            return redirect()->route('backups.index')
                ->with('error', 'File backup tidak ditemukan.');
        }

        unlink($filepath);

        return redirect()->route('backups.index')
            ->with('success', 'Backup berhasil dihapus.');
    }

    protected function getBackupFiles(): array
    {
        $backupDir = storage_path("app/{$this->backupPath}");

        if (!is_dir($backupDir)) {
            mkdir($backupDir, 0755, true);
            return [];
        }

        $files = glob("{$backupDir}/*.sql");
        $backups = [];

        foreach ($files as $file) {
            $backups[] = [
                'filename' => basename($file),
                'size' => $this->formatBytes(filesize($file)),
                'created_at' => date('d M Y H:i', filemtime($file)),
                'timestamp' => filemtime($file),
            ];
        }

        usort($backups, function ($a, $b) {
            return $b['timestamp'] <=> $a['timestamp'];
        });

        return $backups;
    }

    protected function createBackup(): string
    {
        $filename = 'backup_' . date('Y-m-d_H-i-s') . '.sql';
        $backupDir = storage_path("app/{$this->backupPath}");
        $filepath = "{$backupDir}/{$filename}";

        if (!is_dir($backupDir) && !mkdir($backupDir, 0755, true) && !is_dir($backupDir)) {
            throw new \Exception('Tidak dapat membuat direktori backup.');
        }

        $database = config('database.connections.mysql.database');

        if (empty($database)) {
            throw new \Exception('Konfigurasi database tidak ditemukan.');
        }

        $handle = fopen($filepath, 'w');
        if (!$handle) {
            throw new \Exception('Tidak dapat membuat file backup.');
        }

        try {
            fwrite($handle, "-- Database Backup\n");
            fwrite($handle, "-- Generated: " . date('Y-m-d H:i:s') . "\n");
            fwrite($handle, "-- Database: {$database}\n\n");
            fwrite($handle, "SET FOREIGN_KEY_CHECKS=0;\n");
            fwrite($handle, "SET SQL_MODE = \"NO_AUTO_VALUE_ON_ZERO\";\n");
            fwrite($handle, "SET AUTOCOMMIT = 0;\n");
            fwrite($handle, "START TRANSACTION;\n\n");

            // Ambil hanya BASE TABLE agar VIEW tidak bikin error "Create Table"
            $tables = DB::select("SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'");
            $tableKey = "Tables_in_{$database}";

            foreach ($tables as $table) {
                $tableArr = (array) $table;
                $tableName = $tableArr[$tableKey] ?? array_values($tableArr)[0] ?? null;

                if (!$tableName) {
                    continue;
                }

                fwrite($handle, "-- --------------------------------------------------------\n");
                fwrite($handle, "-- Table structure for table `{$tableName}`\n");
                fwrite($handle, "-- --------------------------------------------------------\n\n");

                fwrite($handle, "DROP TABLE IF EXISTS `{$tableName}`;\n");

                $createTableResult = DB::select("SHOW CREATE TABLE `{$tableName}`");

                if (!empty($createTableResult)) {
                    $createRow = (array) $createTableResult[0];
                    $createStatement = $createRow['Create Table'] ?? null;

                    if (!$createStatement) {
                        throw new \Exception("Gagal mengambil struktur tabel: {$tableName}");
                    }

                    fwrite($handle, $createStatement . ";\n\n");
                }

                $rows = DB::table($tableName)->get();

                if ($rows->count() > 0) {
                    fwrite($handle, "-- Dumping data for table `{$tableName}`\n\n");

                    $columns = array_keys((array) $rows->first());
                    $columnsList = '`' . implode('`, `', $columns) . '`';

                    foreach ($rows as $row) {
                        $rowArray = (array) $row;
                        $values = array_map([$this, 'escapeValue'], array_values($rowArray));
                        $valuesList = implode(', ', $values);

                        fwrite(
                            $handle,
                            "INSERT INTO `{$tableName}` ({$columnsList}) VALUES ({$valuesList});\n"
                        );
                    }

                    fwrite($handle, "\n");
                }
            }

            fwrite($handle, "COMMIT;\n");
            fwrite($handle, "SET AUTOCOMMIT = 1;\n");
            fwrite($handle, "SET FOREIGN_KEY_CHECKS=1;\n");
        } catch (\Throwable $e) {
            fclose($handle);

            if (file_exists($filepath)) {
                @unlink($filepath);
            }

            throw $e;
        }

        fclose($handle);

        return $filename;
    }

    protected function escapeValue($value): string
    {
        if (is_null($value)) {
            return 'NULL';
        }

        if (is_bool($value)) {
            return $value ? '1' : '0';
        }

        if (is_int($value) || is_float($value)) {
            return (string) $value;
        }

        return DB::getPdo()->quote((string) $value);
    }

    protected function formatBytes(int $bytes, int $precision = 2): string
    {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];

        $bytes = max($bytes, 0);
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
        $pow = min((int) $pow, count($units) - 1);

        $bytes /= pow(1024, $pow);

        return round($bytes, $precision) . ' ' . $units[$pow];
    }
}