<?php
/**
 * @package     MefisAssetBridge
 * @subpackage  MefisAssetBridgeCore_Api_Endpoints
 *
 * @copyright   Copyright (C) 2025 Goran Usljebrka. All rights reserved.
 * @license     Proprietary — No resale or incorporation into other projects without explicit written permission. see LICENSE.txt
 */

namespace MefisAssetBridgeCore\Api\Endpoints\Asset;

use MefisAssetBridgeCore\Api\ApiClient;
use MefisAssetBridgeCore\Auth\AuthenticatedUser;
use MefisAssetBridgeCore\Api\Response\ApiResponse;
use MefisAssetBridgeCore\Cache\CoreCache;
use MefisAssetBridgeCore\Interfaces\EndpointInterface;
use MefisAssetBridgeCore\Traits\Encryptor;

/**
 * GetAssetData API Endpoint
 *
 * Retrieves encrypted and possibly compressed asset data from server,
 * then decrypts and decompresses it to return the original JSON data.
 */
class GetAssetData implements EndpointInterface
{
    use Encryptor;

    /** @var ApiClient API client instance */
    private ApiClient $ApiClient;

    /** @var AuthenticatedUser|null Authenticated user */
    private ?AuthenticatedUser $user;

    private string $uuid;
    private string $pathHash;
    private bool $verifySSL;
    private ?CoreCache $cache;

    /** @var array Required parameters */
    private array $requiredParams = [
        'id',
    ];

    private string $validationError = '';

    public function __construct(string $uuid, ApiClient $ApiClient, ?AuthenticatedUser $user, string $pathHash = '', string $masterKey = '', bool $verifySSL = true, ?CoreCache $cache = null)
    {
        $this->ApiClient = $ApiClient;
        $this->user      = $user;
        $this->uuid      = $uuid;
        $this->pathHash  = $pathHash;
        $this->verifySSL = $verifySSL;
        $this->cache     = $cache;

        if (empty($masterKey)) {
            throw new \RuntimeException('Master key is required to load asset data');
        }
        $this->setEncryptorKey($masterKey);
    }

    /**
     * Validate request parameters
     *
     * @param array $params Request parameters
     * @return bool True if parameters are valid
     */
    public function validateParams(array $params): bool
    {
        foreach ($this->requiredParams as $param) {
            if (!isset($params[$param])) {
                $this->validationError = sprintf('Missing required parameter: %s', $param);
                return false;
            }
        }
        return true;
    }

    /**
     * Send request to get asset data.
     *
     * Cache is checked first when CoreCache is available.
     * Encrypted binary is always written to cache (even on decrypt failure).
     * Decryption, decompression and JSON decode happen here.
     *
     * Required params: id (int), userUuid (string), assetUuid (string)
     * Optional params: modifiedAt (int Unix timestamp for cache staleness check)
     *
     * @param array $params
     * @return ApiResponse  Data array: [asset_id, data, _compressed, _decrypted, cache]
     */
    public function sendRequest(array $params = []): ApiResponse
    {
        if (!$this->validateParams($params)) {
            return ApiResponse::error($this->validationError);
        }

        $assetId   = (int) $params['id'];
        $userUuid  = $params['userUuid']  ?? '';
        $assetUuid = $params['assetUuid'] ?? '';
        $modifiedAt = isset($params['modifiedAt']) ? (int) $params['modifiedAt'] : null;
        $cacheKey  = $assetUuid . '.data';

        // --- Cache read ---
        $encryptedBinary = null;
        $fromCache = false;

        if ($this->cache !== null && $userUuid !== '' && $assetUuid !== '') {
            $cached = $this->cache->get($userUuid, $cacheKey, $modifiedAt);
            if ($cached !== null) {
                $encryptedBinary = $cached;
                $fromCache = true;
            }
        }

        // --- S3 fetch when cache miss ---
        if ($encryptedBinary === null) {
            $this->ApiClient->setUser($this->user);

            $response = $this->ApiClient->get("assets/{$assetId}/storage/data");
            if (!$response->isSuccess()) {
                return $response;
            }

            $responseData = $response->getData();
            if (!isset($responseData['presignedUrl'])) {
                return ApiResponse::error('Server did not provide presigned URL for data download');
            }

            $s3 = new \MefisAssetBridgeCore\Api\Storage\S3Storage($this->verifySSL);
            $dl = $s3->download($responseData['presignedUrl']);
            if (!$dl->isSuccess()) {
                return ApiResponse::error('Failed to download asset data from S3: ' . $dl->getMessage());
            }

            $dlData = $dl->getData();
            if (!isset($dlData['fileContent'])) {
                return ApiResponse::error('S3 download did not return file content');
            }

            $encryptedBinary = $dlData['fileContent'];

            // Always cache the raw encrypted binary immediately after successful download
            if ($this->cache !== null && $userUuid !== '' && $assetUuid !== '') {
                $this->cache->set($userUuid, $encryptedBinary, $cacheKey);
            }
        }

        // --- Decrypt ---
        $decryptedData = $this->decrypt($encryptedBinary, false);

        if ($decryptedData === null) {
            // Encrypted binary is already cached; return failure so controller can report it
            return ApiResponse::error('Invalid Master Key.');
        }

        // --- Decompress (gzip magic bytes 0x1f 0x8b) ---
        $isGzipped = (strlen($decryptedData) >= 2
            && ord($decryptedData[0]) === 0x1f
            && ord($decryptedData[1]) === 0x8b);

        if ($isGzipped) {
            if (!function_exists('gzdecode')) {
                return ApiResponse::error('Gzip decompression not available on this server');
            }
            $decompressed = gzdecode($decryptedData);
            if ($decompressed === false) {
                return ApiResponse::error('Failed to decompress asset data');
            }
            $decryptedData = $decompressed;
        }

        // --- JSON decode ---
        $assetData = json_decode($decryptedData, true);
        if (json_last_error() !== JSON_ERROR_NONE) {
            return ApiResponse::error('Failed to decode asset data: ' . json_last_error_msg());
        }

        return ApiResponse::success([
            'asset_id'    => $assetId,
            'data'        => $assetData,
            '_compressed' => $isGzipped,
            '_decrypted'  => true,
            'cache'       => $fromCache,
        ], 'Asset data loaded successfully');
    }
}