<?php
/**
 * @package     MefisAssetBridge
 * @subpackage  MefisAssetBridgeCore_Api_Storage
 *
 * @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\Storage;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use MefisAssetBridgeCore\Api\Response\ApiResponse;

/**
 * S3Storage - Lightweight S3 client for presigned POST operations
 *
 * This class is designed for client-side that does NOT have AWS credentials.
 * It performs HTTP POST operations using presigned POST data (URL + fields) generated by server.
 *
 * File size validation must be handled by the component before calling this class.
 * S3 will enforce size limits via policy conditions in the presigned POST.
 *
 * Usage:
 *
 * // Initialize
 * $storage = new S3Storage();
 *
 * // Upload file using presigned POST data
 * $response = $storage->upload($url, $fields, $fileContent);
 * if ($response->isSuccess()) {
 *     $data = $response->getData();
 *     $etag = $data['etag'];
 * }
 *
 * // Download file using presigned URL
 * $response = $storage->download($presignedUrl);
 * if ($response->isSuccess()) {
 *     $data = $response->getData();
 *     $fileContent = $data['fileContent'];
 * }
 */
class S3Storage
{
    private Client $httpClient;
    private bool $verifySSL;

    /**
     * @param bool $verifySSL Whether to verify SSL certificates (default: true)
     */
    public function __construct(bool $verifySSL = true)
    {
        $this->verifySSL = $verifySSL;
        $this->httpClient = new Client([
            'timeout' => 30,
            'connect_timeout' => 10,
            'http_errors' => false,
            'verify' => $verifySSL
        ]);
    }

    /**
     * Upload file to S3 using presigned POST
     *
     * @param string $url POST action URL from server (app2)
     * @param array $fields Form fields (signature, policy, etc.) from server (app2)
     * @param string $fileContent Binary file content to upload
     * @return ApiResponse
     */
    public function upload(string $url, array $fields, string $fileContent): ApiResponse
    {
        try {
            $result = $this->postObject($url, $fields, $fileContent);

            if (!$result->isSuccess()) {
                return ApiResponse::error(
                    $result->getMessage() ?? 'Upload failed',
                    $result->getData(),
                    500
                );
            }

            return ApiResponse::success(
                $result->getData(),
                'File uploaded successfully to S3'
            );

        } catch (\Exception $e) {
            return ApiResponse::error(
                'Upload failed: ' . $e->getMessage(),
                null,
                500
            );
        }
    }

    /**
     * Download file from S3 using presigned URL
     *
     * @param string $presignedUrl Presigned URL from server (app2)
     * @return ApiResponse
     */
    public function download(string $presignedUrl): ApiResponse
    {
        try {
            $result = $this->getObject($presignedUrl);

            if (!$result->isSuccess()) {
                return ApiResponse::error(
                    $result->getMessage() ?? 'Download failed',
                    $result->getData(),
                    500
                );
            }

            return ApiResponse::success(
                $result->getData(),
                'File downloaded successfully from S3'
            );

        } catch (\Exception $e) {
            return ApiResponse::error(
                'Download failed: ' . $e->getMessage(),
                null,
                500
            );
        }
    }

    /**
     * Internal method: POST operation using presigned POST data
     *
     * @param string $url POST action URL
     * @param array $fields Form fields (signature, policy, credentials, key, etc.)
     * @param string $fileContent Binary file content
     * @return StorageResultDto
     */
    private function postObject(string $url, array $fields, string $fileContent): StorageResultDto
    {
        try {
            $fileSize = strlen($fileContent);

            // Build multipart form data
            $multipart = [];

            // Add all form fields (signature, policy, credentials, key, etc.)
            foreach ($fields as $name => $value) {
                $multipart[] = [
                    'name' => $name,
                    'contents' => $value
                ];
            }

            // Add Content-Type field (required by S3 policy)
            $multipart[] = [
                'name' => 'Content-Type',
                'contents' => 'application/octet-stream'
            ];

            // Add file field LAST (required by S3)
            $multipart[] = [
                'name' => 'file',
                'contents' => $fileContent,
                'filename' => basename($fields['key'] ?? 'file')
            ];

            // POST to S3
            $response = $this->httpClient->post($url, [
                'multipart' => $multipart,
                'verify' => $this->verifySSL
            ]);

            $statusCode = $response->getStatusCode();
            $body = (string) $response->getBody();

            // Check for errors (4xx/5xx)
            // Note: Some S3 configurations return 204 or 3xx redirect on success
            if ($statusCode >= 400) {
                $errorData = S3StorageResponseParser::parseS3Error($body);
                $errorMessage = S3StorageResponseParser::buildErrorMessage($errorData, $statusCode);

                return new StorageResultDto(
                    false,
                    'PostObject',
                    $errorMessage,
                    array_merge(['statusCode' => $statusCode], $errorData ?? [])
                );
            }

            // Extract response data
            $eTag = $response->hasHeader('ETag')
                ? trim($response->getHeader('ETag')[0], '"')
                : null;
            $location = $response->hasHeader('Location')
                ? $response->getHeader('Location')[0]
                : null;

            return new StorageResultDto(
                true,
                'PostObject',
                null,
                [
                    'key' => $fields['key'] ?? null,
                    'eTag' => $eTag,
                    'location' => $location,
                    'fileSize' => $fileSize,
                    'statusCode' => $statusCode,
                    'uploaded' => true
                ]
            );

        } catch (GuzzleException $e) {
            return new StorageResultDto(
                false,
                'PostObject',
                "Upload failed: " . $e->getMessage()
            );
        }
    }

    /**
     * Internal method: GET operation using presigned URL
     *
     * @param string $presignedUrl
     * @return StorageResultDto
     */
    private function getObject(string $presignedUrl): StorageResultDto
    {
        try {
            $response = $this->httpClient->get($presignedUrl, [
                'verify' => $this->verifySSL
            ]);

            $statusCode = $response->getStatusCode();
            $body = (string) $response->getBody();

            // Check for errors (4xx/5xx)
            if ($statusCode >= 400) {
                $errorData = S3StorageResponseParser::parseS3Error($body);
                $errorMessage = S3StorageResponseParser::buildErrorMessage($errorData, $statusCode);

                return new StorageResultDto(
                    false,
                    'GetObject',
                    $errorMessage,
                    array_merge(['statusCode' => $statusCode], $errorData ?? [])
                );
            }

            // Extract response data
            $eTag = $response->hasHeader('ETag')
                ? trim($response->getHeader('ETag')[0], '"')
                : null;
            $contentLength = $response->hasHeader('Content-Length')
                ? (int) $response->getHeader('Content-Length')[0]
                : strlen($body);
            $contentType = $response->hasHeader('Content-Type')
                ? $response->getHeader('Content-Type')[0]
                : null;

            return new StorageResultDto(
                true,
                'GetObject',
                null,
                [
                    'eTag' => $eTag,
                    'contentLength' => $contentLength,
                    'contentType' => $contentType,
                    'statusCode' => $statusCode,
                    'downloaded' => true,
                    'fileContent' => $body // Binary content
                ]
            );

        } catch (GuzzleException $e) {
            return new StorageResultDto(
                false,
                'GetObject',
                "Download failed: " . $e->getMessage()
            );
        }
    }
}
