<?php

declare(strict_types=1);

namespace App\Controllers\Admin;

use App\Core\ImageService;
use App\Core\Request;
use App\Core\Response;
use App\Core\Session;
use App\Core\Settings;
use App\Core\View;
use App\Models\GalleryRepository;
use App\Models\ImageProfile;
use App\Models\ImageRepository;
use App\Support\SlugService;
use Throwable;

final class ImageController extends AdminController
{
    private const MAX_BYTES = 31_457_280; // 30 Mo

    /** @param array<string,string> $params */
    public function index(Request $request, array $params): Response
    {
        if ($r = $this->guard()) {
            return $r;
        }
        $gid     = (int) ($params['gid'] ?? 0);
        $gallery = GalleryRepository::find($gid);
        if ($gallery === null) {
            return $this->missing();
        }

        return Response::html(View::render('admin/images/index', [
            'pageTitle' => 'Images — ' . $gallery['title'],
            'gallery'   => $gallery,
            'images'    => ImageRepository::listFor($gid),
            'minDims'   => [
                'lw' => (int) Settings::get('upload_min_landscape_w', '1920'),
                'lh' => (int) Settings::get('upload_min_landscape_h', '1280'),
                'pw' => (int) Settings::get('upload_min_portrait_w', '1280'),
                'ph' => (int) Settings::get('upload_min_portrait_h', '1920'),
            ],
            'flash'     => Session::pull('flash'),
            'errors'    => (array) (Session::pull('flash_errors') ?? []),
        ], 'layouts/admin'));
    }

    /** Téléversement d'UNE image (AJAX JSON, appelé en séquence côté client). @param array<string,string> $params */
    public function upload(Request $request, array $params): Response
    {
        if ($r = $this->guard()) {
            return $r;
        }
        $gid     = (int) ($params['gid'] ?? 0);
        $gallery = GalleryRepository::find($gid);
        if ($gallery === null) {
            return $this->json(['success' => false, 'error' => 'Galerie introuvable.'], 404);
        }
        if (!$this->csrfValid($request)) {
            return $this->json(['success' => false, 'error' => 'Jeton invalide.'], 400);
        }

        $file = $_FILES['file'] ?? null;
        if (($err = self::validateUpload($file)) !== null) {
            return $this->json(['success' => false, 'error' => $err], 422);
        }

        self::raiseLimits();
        $base = ImageRepository::uniqueBase($gid, SlugService::make((string) pathinfo((string) $file['name'], PATHINFO_FILENAME)));
        try {
            $dim = ImageService::process((string) $file['tmp_name'], $gid, $base, ImageProfile::all());
        } catch (Throwable) {
            return $this->json(['success' => false, 'error' => 'Traitement de l\'image impossible.'], 500);
        }

        $name = trim((string) pathinfo((string) $file['name'], PATHINFO_FILENAME));
        $id   = ImageRepository::create($gid, [
            'name'          => $name !== '' ? $name : 'Image',
            'alt'           => $name !== '' ? $name : 'Image',
            'filename'      => $base . '.' . $dim['ext'],
            'original_name' => (string) $file['name'],
            'width'         => $dim['width'],
            'height'        => $dim['height'],
            'sort_order'    => ImageRepository::nextSortOrder($gid),
            'status'        => 'published',
        ]);

        $img  = ImageRepository::find($id);
        $html = View::render('admin/images/_card', ['img' => $img, 'gid' => $gid], null);

        return $this->json(['success' => true, 'html' => $html]);
    }

    /** @param array<string,string> $params */
    public function update(Request $request, array $params): Response
    {
        if ($r = $this->guard()) {
            return $r;
        }
        $gid = (int) ($params['gid'] ?? 0);
        $id  = (int) ($params['id'] ?? 0);
        $img = ImageRepository::find($id);
        if ($img === null || (int) $img['gallery_id'] !== $gid) {
            return $this->missing();
        }
        $redirect = Response::redirect(base_url('admin/galleries/' . $gid . '/images'));
        if (!$this->csrfValid($request)) {
            return $redirect;
        }

        $name = trim((string) $request->input('name', ''));
        $alt  = trim((string) $request->input('alt', ''));
        if ($name === '') {
            Session::set('flash_errors', ['Le nom de l\'image est obligatoire.']);
            return $redirect;
        }

        // Le statut est géré séparément (bascule AJAX) : on le préserve ici.
        ImageRepository::update($id, [
            'name'   => $name,
            'alt'    => $alt !== '' ? $alt : $name,
            'status' => (string) $img['status'],
        ]);
        Session::set('flash', 'Image mise à jour.');

        return $redirect;
    }

    /** Remplace le fichier d'une image existante (AJAX JSON ; même base, dérivés régénérés). @param array<string,string> $params */
    public function replace(Request $request, array $params): Response
    {
        if ($r = $this->guard()) {
            return $r;
        }
        $gid = (int) ($params['gid'] ?? 0);
        $id  = (int) ($params['id'] ?? 0);
        $img = ImageRepository::find($id);
        if ($img === null || (int) $img['gallery_id'] !== $gid) {
            return $this->json(['success' => false, 'error' => 'Image introuvable.'], 404);
        }
        if (!$this->csrfValid($request)) {
            return $this->json(['success' => false, 'error' => 'Jeton invalide.'], 400);
        }

        $file = $_FILES['file'] ?? null;
        if (($err = self::validateUpload($file)) !== null) {
            return $this->json(['success' => false, 'error' => $err], 422);
        }

        self::raiseLimits();
        $base   = (string) pathinfo((string) $img['filename'], PATHINFO_FILENAME);
        $oldExt = strtolower((string) pathinfo((string) $img['filename'], PATHINFO_EXTENSION));

        try {
            $dim = ImageService::process((string) $file['tmp_name'], $gid, $base, ImageProfile::all());
        } catch (Throwable) {
            return $this->json(['success' => false, 'error' => 'Traitement de l\'image impossible.'], 500);
        }

        // Changement de format (jpg <-> png) : nettoie l'ancien dérivé devenu orphelin.
        if ($dim['ext'] !== $oldExt) {
            ImageService::deleteDerivative($gid, $base, $oldExt, ImageProfile::all());
        }
        $filename = $base . '.' . $dim['ext'];
        ImageRepository::updateFile($id, $filename, $dim['width'], $dim['height'], (string) $file['name']);

        return $this->json(['success' => true, 'thumb' => media_url($gid, $filename, 'thumb')]);
    }

    /** Bascule du statut publié/masqué d'une image (AJAX JSON). @param array<string,string> $params */
    public function status(Request $request, array $params): Response
    {
        if ($r = $this->guard()) {
            return $r;
        }
        if (!$this->csrfValid($request)) {
            return $this->json(['success' => false], 400);
        }
        $gid = (int) ($params['gid'] ?? 0);
        $id  = (int) ($params['id'] ?? 0);
        $img = ImageRepository::find($id);
        if ($img === null || (int) $img['gallery_id'] !== $gid) {
            return $this->json(['success' => false], 404);
        }
        $status = $request->input('value') ? 'published' : 'hidden';
        ImageRepository::setStatus($id, $status);

        return $this->json(['success' => true, 'status' => $status]);
    }

    /** @param array<string,mixed> $data */
    private function json(array $data, int $code = 200): Response
    {
        return new Response((string) json_encode($data), $code, ['Content-Type' => 'application/json']);
    }

    /** @param array<string,string> $params */
    public function destroy(Request $request, array $params): Response
    {
        if ($r = $this->guard()) {
            return $r;
        }
        $gid = (int) ($params['gid'] ?? 0);
        $id  = (int) ($params['id'] ?? 0);
        $img = ImageRepository::find($id);
        if ($img !== null && (int) $img['gallery_id'] === $gid && $this->csrfValid($request)) {
            $base = (string) pathinfo((string) $img['filename'], PATHINFO_FILENAME);
            ImageRepository::delete($id);
            ImageService::deleteFor($gid, $base, ImageProfile::all());
            Session::set('flash', 'Image supprimée.');
        }

        return Response::redirect(base_url('admin/galleries/' . $gid . '/images'));
    }

    /** @param array<string,string> $params */
    public function move(Request $request, array $params): Response
    {
        if ($r = $this->guard()) {
            return $r;
        }
        $gid = (int) ($params['gid'] ?? 0);
        $id  = (int) ($params['id'] ?? 0);
        $dir = (string) $request->input('dir', '');
        $img = ImageRepository::find($id);
        if ($img !== null && (int) $img['gallery_id'] === $gid && $this->csrfValid($request) && in_array($dir, ['up', 'down'], true)) {
            ImageRepository::move($id, $dir);
        }

        return Response::redirect(base_url('admin/galleries/' . $gid . '/images'));
    }

    /** Réordonnancement des images par glisser-déposer (AJAX). @param array<string,string> $params */
    public function reorder(Request $request, array $params): Response
    {
        if ($r = $this->guard()) {
            return $r;
        }
        $gid = (int) ($params['gid'] ?? 0);
        if (GalleryRepository::find($gid) === null) {
            return $this->missing();
        }
        if (!$this->csrfValid($request)) {
            return Response::html('', 400);
        }

        $ids = $request->input('order');
        if (is_array($ids)) {
            $clean = array_values(array_filter(array_map('intval', $ids), static fn ($x) => $x > 0));
            if ($clean !== []) {
                ImageRepository::reorder($gid, $clean);
            }
        }

        return Response::html('', 204);
    }

    // ---- Helpers ---------------------------------------------------------

    /** Valide $_FILES['file'] (présence, erreur, taille, type, dimensions mini). Retourne un message ou null. */
    private static function validateUpload(mixed $file): ?string
    {
        if (!is_array($file) || (int) ($file['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE) {
            return 'Aucun fichier reçu.';
        }
        if ((int) $file['error'] !== UPLOAD_ERR_OK) {
            return 'Échec de téléversement.';
        }
        if ((int) $file['size'] > self::MAX_BYTES) {
            return 'Fichier trop volumineux (max 30 Mo).';
        }
        if (!in_array(self::mimeOf((string) $file['tmp_name']), ImageService::ACCEPTED, true)) {
            return 'Type non supporté (JPEG ou PNG uniquement).';
        }
        $size = @getimagesize((string) $file['tmp_name']);
        if ($size === false) {
            return 'Image illisible.';
        }
        return self::tooSmall((int) $size[0], (int) $size[1]);
    }

    /** Contrôle la taille minimale paramétrée, selon l'orientation. Retourne un message ou null. */
    private static function tooSmall(int $w, int $h): ?string
    {
        $landscape = $w >= $h;
        $minW = (int) Settings::get($landscape ? 'upload_min_landscape_w' : 'upload_min_portrait_w', $landscape ? '1920' : '1280');
        $minH = (int) Settings::get($landscape ? 'upload_min_landscape_h' : 'upload_min_portrait_h', $landscape ? '1280' : '1920');
        if ($minW > 0 && $minH > 0 && ($w < $minW || $h < $minH)) {
            return 'Image trop petite : ' . $w . '×' . $h . ' px (minimum ' . $minW . '×' . $minH . ' en ' . ($landscape ? 'paysage' : 'portrait') . ').';
        }
        return null;
    }

    /** Relève mémoire / temps d'exécution pour les gros fichiers (jusqu'à 30 Mo). */
    private static function raiseLimits(): void
    {
        @ini_set('memory_limit', '512M');
        if (function_exists('set_time_limit')) {
            @set_time_limit(120);
        }
    }

    private static function mimeOf(string $path): string
    {
        if (function_exists('finfo_open')) {
            $finfo = finfo_open(FILEINFO_MIME_TYPE);
            if ($finfo !== false) {
                $mime = finfo_file($finfo, $path);
                finfo_close($finfo);
                return (string) $mime;
            }
        }
        $info = @getimagesize($path);
        return is_array($info) ? (string) ($info['mime'] ?? '') : '';
    }

    private function missing(): Response
    {
        return Response::html(View::render('errors/404', ['pageTitle' => 'Introuvable']), 404);
    }
}
