<?php
declare(strict_types=1);

/**
 * File handles files uploaded through uploadiFive
 */
$cms_root_path = "../../../";
require_once $cms_root_path . "includes/compatibility_includer.php";

use Mtc\Cms\Models\PageListItemData;

/** -------- small helpers -------- */
function bad_request(string $msg = 'Invalid request.', int $code = 400): never {
    http_response_code($code);
    header('Content-Type: text/plain; charset=utf-8');
    exit($msg);
}

function ok(string $body): never {
    http_response_code(200);
    header('Content-Type: text/plain; charset=utf-8');
    exit($body);
}

function safe_unlink(string $path): bool {
    return is_file($path) ? @unlink($path) : false;
}

/** -------- validate inputs -------- */
$pageId  = isset($_GET['page_id']) && is_numeric($_GET['page_id']) ? (int)$_GET['page_id'] : null;
$type    = isset($_GET['type']) ? (string)$_GET['type'] : '';
$dataId  = isset($_POST['list_item_data_id']) ? (int)$_POST['list_item_data_id'] : 0;

if (!$pageId || !in_array($type, ['file', 'image'], true) || $dataId <= 0) {
    bad_request();
}

if (!isset($_FILES['pagedata']) || empty($_FILES['pagedata']['name'])) {
    bad_request('No file provided.', 400);
}

$fileNameOriginal = (string)$_FILES['pagedata']['name'];
$tmpName          = (string)$_FILES['pagedata']['tmp_name'];
$fileSize         = (int)($_FILES['pagedata']['size'] ?? 0);
$errorCode        = (int)($_FILES['pagedata']['error'] ?? UPLOAD_ERR_OK);

/** basic upload checks */
if ($errorCode !== UPLOAD_ERR_OK) {
    bad_request('Upload error code: '.$errorCode, 400);
}
if ($fileSize <= 0 || !is_uploaded_file($tmpName)) {
    bad_request('Invalid uploaded file.', 400);
}

/** OPTIONAL: cap upload size server-side (defense-in-depth; adjust as needed) */
$MAX_BYTES = 20 * 1024 * 1024; // 20MB
if ($fileSize > $MAX_BYTES) {
    bad_request('File too large.', 413);
}

/** load list_item_data row */
$list_item_data = PageListItemData::find($dataId);
if (!$list_item_data) {
    bad_request('Unknown list_item_data_id.', 404);
}

/** -------- MIME validation (regex with safety limits) -------- */
if (!empty($list_item_data->mime)) {
    $finfo = @finfo_open(FILEINFO_MIME_TYPE);
    if (!$finfo) bad_request('Server MIME detector unavailable.', 500);

    $mime  = (string)@finfo_file($finfo, $tmpName);
    @finfo_close($finfo);

    if ($mime === '') bad_request('Could not determine MIME.', 415);

    // Safe MIME validation: use regex but with pcre.backtrack_limit to prevent ReDoS
    $allowedMimePattern = (string)$list_item_data->mime;

    // Set conservative limits to prevent ReDoS attacks
    $oldBacktrackLimit = ini_get('pcre.backtrack_limit');
    $oldRecursionLimit = ini_get('pcre.recursion_limit');
    ini_set('pcre.backtrack_limit', '10000');
    ini_set('pcre.recursion_limit', '10000');

    // Validate pattern isn't maliciously long
    if (strlen($allowedMimePattern) > 200) {
        bad_request('MIME pattern too long.', 500);
    }

    // Attempt regex match with error suppression
    $matches = @preg_match("/{$allowedMimePattern}/i", $mime);

    // Restore original limits
    ini_set('pcre.backtrack_limit', $oldBacktrackLimit);
    ini_set('pcre.recursion_limit', $oldRecursionLimit);

    // Check for regex errors (including backtrack limit exceeded)
    if ($matches === false || preg_last_error() !== PREG_NO_ERROR) {
        bad_request('Invalid MIME pattern configuration.', 500);
    }

    if (!$matches) {
        http_response_code(415);
        header('Content-Type: text/plain; charset=utf-8');
        exit($mime); // preserves your original behavior (returns detected mime)
    }
}

/** -------- Image-specific checks (CMYK warning, allowed types) -------- */
if ($type === 'image') {
    // Accept common web image types; keep DB rule too (above).
    // If you want to strictly rely on DB rule, you can remove this allowlist.
    $allowedImageMimes = [
        'image/jpeg','image/png','image/gif','image/webp','image/svg+xml'
    ];
    $finfo = @finfo_open(FILEINFO_MIME_TYPE);
    $mime  = $finfo ? (string)@finfo_file($finfo, $tmpName) : '';
    if ($finfo) @finfo_close($finfo);

    // For raster images, ensure they are real images
    if ($mime !== 'image/svg+xml') {
        $imageInfo = @getimagesize($tmpName);
        if ($imageInfo === false) {
            bad_request('Invalid image data.', 415);
        }
        // CMYK warning (channels = 4 on some GD builds)
        if (isset($imageInfo['channels']) && (int)$imageInfo['channels'] === 4) {
            $alert_message = "The image uploaded ({$fileNameOriginal}) appears to be CMYK; it may be converted to RGB and colors can shift.";
            if (class_exists('ContentManagerPanel')) {
                ContentManagerPanel::addMessage($alert_message, "alert");
            }
        }
    } else {
        // Very light SVG sanity checks (defense-in-depth)
        $svg = file_get_contents($tmpName, false, null, 0, 200000) ?: '';
        if (stripos($svg, '<svg') === false ||
            preg_match('/<(script|foreignObject)\b/i', $svg) ||
            preg_match('/\bon\w+=/i', $svg) ||
            preg_match('/javascript:|data:text\/html/i', $svg)) {
            bad_request('Unsafe SVG content.', 415);
        }
    }

    if ($mime && !in_array($mime, $allowedImageMimes, true)) {
        bad_request('Unsupported image type.', 415);
    }
}

/** -------- reconstruct legacy arrays expected later -------- */
$files = [
    'pagedata' => [
        'size' => [
            $dataId => ["new_{$type}_value" => $fileSize]
        ],
        'name' => [
            $dataId => ["new_{$type}_value" => $fileNameOriginal]
        ],
        'tmp_name' => [
            $dataId => ["new_{$type}_value" => $tmpName]
        ],
    ],
];

$post = [
    'pagedata' => [
        $dataId => [
            'value' => $_POST['list_item_data_value'] ?? '',
            'type'  => $type,
        ],
    ],
];

/** -------- image branch: delete old, then add new via add_fullcms_image -------- */
$filename = '';

if ($type === 'image') {
    // get $image_folders from settings.php
    /** @var array $image_folders */
    global $image_folders;

    if (empty($image_folders['cms_images']) || !is_array($image_folders['cms_images'])) {
        bad_request('Image folders not configured.', 500);
    }

    // Default to configured folders
    $specified_folders = $image_folders['cms_images'];

    // PageListItemData settings override for image sizes
    $settings_raw = (string)($list_item_data->settings ?? '');
    $settings     = $settings_raw !== '' ? json_decode($settings_raw, true) : null;

    if (is_array($settings) && isset($settings['image_sizes']) && is_array($settings['image_sizes'])) {
        // Ensure mandatory sizes exist
        if (!in_array('small', $settings['image_sizes'], true))    $settings['image_sizes'][] = 'small';
        if (!in_array('original', $settings['image_sizes'], true)) $settings['image_sizes'][] = 'original';

        // Filter folders to only those sizes
        foreach ($specified_folders as $folderKey => $_data) {
            if (!in_array($folderKey, $settings['image_sizes'], true)) {
                unset($specified_folders[$folderKey]);
            }
        }
    }

    // If previous value exists, remove that file (and .webp sibling) in all cms_images folders
    $prev = (string)($list_item_data->value ?? '');
    if ($prev !== '') {
        $prevBase = basename($prev); // prevent traversal

        // Additional safety: ensure filename doesn't contain path separators
        if (strpos($prevBase, '/') !== false || strpos($prevBase, '\\') !== false || strpos($prevBase, '..') !== false) {
            bad_request('Invalid filename in database.', 400);
        }

        $webpPrev = preg_match('/\.[^.]+$/', $prevBase) ? preg_replace('/\.[^.]+$/', '.webp', $prevBase) : null;

        foreach ($image_folders['cms_images'] as $image_folder) {
            $folderPath = (string)$image_folder['path'];

            // Validate folder path doesn't contain traversal sequences
            if (strpos($folderPath, '..') !== false) {
                continue; // Skip dangerous paths from config
            }

            $baseDir    = rtrim(SITE_PATH, '/').'/'.trim($folderPath, '/');

            // Verify the resolved path is still within SITE_PATH
            $realBase = realpath($baseDir);
            $realSite = realpath(SITE_PATH);
            if ($realBase === false || $realSite === false || strpos($realBase, $realSite) !== 0) {
                continue; // Skip paths outside SITE_PATH
            }

            $prevPath = $baseDir . '/' . $prevBase;
            safe_unlink($prevPath);

            if ($webpPrev) {
                $webpPath = $baseDir . '/' . $webpPrev;
                safe_unlink($webpPath);
            }
        }
    }

    // Add the new image to the filesystem (legacy helper)
    $filename = add_fullcms_image(
        $specified_folders,
        (int)$files['pagedata']['size'][$dataId]['new_image_value'],
        (string)$files['pagedata']['name'][$dataId]['new_image_value'],
        (string)$files['pagedata']['tmp_name'][$dataId]['new_image_value'],
        (string)$list_item_data->mime
    );

    // Persist new value
    $list_item_data->value = $filename;
    $list_item_data->save();

    /** -------- file branch: call legacy helper -------- */
} elseif ($type === 'file') {
    $filename = add_fullcms_file(
        (int)$files['pagedata']['size'][$dataId]['new_file_value'],
        (string)$files['pagedata']['name'][$dataId]['new_file_value'],
        (string)$files['pagedata']['tmp_name'][$dataId]['new_file_value'],
        (string)$list_item_data->mime
    );
} else {
    bad_request('Unsupported type.', 400);
}

/** success: your original script returns just the filename */
if (!empty($filename) && is_string($filename)) {
    ok($filename);
}

bad_request('Failed to store file.', 500);
