#!/usr/bin/env php
<?php

/**
 * Firmware Rollback Tool
 * Rolls back firmware to previous versions by removing the latest versions
 */

require_once dirname(__DIR__) . '/api/v1/common.php';

function showUsage()
{
    echo "Usage: php rollback-firmware.php [options]\n";
    echo "Options:\n";
    echo "  --stage STAGE      Stage to rollback (dev, staging, prod)\n";
    echo "  --device DEVICE    Device to rollback (rm-e32-r6-b6, esp32-s3-poe-eth-8di-8ro-c)\n";
    echo "  --steps N          Number of versions to rollback (default: 1)\n";
    echo "  --dry-run          Show what would be rolled back without changes\n";
    echo "  --all-stages       Rollback all stages for specified device\n";
    echo "  --all-devices      Rollback all devices for specified stage\n";
    echo "  --help             Show this help message\n\n";
    echo "Examples:\n";
    echo "  php rollback-firmware.php --stage prod --device rm-e32-r6-b6 --steps 1\n";
    echo "  php rollback-firmware.php --all-stages --device rm-e32-r6-b6 --dry-run\n";
    echo "  php rollback-firmware.php --stage prod --all-devices --steps 2\n";
    echo "\nWARNING: This operation removes firmware files. Use --dry-run first!\n";
}

function logRollback($message)
{
    $logFile = FIRMWARE_BASE_PATH . '/logs/rollback.log';
    $logDir = dirname($logFile);

    if (!is_dir($logDir)) {
        mkdir($logDir, 0755, true);
    }

    $timestamp = date('Y-m-d H:i:s');
    $logEntry = "[$timestamp] $message\n";
    file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
    echo $logEntry;
}

function rollbackFirmware($stage, $device, $steps, $dryRun)
{
    $devicePath = FIRMWARE_BASE_PATH . "/$stage/$device";

    if (!is_dir($devicePath)) {
        logRollback("ERROR: Directory not found: $devicePath");
        return 0;
    }

    // Get all firmware files (sorted newest first)
    $firmware = getFirmwareFiles($stage, $device);
    $totalFiles = count($firmware);

    if ($totalFiles <= $steps) {
        logRollback("ERROR: $stage/$device has only $totalFiles files, cannot rollback $steps steps");
        return 0;
    }

    // Files to remove (the N newest files)
    $filesToRemove = array_slice($firmware, 0, $steps);
    $removeCount = count($filesToRemove);

    logRollback("ROLLBACK: $stage/$device - removing $removeCount newest versions (keeping " . ($totalFiles - $removeCount) . ")");

    $removedFiles = 0;
    $removedBytes = 0;

    // Show what will be removed and what will become the new "latest"
    if (!empty($firmware[$steps])) {
        $newLatest = $firmware[$steps];
        logRollback("NEW LATEST: {$newLatest['bin_file']} (version: {$newLatest['version']})");
    }

    foreach ($filesToRemove as $file) {
        $binPath = $file['full_path'];
        $jsonPath = $binPath . '.json';

        $fileSize = $file['size'];
        $fileName = $file['bin_file'];
        $version = $file['version'] ?? 'unknown';

        if ($dryRun) {
            logRollback("DRY-RUN: Would remove $fileName (version: $version, " . formatBytes($fileSize) . ")");
        } else {
            $success = true;

            // Create backup before removal
            $backupDir = FIRMWARE_BASE_PATH . "/archive/rollback-" . date('Y-m-d-H-i-s');
            if (!is_dir($backupDir)) {
                mkdir($backupDir, 0755, true);
            }

            // Backup .bin file
            if (file_exists($binPath)) {
                $backupPath = "$backupDir/" . basename($binPath);
                if (copy($binPath, $backupPath)) {
                    logRollback("BACKUP: $fileName -> archive/");

                    // Now remove the original
                    if (unlink($binPath)) {
                        $removedBytes += $fileSize;
                        logRollback("REMOVED: $fileName (version: $version)");
                    } else {
                        logRollback("ERROR: Failed to remove $fileName");
                        $success = false;
                    }
                } else {
                    logRollback("ERROR: Failed to backup $fileName, skipping removal");
                    $success = false;
                }
            }

            // Remove .json metadata file
            if (file_exists($jsonPath) && $success) {
                $jsonBackup = "$backupDir/" . basename($jsonPath);
                copy($jsonPath, $jsonBackup);
                unlink($jsonPath);
            }

            if ($success) {
                $removedFiles++;
            }
        }
    }

    if (!$dryRun && $removedFiles > 0) {
        logRollback("ROLLBACK COMPLETE: $stage/$device - removed $removedFiles files, freed " . formatBytes($removedBytes));

        // Verify the new state
        $newFirmware = getFirmwareFiles($stage, $device);
        if (!empty($newFirmware)) {
            $currentLatest = $newFirmware[0];
            logRollback("CURRENT LATEST: {$currentLatest['bin_file']} (version: {$currentLatest['version']})");
        }
    }

    return $removedFiles;
}

function formatBytes($bytes, $precision = 1)
{
    $units = ['B', 'KB', 'MB', 'GB'];

    for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
        $bytes /= 1024;
    }

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

// Parse command line options
$options = getopt('', [
    'stage:', 'device:', 'steps:', 'dry-run', 'all-stages', 'all-devices', 'help'
]);

if (isset($options['help']) || empty($options)) {
    showUsage();
    exit(0);
}

$dryRun = isset($options['dry-run']);
$steps = intval($options['steps'] ?? 1);
$allStages = isset($options['all-stages']);
$allDevices = isset($options['all-devices']);

// Validate steps count
if ($steps < 1) {
    die("ERROR: --steps must be at least 1\n");
}

if ($steps > 10) {
    die("ERROR: --steps cannot be more than 10 (safety limit)\n");
}

// Determine stages to process
$stagesToProcess = [];
if ($allStages) {
    $stagesToProcess = VALID_STAGES;
} else {
    $stage = $options['stage'] ?? null;
    if (!$stage) {
        die("ERROR: --stage required (unless --all-stages used)\n");
    }
    if (!in_array($stage, VALID_STAGES)) {
        die("ERROR: Invalid stage '$stage'. Must be one of: " . implode(', ', VALID_STAGES) . "\n");
    }
    $stagesToProcess = [$stage];
}

// Determine devices to process
$devicesToProcess = [];
if ($allDevices) {
    $devicesToProcess = VALID_DEVICES;
} else {
    $device = $options['device'] ?? null;
    if (!$device) {
        die("ERROR: --device required (unless --all-devices used)\n");
    }
    if (!in_array($device, VALID_DEVICES)) {
        die("ERROR: Invalid device '$device'. Must be one of: " . implode(', ', VALID_DEVICES) . "\n");
    }
    $devicesToProcess = [$device];
}

// Safety confirmation for production
if (in_array('prod', $stagesToProcess) && !$dryRun) {
    echo "⚠️  WARNING: You are about to rollback PRODUCTION firmware!\n";
    echo "This will affect devices in production. Are you sure? [y/N]: ";
    $handle = fopen("php://stdin", "r");
    $line = fgets($handle);
    fclose($handle);

    if (trim($line) !== 'y') {
        die("Rollback cancelled.\n");
    }
}

// Log rollback start
$modeStr = $dryRun ? 'DRY-RUN' : 'EXECUTE';
$stageStr = implode(',', $stagesToProcess);
$deviceStr = implode(',', $devicesToProcess);
logRollback("ROLLBACK START: $modeStr mode, stages=[$stageStr], devices=[$deviceStr], steps=$steps");

// Process rollback
$totalRolledBack = 0;

foreach ($stagesToProcess as $stage) {
    foreach ($devicesToProcess as $device) {
        $removed = rollbackFirmware($stage, $device, $steps, $dryRun);
        $totalRolledBack += $removed;
    }
}

// Log rollback completion
if ($dryRun) {
    logRollback("ROLLBACK COMPLETE: DRY-RUN mode - would have removed $totalRolledBack files");
} else {
    logRollback("ROLLBACK COMPLETE: removed $totalRolledBack files total");
}

exit(0);
