<?php

/**
 * Config
 */
const BASE = __DIR__ . "/../../..";

const DEFAULT_MODE = 'serve';                    // 'serve' or 'exif'
const DEFAULT_LINE = 2;                         // 1-based line index
const STORAGE_DIRECTORY = BASE . "/v/home/databases/central/assets/devices/cameras/accessible/archives/images/screenshots";
const ACCESSIBILITY_DIRECTORY = BASE . "/v/home/databases/central/assets/devices/cameras/accessible/status";
const DEFAULT_IMAGE = BASE . "/v/home/frontend/assets/media/image/icon/favicon.ico";

const FINFO = new finfo(FILEINFO_MIME_TYPE);

/**
 * Basic bootstrap
 */
// declare(strict_types=1);

ini_set('display_errors', '0');
error_reporting(E_ALL);

require_once(BASE . "/v/home/scripts/general.php");

/**
 * Helper: send JSON response
 */
function sendJson(array $data, int $statusCode = 200): void
{
    http_response_code($statusCode);
    header('Content-Type: application/json; charset=utf-8');

    echo json_encode(
        $data,
        JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
    );

    exit;
}

/**
 * Helper: send error as JSON
 */
function sendError(string $message, int $statusCode = 400): void
{
    sendJson([
        'success' => false,
        'error'   => $message,
    ], $statusCode);
}

/**
 * Helper: get query param with default
 */
function getQueryParam(string $name, $default = null): ?string
{
    if (!isset($_GET[$name])) {
        return $default;
    }

    $value = trim((string)$_GET[$name]);

    return $value === '' ? $default : $value;
}

/**
 * Get camera/storage key
 */
function getKey(): string
{
    $keyParam = getQueryParam('key', (string)DEFAULT_LINE);

    if (empty(trim($keyParam))) {
        if (getMode() === 'serve') {
            header('Content-Type: ' . FINFO->file(DEFAULT_IMAGE));
            readfile(DEFAULT_IMAGE);
            exit;
        }

        sendError('Invalid key. Key is not set', 400);
    }

    return $keyParam;
}

/**
 * Get storage file
 */
function getLineStorageFile(): string
{
    $keyParam = getKey();

    $storageFile = STORAGE_DIRECTORY . "/$keyParam.dat";

    if (!file_exists($storageFile)) {
        if (getMode() === 'serve') {
            header('Content-Type: ' . FINFO->file(DEFAULT_IMAGE));
            readfile(DEFAULT_IMAGE);
            exit;
        }

        sendError('Invalid key. Storage file does not exist', 400);
    }

    return $storageFile;
}

/**
 * Helper: get mode (serve|exif)
 */
function getMode(): string
{
    $mode = strtolower(getQueryParam('mode', DEFAULT_MODE));

    if (!in_array($mode, ['serve', 'exif'], true)) {
        sendError('Invalid mode. Allowed: serve, exif.', 400);
    }

    return $mode;
}

/**
 * Detect whether the storage file is the new CSV format.
 *
 * CSV format:
 *
 * data,timestamp
 * BASE64_DATA,1726273200
 *
 * The first line must contain a "data" column.
 */
function isCsvStorageFile(string $filePath): bool
{
    $handle = @fopen($filePath, 'rb');

    if (!$handle) {
        return false;
    }

    $firstLine = fgets($handle);
    fclose($handle);

    if ($firstLine === false) {
        return false;
    }

    $firstLine = trim($firstLine);

    if ($firstLine === '') {
        return false;
    }

    $columns = str_getcsv($firstLine);

    if (empty($columns)) {
        return false;
    }

    $normalizedColumns = array_map(
        static function ($column) {
            return strtolower(trim((string)$column));
        },
        $columns
    );

    return in_array('data', $normalizedColumns, true);
}

/**
 * Get CSV column indexes.
 */
function getCsvStorageColumns(string $filePath): array
{
    $handle = @fopen($filePath, 'rb');

    if (!$handle) {
        sendError('Unable to open storage file.', 500);
    }

    $header = fgets($handle);
    fclose($handle);

    if ($header === false) {
        sendError('Storage file is empty.', 404);
    }

    $columns = str_getcsv(trim($header));

    $dataIndex = null;
    $timestampIndex = null;

    foreach ($columns as $index => $column) {
        $column = strtolower(trim((string)$column));

        if ($column === 'data') {
            $dataIndex = $index;
        }

        if ($column === 'timestamp') {
            $timestampIndex = $index;
        }
    }

    if ($dataIndex === null) {
        sendError('CSV storage file does not contain a data column.', 500);
    }

    return [
        'data'      => $dataIndex,
        'timestamp' => $timestampIndex,
    ];
}

/**
 * Helper: count total lines and find line indices (first/last/specific).
 *
 * For CSV storage, the header is NOT counted as a data/version line.
 */
function resolveLineNumber(string $filePath): int
{
    $action = strtolower(getQueryParam('action', 'version'));

    $isCsv = isCsvStorageFile($filePath);

    /**
     * Handle count separately.
     */
    if ($action === 'count') {
        $totalLines = 0;

        $handle = @fopen($filePath, 'rb');

        if ($handle) {
            $isFirstLine = true;

            while (($line = fgets($handle)) !== false) {
                $trimmed = trim($line);

                if ($trimmed === '') {
                    continue;
                }

                // Skip CSV header.
                if ($isCsv && $isFirstLine) {
                    $isFirstLine = false;
                    continue;
                }

                $isFirstLine = false;
                $totalLines++;
            }

            fclose($handle);
        }

        sendJson([
            'success' => true,
            'total'   => $totalLines
        ]);
    }

    $handle = @fopen($filePath, 'rb');

    if (!$handle) {
        sendError('Unable to open storage file.', 500);
    }

    $totalLines = 0;
    $lastValidLine = 0;
    $firstValidLine = 0;
    $physicalLine = 0;

    while (($line = fgets($handle)) !== false) {
        $physicalLine++;

        $trimmed = trim($line);

        if ($trimmed === '') {
            continue;
        }

        // CSV header does not represent an image/version.
        if ($isCsv && $physicalLine === 1) {
            continue;
        }

        $totalLines++;

        if ($firstValidLine === 0) {
            $firstValidLine = $totalLines;
        }

        $lastValidLine = $totalLines;
    }

    fclose($handle);

    if ($totalLines === 0 || $lastValidLine === 0) {
        sendError('Storage file is empty.', 404);
    }

    if ($action === 'first') {
        return $firstValidLine;
    }

    if ($action === 'last') {
        return $lastValidLine;
    }

    if ($action === 'version') {
        $lineParam = getQueryParam('version', (string)DEFAULT_LINE);

        if (!ctype_digit($lineParam)) {
            sendError(
                'Invalid line parameter. Must be a positive integer.',
                400
            );
        }

        $line = (int)$lineParam;

        if ($line < 1) {
            sendError('Line number must be >= 1.', 400);
        }

        if ($line > $totalLines) {
            sendError('Requested version does not exist.', 404);
        }

        return $line;
    }

    sendError(
        'Invalid action parameter. Allowed: version, first, last, count.',
        400
    );
}

/**
 * Read a specific storage version.
 *
 * Supports both:
 *
 * Old format:
 *   BASE64
 *   BASE64
 *
 * New CSV format:
 *   data,timestamp
 *   BASE64,1726273200
 *
 * Returns:
 * [
 *     'data' => '...',
 *     'timestamp' => '...'
 * ]
 */
function readStorageVersion(string $filePath, int $version): array
{
    $isCsv = isCsvStorageFile($filePath);

    $handle = @fopen($filePath, 'rb');

    if (!$handle) {
        sendError('Unable to open storage file.', 500);
    }

    $currentVersion = 0;
    $header = null;

    if ($isCsv) {
        $headerLine = fgets($handle);

        if ($headerLine === false) {
            fclose($handle);
            sendError('Storage file is empty.', 404);
        }

        $header = str_getcsv(trim($headerLine));
    }

    while (($line = fgets($handle)) !== false) {
        $line = trim($line);

        if ($line === '') {
            continue;
        }

        $currentVersion++;

        if ($currentVersion !== $version) {
            continue;
        }

        /**
         * Old format:
         * one Base64 value per line.
         */
        if (!$isCsv) {
            fclose($handle);

            return [
                'data'      => $line,
                'timestamp' => null,
            ];
        }

        /**
         * CSV format.
         */
        $columns = getCsvStorageColumns($filePath);
        $row = str_getcsv($line);

        $data = $row[$columns['data']] ?? null;

        if ($data === null || trim($data) === '') {
            fclose($handle);
            sendError('Requested CSV row has no image data.', 404);
        }

        $timestamp = null;

        if (
            $columns['timestamp'] !== null &&
            isset($row[$columns['timestamp']])
        ) {
            $timestamp = trim((string)$row[$columns['timestamp']]);
        }

        fclose($handle);

        return [
            'data'      => trim((string)$data),
            'timestamp' => $timestamp,
        ];
    }

    fclose($handle);

    sendError('Requested line not found or empty.', 404);
}

/**
 * Helper: read specific line from storage file (legacy compatibility).
 *
 * This now supports both plain Base64 and CSV storage.
 */
function readBase64Line(string $filePath, int $lineNumber): string
{
    $record = readStorageVersion($filePath, $lineNumber);

    return $record['data'];
}

/**
 * Helper: decode base64 safely
 */
function decodeBase64(string $base64): string
{
    if (str_starts_with($base64, 'data:')) {
        $parts = explode(',', $base64, 2);
        $base64 = $parts[1] ?? '';
    }

    $decodedBase64 = base64_decode($base64, true);

    if ($decodedBase64 === false) {
        if (getMode() === 'serve') {
            header('Content-Type: ' . FINFO->file(DEFAULT_IMAGE));
            readfile(DEFAULT_IMAGE);
            exit;
        }

        sendError('Failed to decode base64 content.', 500);
    }

    $decoded = isValidImageGd($decodedBase64);

    if ($decoded === false) {
        if (getMode() === 'serve') {
            header('Content-Type: ' . FINFO->file(DEFAULT_IMAGE));
            readfile(DEFAULT_IMAGE);
            exit;
        }

        sendError('Decoded content is not a valid image.', 500);
    }

    return $decoded;
}

/**
 * Helper: detect MIME type from binary data
 */
function detectMimeType(string $binary): string
{
    if (function_exists('finfo_open')) {
        $finfo = finfo_open(FILEINFO_MIME_TYPE);

        if ($finfo !== false) {
            $mime = finfo_buffer($finfo, $binary);
            finfo_close($finfo);

            if (is_string($mime) && $mime !== '') {
                return $mime;
            }
        }
    }

    if (substr($binary, 0, 3) === "\xFF\xD8\xFF") {
        return 'image/jpeg';
    }

    if (substr($binary, 0, 8) === "\x89PNG\x0D\x0A\x1A\x0A") {
        return 'image/png';
    }

    if (substr($binary, 0, 4) === "GIF8") {
        return 'image/gif';
    }

    if (substr($binary, 0, 4) === "%PDF") {
        return 'application/pdf';
    }

    return 'application/octet-stream';
}

/**
 * Helper: send binary file to client (no disk write)
 */
function streamFile(
    string $binary,
    string $mimeType,
    ?string $downloadName = null
): void {
    $downloadName = $downloadName ?: 'file';

    $downloadName = str_replace(
        ["\r", "\n"],
        '',
        $downloadName
    );

    if (function_exists('ob_get_level')) {
        while (ob_get_level() > 0) {
            ob_end_clean();
        }
    }

    header('Content-Type: ' . $mimeType);
    header('Content-Length: ' . strlen($binary));
    header('X-Content-Type-Options: nosniff');

    $disposition = str_starts_with($mimeType, 'image/')
        ? 'inline'
        : 'attachment';

    header(
        'Content-Disposition: ' .
        $disposition .
        '; filename="' .
        $downloadName .
        '"'
    );

    $chunkSize = 8192;
    $offset = 0;
    $length = strlen($binary);

    while ($offset < $length) {
        $chunk = substr($binary, $offset, $chunkSize);
        echo $chunk;

        $offset += $chunkSize;

        flush();
    }

    exit;
}

/**
 * Helper: extract EXIF data from binary.
 *
 * $timestamp is supplied directly when the storage CSV contains it.
 */
function extractExif(
    string $binary,
    int $version,
    ?string $storageTimestamp = null
): array {
    $tempStream = fopen('php://temp', 'wb+');

    if ($tempStream === false) {
        sendError(
            'Unable to create temporary stream for EXIF.',
            500
        );
    }

    fwrite($tempStream, $binary);
    rewind($tempStream);

    $meta = stream_get_meta_data($tempStream);
    $uri = $meta['uri'] ?? null;

    if (!$uri) {
        fclose($tempStream);

        sendError(
            'Unable to obtain URI for EXIF processing.',
            500
        );
    }

    $exif = @exif_read_data($uri, null, true);

    fclose($tempStream);

    if ($exif === false || !is_array($exif)) {
        $exif = [];
    }

    /**
     * If the storage file contains its own timestamp,
     * prefer that timestamp.
     */
    if (
        $storageTimestamp !== null &&
        $storageTimestamp !== ''
    ) {
        $timestamp = null;

        /**
         * Most likely case: Unix timestamp.
         */
        if (ctype_digit($storageTimestamp)) {
            $timestamp = (int)$storageTimestamp;
        } else {
            /**
             * Also allow a normal date/time string.
             */
            $parsed = strtotime($storageTimestamp);

            if ($parsed !== false) {
                $timestamp = $parsed;
            }
        }

        if ($timestamp !== null) {
            $exif['timestamp'] = date(
                "Y-m-d H:i:s",
                $timestamp
            );
        } else {
            // Preserve the original value if it cannot be parsed.
            $exif['timestamp'] = $storageTimestamp;
        }
    } else {
        /**
         * Legacy storage format:
         * fall back to accessibility/online.csv.
         */
        $onlineCsv =
            ACCESSIBILITY_DIRECTORY .
            "/" .
            getKey() .
            "/online.csv";

        if (file_exists($onlineCsv)) {
            try {
                $file = new SplFileObject($onlineCsv);

                $file->seek($version - 1);

                $value = trim((string)$file->current());

                if ($value !== '') {
                    $timestamp = $value;

                    if ($timestamp !== false) {
                        $exif['timestamp'] = date(
                            "Y-m-d H:i:s",
                            $timestamp
                        );
                    } else {
                        $exif['timestamp'] = $value;
                    }
                }
            } catch (Throwable $e) {
                // Do not make EXIF requests fail merely because
                // the legacy timestamp file is unavailable.
            }
        }
    }

    return $exif;
}

/**
 * Main controller
 */
try {
    $mode = getMode();

    $storageFile = getLineStorageFile();

    $lineNumber = resolveLineNumber($storageFile);

    /**
     * Read the requested version.
     *
     * This works for both:
     *
     * BASE64
     *
     * and:
     *
     * data,timestamp
     * BASE64,timestamp
     */
    $storageRecord = readStorageVersion(
        $storageFile,
        $lineNumber
    );

    $base64Line = $storageRecord['data'];
    $timestamp = $storageRecord['timestamp'];

    $binary = decodeBase64($base64Line);

    $mime = detectMimeType($binary);

    if ($mode === 'serve') {
        $latitude = $_SERVER['HTTP_CF_IPLATITUDE'] ?? null;
        $longitude = $_SERVER['HTTP_CF_IPLONGITUDE'] ?? null;

        $referer_host = !empty($_SERVER['HTTP_REFERER'])
            ? parse_url(
                $_SERVER['HTTP_REFERER'],
                PHP_URL_HOST
            )
            : null;

        if (
            (
                is_null($referer_host) ||
                (
                    !is_null($referer_host) &&
                    (
                        $referer_host !== $currentDomain &&
                        !str_contains(
                            $referer_host,
                            $currentDomain
                        )
                    )
                )
            ) &&
            1 == 1
        ) {
            require_once(BASE . "/v/home/scripts/converter.php");
            require_once(BASE . "/v/home/scripts/imageEditor.php");

            $finfo = new finfo(FILEINFO_MIME_TYPE);

            $SupportConversion = array(
                IMAGETYPE_JPEG,
                IMAGETYPE_PNG,
                IMAGETYPE_GIF,
                IMAGETYPE_BMP,
                IMAGETYPE_WEBP,
                IMAGETYPE_TIFF_II,
                IMAGETYPE_TIFF_MM,
                IMAGETYPE_AVIF,
                IMAGETYPE_JP2
            );

            try {
                processImageV4(
                    $binary,
                    [
                        'watermark' => [
                            'text' =>
                                "surveillance-map.com/view." .
                                getKey(),
                            'position' => 'top-right',
                            'font_file' =>
                                BASE .
                                "/v/home/databases/library/fonts/TrajanPro-Regular.woff",
                            'opacity' => 0
                        ]
                    ],
                    $mime,
                    $SupportConversion
                );
            } catch (\Throwable $e) {
                // Keep original image if watermarking fails.
            }
        }

        $extensionMap = [
            'image/jpeg'               => 'jpg',
            'image/png'                => 'png',
            'image/gif'                => 'gif',
            'application/pdf'          => 'pdf',
            'application/octet-stream' => 'bin',
        ];

        $ext = $extensionMap[$mime] ?? 'bin';

        $filename =
            getKey() .
            '_version_' .
            $lineNumber .
            '.' .
            $ext;

        streamFile(
            $binary,
            $mime,
            $filename
        );
    }

    if ($mode === 'exif') {
        $exifData = extractExif(
            $binary,
            $lineNumber,
            $timestamp
        );

        sendJson([
            'success'   => true,
            'line'      => $lineNumber,
            'mime_type' => $mime,
            'has_exif'  => !empty($exifData),
            'exif'      => $exifData,
        ]);
    }

    sendError('Unsupported mode.', 400);

} catch (Throwable $e) {
    sendError(
        'Unexpected server error: ' . $e->getMessage(),
        500
    );
}

?>
