<?php

declare(strict_types=1);

namespace App\Application\Api\Images;

use App\Application\Security\Services\FileUploadSecurityService;
use App\Domain\Media\ImageProcessor;
use App\Domain\Media\ImageRepository;
use App\Domain\Media\ImageVariantRepository;
use App\Framework\Attributes\Route;
use App\Framework\Core\PathProvider;
use App\Framework\DateTime\Clock;
use App\Framework\Exception\ErrorCode;
use App\Framework\Exception\FrameworkException;
use App\Framework\Http\Exception\NotFound;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Http\Responses\JsonResponse;
use App\Framework\Router\Result\FileResult;
use App\Framework\Ulid\UlidGenerator;

final readonly class ImageApiController
{
    public function __construct(
        private ImageRepository $imageRepository,
        private FileUploadSecurityService $uploadSecurityService,
        private ImageProcessor $imageProcessor,
        private ImageVariantRepository $imageVariantRepository,
        private UlidGenerator $ulidGenerator,
        private PathProvider $pathProvider,
        private Clock $clock
    ) {
    }

    #[Route(path: '/api/images', method: Method::GET)]
    public function getImages(HttpRequest $request): JsonResponse
    {
        $limit = (int) ($request->queryParams['limit'] ?? 50);
        $offset = (int) ($request->queryParams['offset'] ?? 0);
        $search = $request->queryParams['search'] ?? null;

        $images = $this->imageRepository->findAll($limit, $offset, $search);
        $total = $this->imageRepository->count($search);

        return new JsonResponse([
            'data' => array_map(fn ($image) => [
                'ulid' => $image->ulid,
                'filename' => $image->filename,
                'original_filename' => $image->originalFilename,
                'url' => '/images/' . $image->filename,
                'thumbnail_url' => '/images/' . str_replace('_original.', '_thumbnail.', $image->filename),
                'alt_text' => $image->altText,
                'dimensions' => [
                    'width' => $image->width,
                    'height' => $image->height,
                ],
                'file_size' => [
                    'bytes' => $image->fileSize->toBytes(),
                    'human_readable' => $image->fileSize->toHumanReadable(),
                ],
                'mime_type' => $image->mimeType,
            ], $images),
            'meta' => [
                'total' => $total,
                'limit' => $limit,
                'offset' => $offset,
                'has_more' => ($offset + $limit) < $total,
            ],
        ]);
    }

    #[Route(path: '/api/images/{ulid}', method: Method::GET)]
    public function getImage(string $ulid): JsonResponse
    {
        $image = $this->imageRepository->findByUlid($ulid);

        if (! $image) {
            throw NotFound::create(
                ErrorCode::ENTITY_NOT_FOUND,
                "Image with ULID {$ulid} not found"
            )->withData(['ulid' => $ulid]);
        }

        return new JsonResponse([
            'ulid' => $image->ulid,
            'filename' => $image->filename,
            'original_filename' => $image->originalFilename,
            'url' => '/images/' . $image->filename,
            'alt_text' => $image->altText,
            'dimensions' => [
                'width' => $image->width,
                'height' => $image->height,
            ],
            'file_size' => [
                'bytes' => $image->fileSize->toBytes(),
                'human_readable' => $image->fileSize->toHumanReadable(),
            ],
            'mime_type' => $image->mimeType,
            'hash' => $image->hash,
            'variants' => array_map(fn ($variant) => [
                'type' => $variant->variantType,
                'width' => $variant->width,
                'height' => $variant->height,
                'path' => $variant->path,
                'url' => '/storage/' . $variant->path,
            ], $image->variants ?? []),
        ]);
    }

    #[Route(path: '/api/images/{ulid}', method: Method::PUT)]
    public function updateImage(string $ulid, HttpRequest $request): JsonResponse
    {
        $image = $this->imageRepository->findByUlid($ulid);

        if (! $image) {
            throw NotFound::create(
                ErrorCode::ENTITY_NOT_FOUND,
                "Image with ULID {$ulid} not found"
            )->withData(['ulid' => $ulid]);
        }

        $data = $request->parsedBody->toArray();

        // Update alt text if provided
        if (isset($data['alt_text'])) {
            $this->imageRepository->updateAltText($ulid, $data['alt_text']);
        }

        // Update filename if provided
        if (isset($data['filename'])) {
            $this->imageRepository->updateFilename($ulid, $data['filename']);
        }

        return new JsonResponse([
            'success' => true,
            'message' => 'Image updated successfully',
        ]);
    }

    #[Route(path: '/api/images/search', method: Method::GET)]
    public function searchImages(HttpRequest $request): JsonResponse
    {
        $query = $request->queryParams['q'] ?? '';
        $type = $request->queryParams['type'] ?? null; // jpeg, png, webp
        $minWidth = (int) ($request->queryParams['min_width'] ?? 0);
        $minHeight = (int) ($request->queryParams['min_height'] ?? 0);

        $images = $this->imageRepository->search($query, $type, $minWidth, $minHeight);

        return new JsonResponse([
            'results' => array_map(fn ($image) => [
                'ulid' => $image->ulid,
                'filename' => $image->filename,
                'url' => '/images/' . $image->filename,
                'thumbnail_url' => '/images/' . str_replace('_original.', '_thumbnail.', $image->filename),
                'alt_text' => $image->altText,
                'dimensions' => [
                    'width' => $image->width,
                    'height' => $image->height,
                ],
                'file_size' => [
                    'bytes' => $image->fileSize->toBytes(),
                    'human_readable' => $image->fileSize->toHumanReadable(),
                ],
                'mime_type' => $image->mimeType,
            ], $images),
            'count' => count($images),
        ]);
    }

    #[Route(path: '/images/{filename}', method: Method::GET, name: 'show_image')]
    public function showImageFile(string $filename): FileResult
    {
        $image = $this->imageRepository->findByFilename($filename);

        if ($image === null) {
            $image = $this->imageVariantRepository->findByFilename($filename);
        }

        if ($image === null) {
            throw FrameworkException::create(
                ErrorCode::ENTITY_NOT_FOUND,
                "Image not found: {$filename}"
            )->withData(['filename' => $filename]);
        }

        // The path already contains the full file path
        $file = $image->path->toString();

        if (! file_exists($file)) {
            throw FrameworkException::create(
                ErrorCode::ENTITY_NOT_FOUND,
                "Image file not found on filesystem: {$file}"
            )->withData(['filename' => $filename, 'file_path' => $file]);
        }

        // Determine MIME type based on file extension
        $mimeType = match (strtolower(pathinfo($file, PATHINFO_EXTENSION))) {
            'jpg', 'jpeg' => 'image/jpeg',
            'png' => 'image/png',
            'gif' => 'image/gif',
            'webp' => 'image/webp',
            'avif' => 'image/avif',
            default => 'image/jpeg'
        };

        return new FileResult($file, 'original', $mimeType);
    }
}
