<?php
/**
 * Update Performer for SAS Client
 * File: perform_update.php
 * 
 * Handles the actual update process:
 * 1. Downloads update ZIP
 * 2. Validates hash
 * 3. Creates backup
 * 4. Extracts files
 * 5. Runs SQL migrations
 * 6. Updates version
 * 7. Reports back to server
 */

session_start();
require_once 'config.php';

// Only allow logged-in admins
requireLogin();

// Increase execution time for update process
set_time_limit(300); // 5 minutes
ini_set('max_execution_time', 300);

// Define update server URLs (only if not already defined)
if (!defined('UPDATE_DOWNLOAD_URL')) {
    define('UPDATE_DOWNLOAD_URL', 'https://manage.apasaja.my/update_download_api.php');
}
if (!defined('UPDATE_REPORT_URL')) {
    define('UPDATE_REPORT_URL', 'https://manage.apasaja.my/update_report_api.php');
}

// ==========================================
// HELPER FUNCTIONS
// ==========================================

function getCurrentVersion($pdo) {
    try {
        $stmt = $pdo->prepare("SELECT setting_value FROM system_settings WHERE setting_key = 'software_version'");
        $stmt->execute();
        $result = $stmt->fetch();
        return $result ? $result['setting_value'] : '1.0.0';
    } catch (Exception $e) {
        return '1.0.0';
    }
}

function getInstallationId($pdo) {
    try {
        $stmt = $pdo->prepare("SELECT setting_value FROM system_settings WHERE setting_key = 'installation_id'");
        $stmt->execute();
        $result = $stmt->fetch();
        return $result ? $result['setting_value'] : 'unknown';
    } catch (Exception $e) {
        return 'unknown';
    }
}

function getLicenseData() {
    if (!file_exists('license.key')) {
        return null;
    }
    
    $licenseContent = file_get_contents('license.key');
    $key = hash('sha256', $_SERVER['HTTP_HOST'] . $_SERVER['DOCUMENT_ROOT'], true);
    $decoded = base64_decode($licenseContent);
    
    if (strlen($decoded) <= 16) {
        return null;
    }
    
    $iv = substr($decoded, 0, 16);
    $encrypted = substr($decoded, 16);
    $decrypted = openssl_decrypt($encrypted, 'AES-256-CBC', $key, 0, $iv);
    
    if (!$decrypted) {
        return null;
    }
    
    return json_decode($decrypted, true);
}

function reportToServer($installationId, $licenseKey, $domain, $fromVersion, $toVersion, $status, $errorMessage = null, $backupPath = null, $durationSeconds = null) {
    $data = [
        'installation_id' => $installationId,
        'license_key' => $licenseKey,
        'domain' => $domain,
        'from_version' => $fromVersion,
        'to_version' => $toVersion,
        'status' => $status,
        'error_message' => $errorMessage,
        'backup_path' => $backupPath,
        'duration_seconds' => $durationSeconds
    ];
    
    $options = [
        'http' => [
            'header' => "Content-Type: application/json\r\n",
            'method' => 'POST',
            'content' => json_encode($data),
            'timeout' => 10
        ],
        'ssl' => [
            'verify_peer' => false,
            'verify_peer_name' => false
        ]
    ];
    
    $context = stream_context_create($options);
    @file_get_contents(UPDATE_REPORT_URL, false, $context);
}

// ==========================================
// MAIN UPDATE PROCESS
// ==========================================

$response = ['success' => false];
$startTime = time();

try {
    // Get request data
    $toVersion = $_POST['version'] ?? '';
    $expectedHash = $_POST['hash'] ?? '';
    
    if (empty($toVersion) || empty($expectedHash)) {
        throw new Exception('Missing version or hash');
    }
    
    // Get current info
    $currentVersion = getCurrentVersion($pdo);
    $installationId = getInstallationId($pdo);
    $licenseData = getLicenseData();
    
    if (!$licenseData) {
        throw new Exception('No valid license found');
    }
    
    $domain = $_SERVER['HTTP_HOST'];
    $licenseKey = $licenseData['license_key'];
    
    // Report start
    reportToServer($installationId, $licenseKey, $domain, $currentVersion, $toVersion, 'started');
    
    // ==========================================
    // STEP 1: Download Update ZIP
    // ==========================================
    
    $downloadUrl = UPDATE_DOWNLOAD_URL . '?' . http_build_query([
        'license_key' => $licenseKey,
        'domain' => $domain,
        'version' => $toVersion,
        'installation_id' => $installationId,
        'current_version' => $currentVersion
    ]);
    
    $tempDir = __DIR__ . '/temp/';
    if (!is_dir($tempDir)) {
        mkdir($tempDir, 0755, true);
    }
    
    $zipFile = $tempDir . 'update_' . $toVersion . '.zip';
    
    // Use cURL instead of file_get_contents (more reliable on shared hosting)
    $ch = curl_init($downloadUrl);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 300); // 5 minutes
    curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
    
    $zipContent = curl_exec($ch);
    $curl_error = curl_error($ch);
    $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    
    if ($zipContent === false || $http_code !== 200) {
        throw new Exception('Failed to download update from server' . ($curl_error ? ': ' . $curl_error : ' (HTTP ' . $http_code . ')'));
    }
    
    file_put_contents($zipFile, $zipContent);
    
    // ==========================================
    // STEP 2: Validate Hash
    // ==========================================
    
    $actualHash = hash_file('sha256', $zipFile);
    
    if ($actualHash !== $expectedHash) {
        unlink($zipFile);
        throw new Exception('Update file hash mismatch - possible corruption');
    }
    
    // ==========================================
    // STEP 3: Create Backup
    // ==========================================
    
    $backupDir = __DIR__ . '/backups/';
    if (!is_dir($backupDir)) {
        mkdir($backupDir, 0755, true);
    }
    
    $backupFile = $backupDir . 'backup_v' . $currentVersion . '_' . date('Ymd_His') . '.zip';
    
    $zip = new ZipArchive();
    if ($zip->open($backupFile, ZipArchive::CREATE) === TRUE) {
        // Backup critical files/folders
        $filesToBackup = [
            'config.php',
            'license.key',
            'includes/',
            'assets/uploads/'
        ];
        
        foreach ($filesToBackup as $item) {
            $itemPath = __DIR__ . '/' . $item;
            if (file_exists($itemPath)) {
                if (is_dir($itemPath)) {
                    addFolderToZip($zip, $itemPath, $item);
                } else {
                    $zip->addFile($itemPath, $item);
                }
            }
        }
        
        $zip->close();
    } else {
        throw new Exception('Failed to create backup');
    }
    
    reportToServer($installationId, $licenseKey, $domain, $currentVersion, $toVersion, 'backup_created', null, $backupFile);
    
    // ==========================================
    // STEP 4: Extract Update Files
    // ==========================================
    
    $extractDir = $tempDir . 'extract/';
    if (!is_dir($extractDir)) {
        mkdir($extractDir, 0755, true);
    }
    
    $zip = new ZipArchive();
    if ($zip->open($zipFile) === TRUE) {
        $zip->extractTo($extractDir);
        $zip->close();
    } else {
        throw new Exception('Failed to extract update ZIP');
    }
    
    // Copy extracted files to root (excluding config.php and license.key)
    copyUpdateFiles($extractDir, __DIR__);
    
    reportToServer($installationId, $licenseKey, $domain, $currentVersion, $toVersion, 'files_extracted');
    
    // ==========================================
    // STEP 5: Run SQL Migrations
    // ==========================================
    
    $migrationsFile = $extractDir . 'migrations.sql';
    if (file_exists($migrationsFile)) {
        $sql = file_get_contents($migrationsFile);
        
        // Split by semicolon and execute
        $statements = array_filter(array_map('trim', explode(';', $sql)));
        
        foreach ($statements as $statement) {
            if (!empty($statement)) {
                try {
                    $pdo->exec($statement);
                } catch (Exception $e) {
                    // Log but continue
                    error_log("Migration error: " . $e->getMessage());
                }
            }
        }
    }
    
    reportToServer($installationId, $licenseKey, $domain, $currentVersion, $toVersion, 'migrations_run');
    
    // ==========================================
    // STEP 6: Update Version Number
    // ==========================================
    
    $stmt = $pdo->prepare("
        INSERT INTO system_settings (setting_key, setting_value) 
        VALUES ('software_version', ?)
        ON DUPLICATE KEY UPDATE setting_value = ?
    ");
    $stmt->execute([$toVersion, $toVersion]);
    
    // ==========================================
    // STEP 7: Cleanup
    // ==========================================
    
    unlink($zipFile);
    deleteDirectory($extractDir);
    
    // ==========================================
    // STEP 8: Report Success
    // ==========================================
    
    $duration = time() - $startTime;
    reportToServer($installationId, $licenseKey, $domain, $currentVersion, $toVersion, 'completed', null, $backupFile, $duration);
    
    $response = [
        'success' => true,
        'message' => 'Update installed successfully',
        'from_version' => $currentVersion,
        'to_version' => $toVersion,
        'backup_file' => basename($backupFile),
        'duration_seconds' => $duration
    ];
    
} catch (Exception $e) {
    $duration = time() - $startTime;
    
    if (isset($installationId) && isset($licenseKey) && isset($domain)) {
        reportToServer(
            $installationId ?? 'unknown',
            $licenseKey ?? 'unknown',
            $domain ?? 'unknown',
            $currentVersion ?? '1.0.0',
            $toVersion ?? 'unknown',
            'failed',
            $e->getMessage(),
            null,
            $duration
        );
    }
    
    $response = [
        'success' => false,
        'message' => 'Update failed: ' . $e->getMessage()
    ];
}

// ==========================================
// HELPER FUNCTIONS FOR FILE OPERATIONS
// ==========================================

function addFolderToZip($zip, $folderPath, $zipPath) {
    $files = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($folderPath),
        RecursiveIteratorIterator::LEAVES_ONLY
    );
    
    foreach ($files as $file) {
        if (!$file->isDir()) {
            $filePath = $file->getRealPath();
            $relativePath = $zipPath . substr($filePath, strlen($folderPath));
            $zip->addFile($filePath, $relativePath);
        }
    }
}

function copyUpdateFiles($source, $dest) {
    $excludeFiles = ['config.php', 'license.key', '.htaccess'];
    $excludeDirs = ['backups', 'temp', 'cache'];
    
    $dir = @opendir($source);
    if (!$dir) {
        throw new Exception("Failed to open source directory: $source");
    }
    
    while (($file = readdir($dir)) !== false) {
        if ($file === '.' || $file === '..') {
            continue;
        }
        
        if (in_array($file, $excludeFiles) || in_array($file, $excludeDirs)) {
            continue;
        }
        
        $srcPath = $source . '/' . $file;
        $destPath = $dest . '/' . $file;
        
        if (is_dir($srcPath)) {
            if (!is_dir($destPath)) {
                if (!@mkdir($destPath, 0755, true)) {
                    closedir($dir);
                    throw new Exception("Failed to create directory: $destPath");
                }
            }
            copyUpdateFiles($srcPath, $destPath);
        } else {
            if (!@copy($srcPath, $destPath)) {
                closedir($dir);
                throw new Exception("Failed to copy file: $srcPath to $destPath");
            }
        }
    }
    closedir($dir);
    return true;
}

function deleteDirectory($dir) {
    if (!file_exists($dir)) {
        return true;
    }
    
    if (!is_dir($dir)) {
        return unlink($dir);
    }
    
    foreach (scandir($dir) as $item) {
        if ($item == '.' || $item == '..') {
            continue;
        }
        
        if (!deleteDirectory($dir . DIRECTORY_SEPARATOR . $item)) {
            return false;
        }
    }
    
    return rmdir($dir);
}

// ==========================================
// OUTPUT
// ==========================================

header('Content-Type: application/json');
echo json_encode($response);
?>