<?php

/**
 * Multi-Product Firmware API - Common Functions
 *
 * This file provides common functions for the multi-product firmware version API system.
 */

// Configuration
define('FIRMWARE_BASE_PATH', '/var/www/firmware');
define('VALID_STAGES', ['dev', 'staging', 'prod']);

// Product and device configuration
define('VALID_PRODUCTS', [
    'climate-controller' => [
        'name' => 'Climate Controller',
        'devices' => ['rm-e32-r6-b6', 'esp32-s3-poe-eth-8di-8ro-c']
    ],
    'adc-module' => [
        'name' => 'ADC Module',
        'devices' => ['esp32-adc-v1', 'esp32-adc-v2']
    ]
]);

/**
 * Validate API parameters for multi-product setup
 */
function validateParameters($product, $stage, $device)
{
    if (!array_key_exists($product, VALID_PRODUCTS)) {
        return ['error' => 'Invalid product. Must be one of: ' . implode(', ', array_keys(VALID_PRODUCTS))];
    }

    if (!in_array($stage, VALID_STAGES)) {
        return ['error' => 'Invalid stage. Must be one of: ' . implode(', ', VALID_STAGES)];
    }

    $validDevices = VALID_PRODUCTS[$product]['devices'];
    if (!in_array($device, $validDevices)) {
        return ['error' => "Invalid device for product '$product'. Must be one of: " . implode(', ', $validDevices)];
    }

    return null; // No error
}

/**
 * Extract version and build type from firmware filename
 */
function extractVersionAndBuildTypeFromFilename(&$fileInfo, $product, $device)
{
    // Extract version and build type from filename
    // Pattern: product-device-version-buildtype.bin
    // Example: climate-controller-rm-e32-r6-b6-213ddf1-dirty-dev.bin

    // First, extract everything after product-device-
    if (preg_match('/' . preg_quote($product) . '-' . preg_quote($device) . '-(.+)\.bin$/', $fileInfo['bin_file'], $matches)) {
        $fullVersion = $matches[1]; // e.g., "213ddf1-dirty-dev"

        // Try to separate version and build type (dev/prod at the end)
        if (preg_match('/^(.+)-(dev|prod)$/', $fullVersion, $subMatches)) {
            $fileInfo['version'] = $subMatches[1]; // e.g., "213ddf1-dirty"
            $fileInfo['build_type'] = $subMatches[2]; // e.g., "dev"
        } else {
            // No clear dev/prod suffix, use full version
            $fileInfo['version'] = $fullVersion;
            $fileInfo['build_type'] = 'unknown';
        }
    } else {
        // Couldn't parse filename, use defaults
        $fileInfo['version'] = 'unknown';
        $fileInfo['build_type'] = 'unknown';
    }
}

/**
 * Get firmware files for a specific product, stage and device
 */
function getFirmwareFiles($product, $stage, $device)
{
    $devicePath = FIRMWARE_BASE_PATH . "/products/$product/$stage/$device";

    if (!is_dir($devicePath)) {
        return [];
    }

    $files = [];
    $pattern = "$devicePath/$product-$device-*.bin";

    foreach (glob($pattern) as $binFile) {
        $jsonFile = $binFile . '.json';
        $fileInfo = [
            'bin_file' => basename($binFile),
            'full_path' => $binFile,
            'size' => filesize($binFile),
            'modified' => filemtime($binFile)
        ];

        // Load metadata if available
        if (file_exists($jsonFile)) {
            $metadata = json_decode(file_get_contents($jsonFile), true);
            if ($metadata) {
                $fileInfo['metadata'] = $metadata;
                // Only use metadata version if it exists, otherwise extract from filename
                if (isset($metadata['version']) && $metadata['version'] !== '') {
                    $fileInfo['version'] = $metadata['version'];
                } else {
                    // No version in metadata, extract from filename
                    extractVersionAndBuildTypeFromFilename($fileInfo, $product, $device);
                }
                // Use metadata build_type if available, otherwise use extracted one
                if (!isset($fileInfo['build_type'])) {
                    $fileInfo['build_type'] = $metadata['build_type'] ?? 'unknown';
                }
            } else {
                // JSON parse failed, extract from filename
                extractVersionAndBuildTypeFromFilename($fileInfo, $product, $device);
            }
        } else {
            // No JSON file, extract from filename
            extractVersionAndBuildTypeFromFilename($fileInfo, $product, $device);
        }

        $files[] = $fileInfo;
    }

    // Sort by modification time, newest first
    usort($files, function ($a, $b) {
        return $b['modified'] - $a['modified'];
    });

    return $files;
}

/**
 * Get the latest firmware version for a product/stage/device
 */
function getLatestFirmware($product, $stage, $device)
{
    $files = getFirmwareFiles($product, $stage, $device);

    if (empty($files)) {
        return null;
    }

    return $files[0]; // First item is the newest
}

/**
 * Compare two semantic version strings
 * Returns: -1 if v1 < v2, 0 if equal, 1 if v1 > v2
 */
function compareSemanticVersions($v1, $v2)
{
    // Handle semantic versions (v1.2.3, v1.2.3-rc1, etc.)
    $v1Clean = preg_replace('/^v/', '', $v1); // Remove leading 'v'
    $v2Clean = preg_replace('/^v/', '', $v2);

    return version_compare($v1Clean, $v2Clean);
}

/**
 * Check if dev stage update is available (using git commits + file info)
 * For dev builds with git commits, we compare based on file metadata
 */
function checkDevUpdateAvailable($currentVersion, $latestVersion, $latestFileInfo)
{
    // For dev stage, we have git commits like "14689cd-dirty"
    // We can't compare git hashes semantically, so we use different strategies

    // Strategy 1: If versions are identical, no update needed
    if ($currentVersion === $latestVersion) {
        return false;
    }

    // Strategy 2: Check if current version exists in server files
    // If current version file doesn't exist on server, update is available
    $currentVersionExists = checkVersionExists($latestFileInfo, $currentVersion);
    if (!$currentVersionExists) {
        return true; // Current version not found on server, update available
    }

    // Strategy 3: Both versions exist, but they're different
    // Since getLatestFirmware() returns the newest by modification time,
    // if current != latest, then update is available
    return $currentVersion !== $latestVersion;
}

/**
 * Check if semantic version update is available (staging/prod)
 */
function checkSemanticUpdateAvailable($currentVersion, $latestVersion)
{
    // Validate that both versions are semantic versions
    if (!isSemanticVersion($currentVersion) || !isSemanticVersion($latestVersion)) {
        return false; // Require semantic versions for staging/prod
    }

    return compareSemanticVersions($currentVersion, $latestVersion) < 0;
}

/**
 * Check if an update is available (hybrid approach)
 * - Dev stage: Use file-based comparison for git commits
 * - Staging/Prod: Use semantic version comparison
 */
function checkUpdateAvailable($currentVersion, $latestVersion, $stage, $latestFileInfo = null)
{
    if (!$currentVersion || !$latestVersion) {
        return false;
    }

    // Stage-specific update logic
    switch ($stage) {
        case 'dev':
            // Development: Allow git commits, use file-based comparison
            return checkDevUpdateAvailable($currentVersion, $latestVersion, $latestFileInfo);

        case 'staging':
        case 'prod':
            // Staging/Production: Require semantic versioning
            return checkSemanticUpdateAvailable($currentVersion, $latestVersion);

        default:
            return false;
    }
}

/**
 * Check if a version string is a semantic version
 */
function isSemanticVersion($version)
{
    // Remove leading 'v' if present
    $clean = preg_replace('/^v/', '', $version);

    // Check if it matches semantic version pattern (major.minor.patch with optional pre-release)
    return preg_match('/^\d+\.\d+\.\d+(-[a-zA-Z0-9\-\.]+)?$/', $clean);
}

/**
 * Check if a version exists in the firmware files list
 */
function checkVersionExists($filesInfo, $version)
{
    if (!is_array($filesInfo)) {
        return false;
    }

    foreach ($filesInfo as $file) {
        if (isset($file['version']) && $file['version'] === $version) {
            return true;
        }
    }

    return false;
}

/**
 * Get download URL for a firmware file
 */
function getDownloadUrl($product, $stage, $device, $filename)
{
    $baseUrl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http')
               . '://' . $_SERVER['HTTP_HOST'];

    return "$baseUrl/products/$product/$stage/$device/$filename";
}

/**
 * Send JSON response
 */
function sendJsonResponse($data, $httpCode = 200)
{
    header('Content-Type: application/json');
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Methods: GET');
    header('Access-Control-Allow-Headers: Content-Type');

    http_response_code($httpCode);
    echo json_encode($data);
    exit;
}

/**
 * Send error response
 */
function sendError($message, $httpCode = 400)
{
    sendJsonResponse(['error' => $message], $httpCode);
}

/**
 * Get client IP for logging
 */
function getClientIp()
{
    if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
        return $_SERVER['HTTP_CLIENT_IP'];
    } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
        return $_SERVER['HTTP_X_FORWARDED_FOR'];
    } else {
        return $_SERVER['REMOTE_ADDR'];
    }
}

/**
 * Log API access
 */
function logAccess($product, $stage, $device, $action = 'version_check')
{
    $logFile = FIRMWARE_BASE_PATH . '/logs/api_access.log';
    $logDir = dirname($logFile);

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

    $timestamp = date('Y-m-d H:i:s');
    $ip = getClientIp();
    $userAgent = $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown';

    $logEntry = "$timestamp | $ip | $action | $product/$stage/$device | $userAgent\n";
    file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX);
}
