<?php

use App\Controllers\PublicController;

/**
 * Media uploads for the admin console.
 *
 *   POST /api/media/upload   (multipart: file=<image|video>, alt?)  -> { url, ... }
 *   GET  /api/media/list                                            -> recent uploads
 *
 * Admin-gated. Images are optimised with GD (downscaled, re-encoded); videos
 * are passed through ffmpeg (scaled, compressed, audio dropped). Everything is
 * written to app/public/media/ — the SOURCE tree — so it survives rebuilds and
 * is copied into public/ on the next build. A cja_media row is recorded.
 */
class Media extends PublicController
{
    private const MAX_IMAGE_W = 1600;   // downscale wider images
    private const MAX_VIDEO_W = 900;
    private const MEDIA_DIR = '/www/wwwroot/CarlosAriasPersonal/app/public/media';

    private const IMAGE_MIME = [
        'image/jpeg' => 'jpg', 'image/png' => 'png',
        'image/webp' => 'webp', 'image/gif' => 'gif',
    ];
    private const VIDEO_MIME = ['video/mp4' => 'mp4', 'video/quicktime' => 'mp4'];

    public function upload(): void
    {
        $this->requireAdmin();
        $this->guardPublic('media_upload', 60, 300);

        if (empty($_FILES['file']) || ($_FILES['file']['error'] ?? 1) !== UPLOAD_ERR_OK) {
            $this->json(null, 400, ['code' => 'no_file', 'message' => 'No file received.']);
        }

        $tmp  = $_FILES['file']['tmp_name'];
        $orig = $_FILES['file']['name'];
        $ext  = strtolower(pathinfo($orig, PATHINFO_EXTENSION));
        $alt  = mb_substr(trim((string) ($_POST['alt'] ?? '')), 0, 255);

        if (!is_dir(self::MEDIA_DIR)) {
            @mkdir(self::MEDIA_DIR, 0775, true);
        }

        $base = $this->slug(pathinfo($orig, PATHINFO_FILENAME));
        $rand = substr(bin2hex(random_bytes(4)), 0, 6);

        // Detect type without fileinfo: getimagesize() covers images (and gives
        // the mime); video is recognised by extension and validated by ffmpeg.
        $info = @getimagesize($tmp);
        if ($info && !empty($info['mime']) && isset(self::IMAGE_MIME[$info['mime']])) {
            $result = $this->handleImage($tmp, $base, $rand, $info['mime']);
        } elseif (in_array($ext, ['mp4', 'mov', 'm4v'], true)) {
            $result = $this->handleVideo($tmp, $base, $rand);
        } else {
            $this->json(null, 415, ['code' => 'bad_type', 'message' => 'Only images (JPG, PNG, WebP, GIF) and MP4 video are allowed.']);
        }

        \Db::insert('cja_media', [
            'kind'     => $result['kind'],
            'url'      => $result['url'],
            'filename' => $result['filename'],
            'mime'     => $result['mime'],
            'width'    => $result['width'] ?? null,
            'height'   => $result['height'] ?? null,
            'bytes'    => $result['bytes'] ?? null,
            'alt'      => $alt,
        ]);

        $this->json($result + ['alt' => $alt]);
    }

    public function list(): void
    {
        $this->requireAdmin();
        $rows = \Db::select('SELECT kind, url, filename, width, height, alt, created_at
                             FROM cja_media ORDER BY created_at DESC LIMIT 200');
        $this->json(['media' => $rows]);
    }

    // ---- image: GD downscale + re-encode ------------------------------------
    private function handleImage(string $tmp, string $base, string $rand, string $mime): array
    {
        // GIFs (often animated) pass through untouched; others are re-encoded.
        if ($mime === 'image/gif') {
            $name = "{$base}-{$rand}.gif";
            $dest = self::MEDIA_DIR . "/{$name}";
            copy($tmp, $dest);
            @chmod($dest, 0664);
            [$w, $h] = @getimagesize($dest) ?: [null, null];
            return ['kind' => 'image', 'url' => "/media/{$name}", 'filename' => $name,
                    'mime' => 'image/gif', 'width' => $w, 'height' => $h, 'bytes' => filesize($dest)];
        }

        $src = match ($mime) {
            'image/jpeg' => imagecreatefromjpeg($tmp),
            'image/png'  => imagecreatefrompng($tmp),
            'image/webp' => imagecreatefromwebp($tmp),
        };
        if (!$src) {
            $this->json(null, 422, ['code' => 'decode_failed', 'message' => 'Could not read that image.']);
        }

        $w = imagesx($src);
        $h = imagesy($src);
        if ($w > self::MAX_IMAGE_W) {
            $nw = self::MAX_IMAGE_W;
            $nh = (int) round($h * ($nw / $w));
            $dst = imagecreatetruecolor($nw, $nh);
            // preserve alpha for png/webp
            imagealphablending($dst, false);
            imagesavealpha($dst, true);
            imagecopyresampled($dst, $src, 0, 0, 0, 0, $nw, $nh, $w, $h);
            imagedestroy($src);
            $src = $dst;
            $w = $nw; $h = $nh;
        }

        // JPG output for photos; keep PNG/WebP for graphics with transparency.
        $hasAlpha = in_array($mime, ['image/png', 'image/webp'], true);
        $ext = $hasAlpha ? 'webp' : 'jpg';
        $name = "{$base}-{$rand}.{$ext}";
        $dest = self::MEDIA_DIR . "/{$name}";

        if ($ext === 'webp') {
            imagewebp($src, $dest, 82);
        } else {
            imagejpeg($src, $dest, 82);
        }
        imagedestroy($src);
        @chmod($dest, 0664);

        return ['kind' => 'image', 'url' => "/media/{$name}", 'filename' => $name,
                'mime' => $ext === 'webp' ? 'image/webp' : 'image/jpeg',
                'width' => $w, 'height' => $h, 'bytes' => filesize($dest)];
    }

    // ---- video: ffmpeg scale + compress -------------------------------------
    private function handleVideo(string $tmp, string $base, string $rand): array
    {
        $name = "{$base}-{$rand}.mp4";
        $dest = self::MEDIA_DIR . "/{$name}";

        // scale down to <= MAX_VIDEO_W wide (keep even dims), drop audio, web-optimise
        $cmd = sprintf(
            'ffmpeg -y -v error -i %s -vf "scale=%d:-2:flags=lanczos" -an ' .
            '-c:v libx264 -preset veryfast -crf 30 -pix_fmt yuv420p -movflags +faststart %s',
            escapeshellarg($tmp), self::MAX_VIDEO_W, escapeshellarg($dest)
        );
        exec($cmd, $out, $code);
        if ($code !== 0 || !file_exists($dest)) {
            $this->json(null, 422, ['code' => 'video_failed', 'message' => 'Could not process that video.']);
        }
        @chmod($dest, 0664);

        return ['kind' => 'video', 'url' => "/media/{$name}", 'filename' => $name,
                'mime' => 'video/mp4', 'width' => null, 'height' => null, 'bytes' => filesize($dest)];
    }

    private function slug(string $s): string
    {
        $s = strtolower(trim($s));
        $s = preg_replace('/[^a-z0-9]+/', '-', $s);
        $s = trim($s, '-');
        return $s !== '' ? mb_substr($s, 0, 40) : 'file';
    }
}
