<?php
declare(strict_types=1);
namespace App\Security;

use InvalidArgumentException;
use RuntimeException;

final class SecureFileService
{
    public static function storeUploaded(array $file, string $root, array $types, int $maxBytes, string $prefix='file'): array
    {
        if (($file['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) throw new InvalidArgumentException('Fichier absent ou incomplet.');
        $tmp=(string)($file['tmp_name']??''); $size=(int)($file['size']??0);
        if($size<1 || $size>$maxBytes) throw new InvalidArgumentException('Taille de fichier non autorisée.');
        if($tmp==='' || (!is_uploaded_file($tmp) && !is_file($tmp))) throw new InvalidArgumentException('Fichier temporaire invalide.');
        $mime=(new \finfo(FILEINFO_MIME_TYPE))->file($tmp) ?: 'application/octet-stream';
        if(!isset($types[$mime])) throw new InvalidArgumentException('Type de fichier non autorisé : '.$mime);
        $original=self::cleanOriginalName((string)($file['name']??'document'));
        $clientExt=strtolower(pathinfo($original,PATHINFO_EXTENSION));
        $expected=(array)$types[$mime]; $expected=array_values(array_filter(array_map('strtolower',$expected)));
        if($clientExt!=='' && !in_array($clientExt,$expected,true)) throw new InvalidArgumentException('Extension incompatible avec le contenu réel du fichier.');
        if(!is_dir($root) && !mkdir($root,0770,true) && !is_dir($root)) throw new RuntimeException('Impossible de créer le stockage privé.');
        $base=realpath($root); if($base===false) throw new RuntimeException('Stockage privé indisponible.');
        $name=preg_replace('/[^a-z0-9_-]+/i','-',trim($prefix,'-_')) ?: 'file';
        $stored=$name.'_'.bin2hex(random_bytes(20)).'.'.$expected[0];
        $target=$base.DIRECTORY_SEPARATOR.$stored;
        if(!move_uploaded_file($tmp,$target) && !rename($tmp,$target)) throw new RuntimeException('Impossible de stocker le fichier.');
        @chmod($target,0640);
        $real=realpath($target);
        if($real===false || !str_starts_with($real,$base.DIRECTORY_SEPARATOR)) { @unlink($target); throw new RuntimeException('Chemin de stockage refusé.'); }
        $verified=(new \finfo(FILEINFO_MIME_TYPE))->file($real) ?: 'application/octet-stream';
        if($verified!==$mime){@unlink($real);throw new RuntimeException('Le type du fichier a changé pendant le stockage.');}
        return ['path'=>$real,'name'=>$stored,'original'=>$original,'mime'=>$mime,'size'=>filesize($real)?:$size,'sha256'=>hash_file('sha256',$real)];
    }

    public static function requirePrivatePath(string $path,string $root): string
    {
        $base=realpath($root);$real=realpath($path);
        if($base===false||$real===false||!is_file($real)||!str_starts_with($real,$base.DIRECTORY_SEPARATOR)) throw new RuntimeException('Ressource introuvable.');
        return $real;
    }

    public static function cleanOriginalName(string $name): string
    {
        $name=basename(str_replace("\0",'',trim($name)));
        $name=preg_replace('/[\x00-\x1F\x7F]+/u','',$name)??'';
        return mb_substr($name!==''?$name:'document',0,180);
    }
}
