<?php

declare(strict_types=1);

namespace App\Application\Api\Images;

use App\Application\Security\Services\FileUploadSecurityService;
use App\Domain\Media\Image;
use App\Domain\Media\ImageProcessor;
use App\Domain\Media\ImageRepository;
use App\Framework\Attributes\Route;
use App\Framework\Core\ValueObjects\FileSize;
use App\Framework\Core\ValueObjects\Hash;
use App\Framework\DateTime\Clock;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\FrameworkException;
use App\Framework\Filesystem\ValueObjects\FilePath;
use App\Framework\Http\Method;
use App\Framework\Http\MimeType;
use App\Framework\Http\Request;
use App\Framework\Http\Responses\JsonResponse;
use App\Framework\Http\Status;
use App\Framework\Http\UploadedFile;
use App\Framework\Ulid\Ulid;

final readonly class UploadImageController
{
    public function __construct(
        private ImageRepository $imageRepository,
        private FileUploadSecurityService $uploadSecurityService,
        private ImageProcessor $imageProcessor,
        private Clock $clock
    ) {
    }

    #[Route(path: '/api/images', method: Method::POST)]
    public function __invoke(Request $request): JsonResponse
    {
        // Validate uploaded file
        $uploadedFiles = $request->files;

        if ($uploadedFiles->isEmpty() || ! $uploadedFiles->has('image')) {
            throw FrameworkException::create(
                ErrorCode::VAL_REQUIRED_FIELD_MISSING,
                'No image file uploaded'
            )->withData([
                'field' => 'image',
                'files_empty' => $uploadedFiles->isEmpty(),
                'available_fields' => $uploadedFiles->keys(),
            ]);
        }

        $uploadedFile = $uploadedFiles->get('image');
        if (! ($uploadedFile instanceof UploadedFile)) {
            throw FrameworkException::create(
                ErrorCode::VAL_INVALID_FORMAT,
                'Invalid uploaded file'
            );
        }

        // Security validation
        try {
            $validationResult = $this->uploadSecurityService->validateUpload($uploadedFile);
            if (! $validationResult) {
                throw FrameworkException::create(
                    ErrorCode::SEC_FILE_UPLOAD_REJECTED,
                    'File upload security validation failed'
                );
            }
        } catch (\Exception $e) {
            throw $e;
        }

        // Validate MIME type
        $detectedMimeType = MimeType::fromFilePath($uploadedFile->name);
        if (! $detectedMimeType || ! $detectedMimeType->isImage()) {
            throw FrameworkException::create(
                ErrorCode::VAL_INVALID_FORMAT,
                'Uploaded file is not a valid image'
            )->withData(['detected_mime_type' => $detectedMimeType?->value]);
        }

        // Generate ULID for image
        $ulid = new Ulid($this->clock);

        // Calculate file hash first (needed for filename)
        if (! is_file($uploadedFile->tmpName)) {
            throw FrameworkException::create(
                ErrorCode::VAL_INVALID_FORMAT,
                'Temporary file is not a valid file'
            )->withData([
                'tmp_name' => $uploadedFile->tmpName,
                'is_file' => is_file($uploadedFile->tmpName),
                'is_dir' => is_dir($uploadedFile->tmpName),
                'exists' => file_exists($uploadedFile->tmpName),
            ]);
        }

        $fileHash = Hash::fromFile($uploadedFile->tmpName);

        // Get original filename and create structured storage path
        $originalFilename = $uploadedFile->name;
        $extension = $uploadedFile->getExtension() ?: 'jpg';

        // Create structured path: uploads/YYYY/MM/DD/XXX/YYY/ZZZ/
        $uploadDirectory = sprintf('uploads/%s/%s/%s', date('Y'), date('m'), date('d'));
        $ulidString = $ulid->__toString();
        $id = substr($ulidString, 10); // Remove timestamp part
        $idStr = str_pad((string)$id, 9, '0', STR_PAD_LEFT);
        $filePathPattern = sprintf(
            '%s/%s/%s',
            substr($idStr, 0, 3),
            substr($idStr, 3, 3),
            substr($idStr, 6, 3),
        );

        // Create full storage path
        $storagePath = $uploadDirectory . '/' . $filePathPattern;
        $filename = $idStr . '_' . $fileHash->toString() . '_original.' . $extension;

        // Get image dimensions from temporary file
        $imageInfo = getimagesize($uploadedFile->tmpName);
        if ($imageInfo === false) {
            throw FrameworkException::create(
                ErrorCode::VAL_INVALID_FORMAT,
                'Could not read image dimensions'
            )->withData(['tmp_file' => $uploadedFile->tmpName]);
        }

        $width = $imageInfo[0];
        $height = $imageInfo[1];

        // Get alt text from request (optional)
        $altText = $request->parsedBody->get('alt_text', '');

        // Create full storage directory path
        $storageBasePath = FilePath::cwd()->join('storage');
        $fullStoragePath = $storageBasePath->join($storagePath);

        // Create Image entity (needed for SaveImageFile service)
        // Note: SaveImageFile expects directory path, not full file path
        $image = new Image(
            ulid: $ulid,
            filename: $filename,
            originalFilename: $originalFilename,
            mimeType: $detectedMimeType,
            fileSize: FileSize::fromBytes($uploadedFile->size), // Use uploaded file size
            width: $width,
            height: $height,
            hash: $fileHash, // Use already calculated hash
            path: $fullStoragePath, // Directory path as FilePath object
            altText: $altText
        );

        // Save to database and move file (ImageRepository handles both)
        $this->imageRepository->save($image, $uploadedFile->tmpName);

        // Update file information after successful save
        $fullFilePath = $fullStoragePath->join($filename);
        $actualFileSize = FileSize::fromFile($fullFilePath->toString());
        $actualHash = Hash::fromFile($fullFilePath->toString());

        // Update image with actual file information
        $image = new Image(
            ulid: $ulid,
            filename: $filename,
            originalFilename: $originalFilename,
            mimeType: $detectedMimeType,
            fileSize: $actualFileSize,
            width: $width,
            height: $height,
            hash: $actualHash,
            path: $fullStoragePath,
            altText: $altText
        );

        // Process image variants (thumbnails, WebP, AVIF, etc.)
        $this->imageProcessor->createAllVariants($image);

        // Return created image data
        return new JsonResponse([
            'ulid' => $image->getUlidString(),
            'filename' => $image->filename,
            'original_filename' => $image->originalFilename,
            'url' => '/images/' . $filename,
            'thumbnail_url' => '/images/' . str_replace('_original.', '_thumbnail.', $filename),
            'alt_text' => $image->altText,
            'dimensions' => [
                'width' => $image->width,
                'height' => $image->height,
                'aspect_ratio' => $image->getAspectRatio(),
                'orientation' => $image->getDimensions()->getOrientation()->value,
            ],
            'mime_type' => $image->mimeType->value,
            'file_size' => [
                'bytes' => $image->fileSize->toBytes(),
                'human_readable' => $image->getHumanReadableFileSize(),
            ],
            'hash' => $image->hash->toString(),
            'is_image' => $image->isImageFile(),
            'created_at' => $image->ulid->getDateTime()->format('c'),
        ], Status::CREATED);
    }
}
