<?php
declare(strict_types=1);

namespace Infolab\Controllers;

use Infolab\Database;
use Infolab\Request;
use Infolab\Response;
use Infolab\Validator;

/**
 * Galerie d'images par projet.
 *
 * Deux modes de création :
 *  - URL externe  (Content-Type: application/json + body {"url":"https://..."})
 *  - Upload disque (Content-Type: multipart/form-data + file=<binaire>)
 *
 * Les uploads vont dans `public/uploads/projects/:slug/<hash>.<ext>`, accessibles
 * en direct via Apache/Caddy. Le DELETE supprime aussi le fichier sur disque.
 */
final class ImagesController
{
    private const MAX_BYTES = 5 * 1024 * 1024; // 5 MB
    private const ALLOWED_MIMES = [
        'image/jpeg' => 'jpg',
        'image/png'  => 'png',
        'image/webp' => 'webp',
        'image/gif'  => 'gif',
    ];
    private const KINDS = ['link', 'document', 'dataset', 'video', 'other'];

    public function index(Request $req, array $params): void
    {
        $p = ProjectsController::findOr404($params['slug']);
        $rows = Database::fetchAll(
            'SELECT id, project_id, url, caption, sort_order, storage_path, mime_type, size_bytes, width, height, created_at, updated_at
             FROM project_images WHERE project_id = :pid ORDER BY sort_order ASC, id ASC',
            [':pid' => (int) $p['id']],
        );
        Response::json(['data' => array_map([self::class, 'present'], $rows)]);
    }

    public function store(Request $req, array $params): void
    {
        $p = ProjectsController::findOr404($params['slug']);

        // Branche A : upload disque (multipart/form-data)
        if ($req->isMultipart() && $req->file('file') !== null) {
            $this->storeUpload($req, $p);
            return;
        }

        // Branche B : URL externe (JSON body)
        $this->storeUrl($req, $p);
    }

    public function update(Request $req, array $params): void
    {
        $p   = ProjectsController::findOr404($params['slug']);
        $img = self::findOr404((int) $params['id'], (int) $p['id']);

        $body = $req->json();
        $v = new Validator($body);
        $v->string('caption', 255);
        $v->integer('sort_order', 0);
        $v->check();

        $update = [];
        foreach (['caption', 'sort_order'] as $f) {
            if (array_key_exists($f, $body)) {
                $update[$f] = $body[$f];
            }
        }
        if (isset($update['sort_order'])) {
            $update['sort_order'] = (int) $update['sort_order'];
        }
        if ($update !== []) {
            Database::update('project_images', $update, 'id = :id', [':id' => (int) $img['id']]);
        }
        $fresh = Database::fetchOne('SELECT * FROM project_images WHERE id = :id', [':id' => (int) $img['id']]);
        Response::json(self::present($fresh));
    }

    public function destroy(Request $req, array $params): void
    {
        $p   = ProjectsController::findOr404($params['slug']);
        $img = self::findOr404((int) $params['id'], (int) $p['id']);

        // Supprime la ligne en DB d'abord, puis le fichier disque (best-effort).
        Database::delete('project_images', 'id = :id', [':id' => (int) $img['id']]);
        if (!empty($img['storage_path'])) {
            $absolute = $this->docroot() . '/' . ltrim((string) $img['storage_path'], '/');
            if (is_file($absolute)) {
                @unlink($absolute);
            }
        }
        Response::noContent();
    }

    // -------------------------------------------------------------------------
    // Helpers privés
    // -------------------------------------------------------------------------

    /** @param array<string,mixed> $project */
    private function storeUrl(Request $req, array $project): void
    {
        $body = $req->json();
        $v = new Validator($body);
        $v->required('url')->url('url', 1024);
        $v->string('caption', 255);
        $v->integer('sort_order', 0);
        $v->check();

        $sortOrder = isset($body['sort_order'])
            ? (int) $body['sort_order']
            : self::nextSortOrder((int) $project['id']);

        $id = Database::insert('project_images', [
            'project_id'  => (int) $project['id'],
            'url'         => (string) $body['url'],
            'caption'     => $body['caption'] ?? null,
            'sort_order'  => $sortOrder,
        ]);
        $row = Database::fetchOne('SELECT * FROM project_images WHERE id = :id', [':id' => $id]);
        Response::json(self::present($row), 201);
    }

    /** @param array<string,mixed> $project */
    private function storeUpload(Request $req, array $project): void
    {
        $file = $req->file('file');
        if ($file === null) {
            throw new \ApiException(400, ['error' => 'bad_request', 'message' => 'no file uploaded']);
        }

        // 1) Erreur PHP d'upload ?
        if ($file['error'] !== UPLOAD_ERR_OK) {
            $msg = match ($file['error']) {
                UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE => 'file too large (PHP upload limit)',
                UPLOAD_ERR_PARTIAL    => 'upload incomplete',
                UPLOAD_ERR_NO_TMP_DIR => 'server tmp dir missing',
                UPLOAD_ERR_CANT_WRITE => 'server cannot write to disk',
                UPLOAD_ERR_EXTENSION  => 'PHP extension blocked the upload',
                default               => 'upload failed',
            };
            throw new \ApiException(400, ['error' => 'bad_request', 'message' => $msg]);
        }

        // 2) Taille
        if ($file['size'] > self::MAX_BYTES) {
            throw new \ApiException(422, [
                'error'  => 'validation_failed',
                'fields' => ['file' => 'too_large'],
            ]);
        }

        // 3) MIME type RÉEL (pas l'header fourni par le client, qui est falsifiable)
        $tmp = $file['tmp_name'];
        $finfo = new \finfo(FILEINFO_MIME_TYPE);
        $detected = (string) $finfo->file($tmp);
        if (!isset(self::ALLOWED_MIMES[$detected])) {
            throw new \ApiException(422, [
                'error'  => 'validation_failed',
                'fields' => ['file' => 'unsupported_mime'],
                'message' => sprintf('MIME %s non autorisé (autorisés : %s)', $detected, implode(', ', array_keys(self::ALLOWED_MIMES))),
            ]);
        }
        $ext = self::ALLOWED_MIMES[$detected];

        // 4) Dimensions image (peut échouer sur GIF/webp anciens, best-effort)
        $width = null;
        $height = null;
        $info = @getimagesize($tmp);
        if (is_array($info)) {
            $width  = (int) $info[0];
            $height = (int) $info[1];
        }

        // 5) Construit le chemin cible : public/uploads/projects/:slug/<random>.<ext>
        $slug = preg_replace('/[^a-z0-9\-]/', '', strtolower((string) $project['slug'])) ?? 'project';
        $dirRelative = "uploads/projects/$slug";
        $dirAbsolute = $this->docroot() . '/' . $dirRelative;
        if (!is_dir($dirAbsolute) && !@mkdir($dirAbsolute, 0755, true) && !is_dir($dirAbsolute)) {
            error_log("[Images] cannot create $dirAbsolute");
            throw new \ApiException(500, ['error' => 'internal_server_error']);
        }
        $basename = bin2hex(random_bytes(12)) . '.' . $ext;
        $pathRelative = "$dirRelative/$basename";
        $pathAbsolute = "$dirAbsolute/$basename";

        // 6) Déplace le fichier upload
        if (!move_uploaded_file($tmp, $pathAbsolute)) {
            error_log("[Images] move_uploaded_file failed -> $pathAbsolute");
            throw new \ApiException(500, ['error' => 'internal_server_error']);
        }
        // chmod défensif pour Apache.
        @chmod($pathAbsolute, 0644);

        // 7) Lit la légende & sort_order optionnels passés en multipart
        $caption = $req->formField('caption');
        $sortOrderRaw = $req->formField('sort_order');
        $sortOrder = $sortOrderRaw !== null && $sortOrderRaw !== ''
            ? (int) $sortOrderRaw
            : self::nextSortOrder((int) $project['id']);

        $publicUrl = '/' . $pathRelative;

        $id = Database::insert('project_images', [
            'project_id'   => (int) $project['id'],
            'url'          => $publicUrl,
            'caption'      => $caption,
            'sort_order'   => $sortOrder,
            'storage_path' => $pathRelative,
            'mime_type'    => $detected,
            'size_bytes'   => (int) $file['size'],
            'width'        => $width,
            'height'       => $height,
        ]);
        $row = Database::fetchOne('SELECT * FROM project_images WHERE id = :id', [':id' => $id]);
        Response::json(self::present($row), 201);
    }

    private static function findOr404(int $id, int $projectId): array
    {
        $row = Database::fetchOne(
            'SELECT * FROM project_images WHERE id = :id AND project_id = :pid',
            [':id' => $id, ':pid' => $projectId],
        );
        if ($row === null) {
            throw new \ApiException(404, ['error' => 'not_found']);
        }
        return $row;
    }

    private static function nextSortOrder(int $projectId): int
    {
        return (int) Database::fetchValue(
            'SELECT COALESCE(MAX(sort_order), 0) + 10 FROM project_images WHERE project_id = :pid',
            [':pid' => $projectId],
        );
    }

    /**
     * Racine document du serveur web (Apache DocumentRoot = .../public).
     * Le contrôleur tourne depuis public/index.php donc __DIR__ . '/../..' = public/
     * Sauf qu'on est dans src/Controllers/ → racine = src/../../public = public.
     */
    private function docroot(): string
    {
        return dirname(__DIR__, 2) . '/public';
    }

    /** @param array<string,mixed> $row */
    private static function present(array $row): array
    {
        return [
            'id'           => (int) $row['id'],
            'project_id'   => (int) $row['project_id'],
            'url'          => $row['url'],
            'caption'      => $row['caption'],
            'sort_order'   => (int) $row['sort_order'],
            'storage_path' => $row['storage_path'],
            'mime_type'    => $row['mime_type'],
            'size_bytes'   => $row['size_bytes'] !== null ? (int) $row['size_bytes'] : null,
            'width'        => $row['width']      !== null ? (int) $row['width']      : null,
            'height'       => $row['height']     !== null ? (int) $row['height']     : null,
            'created_at'   => $row['created_at'],
            'updated_at'   => $row['updated_at'],
        ];
    }
}
