<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

class UpDb extends Command
{
    protected $signature = 'db:updb
                            {--no-backup : Skip saving a local backup before wiping}
                            {--force : Skip confirmation prompt}';

    protected $description = 'Download the production database and replace the local database with it';

    public function handle(): int
    {
        if (! $this->option('force') && ! $this->confirm('This will wipe your local database and replace it with production. Are you sure?')) {
            $this->info('Aborted.');
            return self::SUCCESS;
        }

        $local      = $this->connectionConfig('local');
        $production = $this->connectionConfig('production');

        $timestamp  = now()->format('Y_m_d_His');
        $dumpPath   = storage_path("app/private/db_dumps/production_{$timestamp}.dump");
        $backupPath = storage_path("app/private/db_dumps/local_backup_{$timestamp}.dump");

        if (! is_dir(storage_path('app/private/db_dumps'))) {
            mkdir(storage_path('app/private/db_dumps'), 0755, true);
        }

        // Step 1: Dump production
        $this->info('Dumping production database...');
        $dumpResult = $this->pgDump($production, $dumpPath);

        if ($dumpResult !== 0) {
            $this->error('Failed to dump production database.');
            return self::FAILURE;
        }

        $this->info("Production dump saved to: {$dumpPath}");

        // Step 2: Back up local (unless skipped)
        if (! $this->option('no-backup')) {
            $this->info('Backing up local database...');
            $backupResult = $this->pgDump($local, $backupPath);

            if ($backupResult !== 0) {
                $this->error('Failed to back up local database. Aborting to be safe. Use --no-backup to skip.');
                return self::FAILURE;
            }

            $this->info("Local backup saved to: {$backupPath}");
        }

        // Step 3: Drop and recreate the local database
        $this->info('Wiping local database...');
        $this->dropAndRecreateLocalDatabase($local);

        // Step 4: Restore production dump into local
        $this->info('Restoring production dump to local database...');
        $restoreResult = $this->pgRestore($local, $dumpPath);

        if ($restoreResult !== 0) {
            $this->error('Restore failed. Your local backup is at: ' . ($this->option('no-backup') ? 'N/A (backup was skipped)' : $backupPath));
            return self::FAILURE;
        }

        $this->info('Done. Local database now mirrors production.');

        return self::SUCCESS;
    }

    private function connectionConfig(string $type): array
    {
        $connection = $type === 'production' ? 'pgsql_production' : config('database.default');

        return [
            'host'     => config("database.connections.{$connection}.host"),
            'port'     => config("database.connections.{$connection}.port"),
            'database' => config("database.connections.{$connection}.database"),
            'username' => config("database.connections.{$connection}.username"),
            'password' => config("database.connections.{$connection}.password"),
        ];
    }

    private function pgDump(array $config, string $outputPath): int
    {
        $pgpass = $this->writePgPass($config);

        $command = sprintf(
            'pg_dump -Fc -h %s -p %s -U %s %s -f %s',
            escapeshellarg($config['host']),
            escapeshellarg($config['port']),
            escapeshellarg($config['username']),
            escapeshellarg($config['database']),
            escapeshellarg($outputPath),
        );

        $resultCode = $this->runWithPgPass($pgpass, $command);

        unlink($pgpass);

        return $resultCode;
    }

    private function pgRestore(array $config, string $dumpPath): int
    {
        $pgpass = $this->writePgPass($config);

        $command = sprintf(
            'pg_restore -h %s -p %s -U %s -d %s --no-owner --no-privileges %s',
            escapeshellarg($config['host']),
            escapeshellarg($config['port']),
            escapeshellarg($config['username']),
            escapeshellarg($config['database']),
            escapeshellarg($dumpPath),
        );

        $resultCode = $this->runWithPgPass($pgpass, $command);

        unlink($pgpass);

        return $resultCode;
    }

    /**
     * Write a temporary pgpass file and return its path.
     * This is the cross-platform alternative to PGPASSWORD which doesn't work on Windows.
     */
    private function writePgPass(array $config): string
    {
        // Escape any colons or backslashes in the password per pgpass format rules
        $password = str_replace(['\\', ':'], ['\\\\', '\\:'], $config['password']);
        $content  = "{$config['host']}:{$config['port']}:*:{$config['username']}:{$password}";
        $path     = tempnam(sys_get_temp_dir(), 'pgpass_');

        file_put_contents($path, $content);
        chmod($path, 0600);

        return $path;
    }

    private function runWithPgPass(string $pgpassFile, string $command): int
    {
        // PGPASSFILE is supported on all platforms including Windows
        putenv("PGPASSFILE={$pgpassFile}");
        passthru($command, $resultCode);
        putenv('PGPASSFILE');

        return $resultCode;
    }

    private function dropAndRecreateLocalDatabase(array $config): void
    {
        $database = $config['database'];

        // Connect to the postgres maintenance database to drop/recreate
        $pdo = new \PDO(
            "pgsql:host={$config['host']};port={$config['port']};dbname=postgres",
            $config['username'],
            $config['password'],
        );

        $pdo->exec("SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = " . $pdo->quote($database));
        $pdo->exec("DROP DATABASE IF EXISTS " . '"' . $database . '"');
        $pdo->exec("CREATE DATABASE " . '"' . $database . '"');
    }
}
