<?php

declare(strict_types=1);

final class MyronPublicMediaRenditionService
{
    /**
     * @var array<string, int>
     */
    public const IMAGE_RENDITION_WIDTHS = [
        'thumb' => 320,
        'card' => 400,
        'card-lg' => 960,
        'preview' => 1280,
        'hero' => 1920,
        'xl' => 2560,
    ];

    /**
     * @var array<string, string>
     */
    private const SOURCE_LOADERS = [
        'image/jpeg' => 'imagecreatefromjpeg',
        'image/png' => 'imagecreatefrompng',
        'image/webp' => 'imagecreatefromwebp',
    ];

    public function __construct(private readonly string $appRoot)
    {
    }

    public static function fromAppRoot(string $appRoot): self
    {
        return new self(rtrim($appRoot, DIRECTORY_SEPARATOR));
    }

    public function mediaRoot(): string
    {
        $configured = trim((string) (getenv('MYRON_STATIC_MEDIA_PATH') ?: ''));
        if ($configured !== '') {
            return rtrim($configured, DIRECTORY_SEPARATOR);
        }

        return $this->appRoot . DIRECTORY_SEPARATOR . 'assets' . DIRECTORY_SEPARATOR . 'media';
    }

    /**
     * @param array<string, mixed> $file
     * @param array<string, int> $renditionWidths
     * @return array<string, string>
     */
    public function staticRenditionUrlsForFile(array $file, array $renditionWidths = self::IMAGE_RENDITION_WIDTHS): array
    {
        $fileId = $this->safeFileId((string) ($file['id'] ?? ''));
        if ($fileId === '') {
            return [];
        }

        $urls = [];
        foreach ($renditionWidths as $preset => $_width) {
            $filename = $this->latestStaticFilename($fileId, (string) $preset);
            if ($filename === '') {
                continue;
            }
            $urls[(string) $preset] = myronBuildAssetUrl('media/' . $fileId . '/' . $filename);
        }

        return $urls;
    }

    /**
     * @param array<string, mixed> $file
     * @param array<string, int> $renditionWidths
     * @return array<string, array{path:string,url:string,width:int,height:int,mime:string,size:int}>
     */
    public function generateForFile(array $file, string $sourcePath, array $renditionWidths = self::IMAGE_RENDITION_WIDTHS): array
    {
        if (!is_file($sourcePath) || !$this->canRender((string) ($file['mime_type'] ?? ''))) {
            return [];
        }

        $fileId = $this->safeFileId((string) ($file['id'] ?? ''));
        if ($fileId === '') {
            return [];
        }

        $width = (int) ($file['image_width'] ?? 0);
        $height = (int) ($file['image_height'] ?? 0);
        if ($width <= 0 || $height <= 0) {
            $dimensions = @getimagesize($sourcePath);
            if (!is_array($dimensions) || !isset($dimensions[0], $dimensions[1])) {
                return [];
            }
            $width = (int) $dimensions[0];
            $height = (int) $dimensions[1];
        }
        if ($width <= 0 || $height <= 0) {
            return [];
        }

        $this->deleteForFile($fileId);
        $targetDir = $this->directoryForFileId($fileId);
        myronEnsureDirectory($targetDir);

        $generated = [];
        foreach ($renditionWidths as $preset => $targetWidth) {
            $preset = $this->safePreset((string) $preset);
            $targetWidth = (int) $targetWidth;
            if ($preset === '' || $targetWidth <= 0) {
                continue;
            }

            $effectiveWidth = min($width, $targetWidth);
            $effectiveHeight = max(1, (int) round(($effectiveWidth / $width) * $height));
            $tmpPath = $targetDir . DIRECTORY_SEPARATOR . '.' . $preset . '-' . bin2hex(random_bytes(4)) . '.webp';

            if (!$this->writeWebpRendition($sourcePath, (string) ($file['mime_type'] ?? ''), $tmpPath, $effectiveWidth, $effectiveHeight)) {
                if (is_file($tmpPath)) {
                    @unlink($tmpPath);
                }
                continue;
            }

            $hash = substr(hash_file('sha256', $tmpPath) ?: sha1((string) filemtime($tmpPath)), 0, 16);
            $filename = $preset . '-' . $effectiveWidth . 'w-' . $hash . '.webp';
            $targetPath = $targetDir . DIRECTORY_SEPARATOR . $filename;
            if (is_file($targetPath)) {
                @unlink($tmpPath);
            } elseif (!@rename($tmpPath, $targetPath)) {
                @unlink($tmpPath);
                continue;
            }

            $generated[$preset] = [
                'path' => $targetPath,
                'url' => myronBuildAssetUrl('media/' . $fileId . '/' . $filename),
                'width' => $effectiveWidth,
                'height' => $effectiveHeight,
                'mime' => 'image/webp',
                'size' => (int) filesize($targetPath),
            ];
        }

        return $generated;
    }

    public function deleteForFile(string $fileId): void
    {
        $safeId = $this->safeFileId($fileId);
        if ($safeId === '') {
            return;
        }

        $dir = $this->directoryForFileId($safeId);
        if (!is_dir($dir)) {
            return;
        }

        foreach (scandir($dir) ?: [] as $item) {
            if ($item === '.' || $item === '..') {
                continue;
            }
            $path = $dir . DIRECTORY_SEPARATOR . $item;
            if (is_file($path)) {
                @unlink($path);
            }
        }
        @rmdir($dir);

        $root = $this->mediaRoot();
        if (is_dir($root) && count(array_diff(scandir($root) ?: [], ['.', '..', '.gitignore'])) === 0) {
            // Keep app/assets/media/.gitignore in place as directory intent.
            return;
        }
    }

    /**
     * @param array<string, int> $renditionWidths
     * @return array{processed:int,generated:int,skipped:int,errors:int}
     */
    public function backfillFromDatabase(PDO $pdo, array $renditionWidths = self::IMAGE_RENDITION_WIDTHS): array
    {
        $summary = [
            'processed' => 0,
            'generated' => 0,
            'skipped' => 0,
            'errors' => 0,
        ];

        $statement = $pdo->query(
            "SELECT * FROM files
             WHERE mime_type IN ('image/jpeg', 'image/png', 'image/webp')
             ORDER BY created_at ASC, id ASC"
        );
        if (!$statement) {
            return $summary;
        }

        foreach ($statement->fetchAll(PDO::FETCH_ASSOC) ?: [] as $row) {
            if (!is_array($row)) {
                continue;
            }
            $summary['processed']++;
            $storedFilename = basename(trim((string) ($row['stored_filename'] ?? '')));
            $sourcePath = $storedFilename !== '' ? myronUploadsDir() . DIRECTORY_SEPARATOR . $storedFilename : '';
            if ($sourcePath === '' || !is_file($sourcePath)) {
                $summary['skipped']++;
                continue;
            }

            try {
                $generated = $this->generateForFile($row, $sourcePath, $renditionWidths);
                if ($generated === []) {
                    $summary['skipped']++;
                } else {
                    $summary['generated']++;
                }
            } catch (Throwable $exception) {
                $summary['errors']++;
                error_log('[static-media-backfill] failed for ' . (string) ($row['id'] ?? '') . ': ' . $exception->getMessage());
            }
        }

        return $summary;
    }

    private function directoryForFileId(string $fileId): string
    {
        return $this->mediaRoot() . DIRECTORY_SEPARATOR . $this->safeFileId($fileId);
    }

    private function latestStaticFilename(string $fileId, string $preset): string
    {
        $dir = $this->directoryForFileId($fileId);
        if (!is_dir($dir)) {
            return '';
        }

        $preset = $this->safePreset($preset);
        if ($preset === '') {
            return '';
        }

        $matches = glob($dir . DIRECTORY_SEPARATOR . $preset . '-*w-*.webp') ?: [];
        $matches = array_values(array_filter($matches, static function (string $path) use ($preset): bool {
            return is_file($path) && preg_match('/^' . preg_quote($preset, '/') . '-[0-9]+w-[a-f0-9]{16}\.webp$/', basename($path)) === 1;
        }));
        if ($matches === []) {
            return '';
        }

        usort($matches, static function (string $a, string $b): int {
            $mtime = (int) filemtime($b) <=> (int) filemtime($a);
            return $mtime !== 0 ? $mtime : strcmp($b, $a);
        });

        return basename($matches[0]);
    }

    private function canRender(string $mimeType): bool
    {
        $mimeType = strtolower(trim($mimeType));
        if (!isset(self::SOURCE_LOADERS[$mimeType])) {
            return false;
        }
        if (!function_exists('imagecreatetruecolor') || !function_exists('imagecopyresampled') || !function_exists('imagewebp')) {
            return false;
        }

        return function_exists(self::SOURCE_LOADERS[$mimeType]);
    }

    private function writeWebpRendition(string $sourcePath, string $mimeType, string $targetPath, int $targetWidth, int $targetHeight): bool
    {
        $mimeType = strtolower(trim($mimeType));
        $loader = self::SOURCE_LOADERS[$mimeType] ?? '';
        if ($loader === '' || !function_exists($loader)) {
            return false;
        }

        try {
            $source = @$loader($sourcePath);
            if (!$source) {
                return false;
            }

            $canvas = imagecreatetruecolor($targetWidth, $targetHeight);
            if (!$canvas) {
                unset($source);
                return false;
            }
            imagealphablending($canvas, false);
            imagesavealpha($canvas, true);
            $transparent = imagecolorallocatealpha($canvas, 0, 0, 0, 127);
            if ($transparent !== false) {
                imagefilledrectangle($canvas, 0, 0, $targetWidth, $targetHeight, $transparent);
            }

            $sourceWidth = imagesx($source);
            $sourceHeight = imagesy($source);
            if ($sourceWidth <= 0 || $sourceHeight <= 0) {
                unset($source, $canvas);
                return false;
            }

            $ok = imagecopyresampled($canvas, $source, 0, 0, 0, 0, $targetWidth, $targetHeight, $sourceWidth, $sourceHeight);
            if (!$ok) {
                unset($source, $canvas);
                return false;
            }

            myronEnsureDirectory(dirname($targetPath));
            $written = imagewebp($canvas, $targetPath, 82);
            unset($source, $canvas);

            return (bool) $written && is_file($targetPath);
        } catch (Throwable $exception) {
            if (is_file($targetPath)) {
                @unlink($targetPath);
            }
            error_log('[static-media] rendition generation failed: ' . $exception->getMessage());
            return false;
        }
    }

    private function safeFileId(string $fileId): string
    {
        return preg_replace('/[^A-Za-z0-9_-]+/', '', trim($fileId)) ?: '';
    }

    private function safePreset(string $preset): string
    {
        return preg_replace('/[^A-Za-z0-9_-]+/', '', strtolower(trim($preset))) ?: '';
    }
}

if (!function_exists('myronPublicMediaRenditionService')) {
    function myronPublicMediaRenditionService(): MyronPublicMediaRenditionService
    {
        static $service = null;
        if (!$service instanceof MyronPublicMediaRenditionService) {
            $service = MyronPublicMediaRenditionService::fromAppRoot(dirname(__DIR__, 2));
        }

        return $service;
    }
}
