<?php
/**
 * Fichier  : api/products.php
 * Auteur   : KREMER Régis
 * Version  : 2.3.0
 * Date     : 2026-02-26
 * Description : API REST — CRUD des produits avec upload image et validation MIME
 *
 * Changelog :
 *   v2.1.0 (2026-02-21) — Version initiale auditée et sécurisée
 *   v2.1.1 (2026-02-21) — Corrections sécurité (CSRF, MIME, session, migrateSuppliers)
 *   v2.2.0 (2026-02-21) — Permissions magasins, bons de commande, rapport d'écart,
 *   v2.3.0 (2026-02-26) — Lot 1: permissions fines + packagings dynamiques (migration).
 *                      import CSV, historique prix, bon de livraison multi-lignes
 */
require_once dirname(__DIR__).'/config.php';
startSession(); requireAuth();
$method = $_SERVER['REQUEST_METHOD'];
if($method === 'GET') {
    requirePerm('products.read');
}
if($method !== 'GET') {
    requireAdmin();
    csrfCheck();
}

// ── Lot 4: EAN multiples + versioning ──
function sanitizeEans($raw): array {
    if(is_string($raw)){
        $raw = array_filter(array_map('trim', preg_split('/[,;

	 ]+/', $raw)));
    }
    if(!is_array($raw)) return [];
    $out = [];
    foreach($raw as $e){
        $e = trim((string)$e);
        if($e==='') continue;
        if(!in_array($e,$out,true)) $out[] = $e;
    }
    return $out;
}


function sanitizeSupplierLinks($raw): array {
    if(is_string($raw)){
        $decoded = json_decode($raw, true);
        $raw = is_array($decoded) ? $decoded : [];
    }
    if(!is_array($raw)) return [];
    $out = [];
    foreach($raw as $row){
        if(!is_array($row)) continue;
        $supplierId = trim((string)($row['supplierId'] ?? ''));
        $supplierSku = trim((string)($row['supplierSku'] ?? ''));
        $packagingLabel = trim((string)($row['packagingLabel'] ?? ''));
        $allowedWarehouses = $row['allowedWarehouses'] ?? [];
        if(is_string($allowedWarehouses)){
            $allowedWarehouses = json_decode($allowedWarehouses, true) ?: [];
        }
        if(!is_array($allowedWarehouses)) $allowedWarehouses = [];
        $item = [
            'supplierId' => $supplierId,
            'supplierSku' => $supplierSku,
            'purchasePrice' => round((float)($row['purchasePrice'] ?? 0), 4),
            'packagingLabel' => $packagingLabel,
            'packagingQty' => (float)($row['packagingQty'] ?? 0),
            'minOrderQty' => (float)($row['minOrderQty'] ?? 0),
            'leadTimeDays' => (int)($row['leadTimeDays'] ?? 0),
            'discountRate' => (float)($row['discountRate'] ?? 0),
            'preferred' => !empty($row['preferred']),
            'allowedWarehouses' => array_values(array_filter(array_map('strval', $allowedWarehouses), fn($v)=>$v!=='')),
        ];
        if($item['supplierId']==='' && $item['supplierSku']==='' && $item['purchasePrice']<=0 && $item['packagingLabel']===''){
            continue;
        }
        $out[] = $item;
    }
    return $out;
}

function recordProductVersion(int $productId, int $version, string $action, array $before, array $after): void {
    $u = currentUser();
    $diff = [];
    $keys = array_unique(array_merge(array_keys($before), array_keys($after)));
    foreach($keys as $k){
        if(in_array($k,['updatedAt','createdAt'],true)) continue;
        $b = $before[$k] ?? null;
        $a = $after[$k] ?? null;
        if($b === $a) continue;
        $diff[$k] = ['from'=>$b,'to'=>$a];
    }
    $items = loadData('product_versions');
    $items[] = [
        'id' => genId(),
        'productId' => $productId,
        'version' => $version,
        'action' => $action,
        'userId' => (int)($u['id'] ?? 0),
        'userName' => (string)($u['name'] ?? $u['username'] ?? ''),
        'diff' => $diff,
        'createdAt' => date('c'),
    ];
    saveData('product_versions', $items);
}

// ── IMAGE UPLOAD (multipart/form-data) ──
// Supports create (default) and edit image (mode=editImage) without changing endpoints.
if($method === 'POST' && isset($_FILES['image']) && (($_GET['mode'] ?? '') !== 'editImage')){
    $name = trim($_POST['name'] ?? '');
    if($name === '') jsonError('Nom obligatoire.');
    $sku  = trim($_POST['sku'] ?? '') ?: nextSKU();
    foreach(loadData('products') as $p) if(($p['sku']??'') === $sku) jsonError('SKU déjà utilisé.');
    
    $imgUrl = '';
    if(($_FILES['image']['error'] ?? 1) === UPLOAD_ERR_OK){
        // Validate real MIME type (not just extension) to prevent PHP file upload
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $realMime = finfo_file($finfo, $_FILES['image']['tmp_name']);
        finfo_close($finfo);
        $allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
        $allowedExts  = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
        $ext = strtolower(pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION));
        if(in_array($realMime, $allowedMimes) && in_array($ext, $allowedExts)){
            $fn = uniqid('img_').'.'.$ext;
            if(move_uploaded_file($_FILES['image']['tmp_name'], UPLOADS_DIR.'/'.$fn)) $imgUrl = 'uploads/'.$fn;
        } else {
            jsonError('Type de fichier non autorisé. Utilisez JPG, PNG, GIF ou WEBP.', 400);
        }
    }
    
    $item = addItem('products', [
        'name'             => $name,
        'sku'              => $sku,
        'barcode'          => trim($_POST['barcode'] ?? ''),
        'brand'            => trim($_POST['brand'] ?? ''),
        'category'         => trim($_POST['category'] ?? ''),
        'description'      => trim($_POST['description'] ?? ''),
        'cost'             => floatval($_POST['cost'] ?? 0),
        'supplier'         => trim($_POST['supplier'] ?? ''),
        'supplierLinks'    => sanitizeSupplierLinks($_POST['supplierLinks'] ?? []),
        'allowedWarehouses' => (isset($_POST['allowedWarehouses']) ? (json_decode($_POST['allowedWarehouses'], true) ?: []) : []),
        'picking'          => trim($_POST['picking'] ?? ''),
        'location'         => trim($_POST['location'] ?? ''),
        'rack'             => trim($_POST['rack'] ?? ''),
        'shelf'            => trim($_POST['shelf'] ?? ''),
        'datasheetUrl'     => trim($_POST['datasheetUrl'] ?? ''),
        'msdsUrl'          => trim($_POST['msdsUrl'] ?? ''),
        // Packaging (legacy kept + new fields)
        'unitType'         => trim($_POST['unitType'] ?? 'piece'),
        'unitsPerBox'      => intval($_POST['unitsPerBox'] ?? 0),
        'boxesPerPallet'   => intval($_POST['boxesPerPallet'] ?? 0),
        'volume'           => floatval($_POST['volume'] ?? 0),
        'packBaseUnit'     => trim($_POST['packBaseUnit'] ?? ''),
        'packCartonQty'    => intval($_POST['packCartonQty'] ?? 0),
        'packPaletteQty'   => intval($_POST['packPaletteQty'] ?? 0),
        'packBidonQty'     => floatval($_POST['packBidonQty'] ?? 0),
        'packFutQty'       => floatval($_POST['packFutQty'] ?? 0),
        'packAerosolQty'   => floatval($_POST['packAerosolQty'] ?? 0),
        'packSacQty'       => floatval($_POST['packSacQty'] ?? 0),
        'packSeauQty'      => floatval($_POST['packSeauQty'] ?? 0),
        'packRouleauQty'   => floatval($_POST['packRouleauQty'] ?? 0),
        'packBobineQty'    => floatval($_POST['packBobineQty'] ?? 0),
        'packTubeQty'      => floatval($_POST['packTubeQty'] ?? 0),
        'packBoiteQty'     => floatval($_POST['packBoiteQty'] ?? 0),
        'packPaquetQty'    => floatval($_POST['packPaquetQty'] ?? 0),
        'costByUnit'       => (isset($_POST['costByUnit']) && is_string($_POST['costByUnit'])) ? (json_decode($_POST['costByUnit'], true) ?: []) : [],
        'trackLot'         => filter_var($_POST['trackLot'] ?? false, FILTER_VALIDATE_BOOLEAN),
        'trackExpiry'      => filter_var($_POST['trackExpiry'] ?? false, FILTER_VALIDATE_BOOLEAN),
        'shelfLifeDays'    => ($_POST['shelfLifeDays'] ?? '') !== '' ? intval($_POST['shelfLifeDays']) : null,
        'safetyStock'      => ($_POST['safetyStock'] ?? '') !== '' ? intval($_POST['safetyStock']) : null,
        'cump'             => floatval($_POST['cump'] ?? floatval($_POST['cost'] ?? 0)),
        'image'            => $imgUrl,
        'defaultWarehouse' => $_POST['defaultWarehouse'] ?? null,
        'eans'             => sanitizeEans($_POST['eans'] ?? []),
        'version'          => 1,
    ]);
    
    // Auto-create stock entry if defaultWarehouse set
    if(!empty($item['defaultWarehouse'])){
        $stk = loadData('stock');
        $stk[] = [
            'id'           => genId(),
            'productId'    => $item['id'],
            'warehouseId'  => (int)$item['defaultWarehouse'],
            'quantity'     => 0,
            'minStock'     => null,
            'maxStock'     => null,
            'createdAt'    => date('c'),
        ];
        saveData('stock', $stk);
    }
    recordProductVersion((int)($item['id'] ?? 0), 1, 'create', [], $item);
    jsonResponse($item, 201);
}

// ── IMAGE EDIT (multipart/form-data) ──
// Called by Api.prodEditImg() with ?mode=editImage
if($method === 'POST' && isset($_FILES['image']) && (($_GET['mode'] ?? '') === 'editImage')){
    $id = $_POST['id'] ?? null;
    if(!$id) jsonError('ID manquant.');

    $products = loadData('products');
    $p = findById($products, $id);
    if(!$p) jsonError('Non trouvé.', 404);

    $imgUrl = $p['image'] ?? '';
    if(($_FILES['image']['error'] ?? 1) === UPLOAD_ERR_OK){
        // Validate real MIME type
        $finfo = finfo_open(FILEINFO_MIME_TYPE);
        $realMime = finfo_file($finfo, $_FILES['image']['tmp_name']);
        finfo_close($finfo);
        $allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
        $allowedExts  = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
        $ext = strtolower(pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION));
        if(in_array($realMime, $allowedMimes) && in_array($ext, $allowedExts)){
            $fn = uniqid('img_').'.'.$ext;
            if(move_uploaded_file($_FILES['image']['tmp_name'], UPLOADS_DIR.'/'.$fn)){
                // Remove previous file if it was a local upload.
                if(!empty($imgUrl)){
                    $oldPath = APP_ROOT.'/'.$imgUrl;
                    if(file_exists($oldPath)) @unlink($oldPath);
                }
                $imgUrl = 'uploads/'.$fn;
            }
        } else {
            jsonError('Type de fichier non autorisé. Utilisez JPG, PNG, GIF ou WEBP.', 400);
        }
    }

    $fields = ['image' => $imgUrl];
    $before = $p; // capture state before update
    $nextVersion = (int)($p['version'] ?? 1) + 1;
    $fields['version'] = $nextVersion;
    $up = updateItem('products', $id, $fields);
    if($up){
        recordProductVersion((int)$id, $nextVersion, 'update', $before, $up);
        jsonResponse($up);
    }
    jsonError('Non trouvé.', 404);
}

$body = json_decode(file_get_contents('php://input'), true) ?? [];

switch($method){
case 'GET':
    $prods = loadData('products');
    $q     = strtolower($_GET['q'] ?? '');
    $cat   = $_GET['category'] ?? '';
    
    if($cat) $prods = array_filter($prods, fn($p) => ($p['category']??'') === $cat);
    if($q)   $prods = array_filter($prods, fn($p) =>
        str_contains(strtolower($p['name']??''), $q) ||
        str_contains(strtolower($p['sku']??''), $q) ||
        str_contains(strtolower($p['barcode']??''), $q ) ||
        (isset($p['eans']) && is_array($p['eans']) && in_array($q, array_map(fn($x)=>strtolower((string)$x), $p['eans']), true)) ||
        str_contains(strtolower($p['brand']??''), $q)
    );
    jsonResponse(array_values($prods));

case 'POST':
    $name = trim($body['name'] ?? '');
    if($name === '') jsonError('Nom obligatoire.');
    $sku  = trim($body['sku'] ?? '') ?: nextSKU();
    foreach(loadData('products') as $p) if(($p['sku']??'') === $sku) jsonError('SKU déjà utilisé.');
    
    $item = addItem('products', [
        'name'             => $name,
        'sku'              => $sku,
        'barcode'          => trim($body['barcode'] ?? ''),
        'brand'            => trim($body['brand'] ?? ''),
        'category'         => trim($body['category'] ?? ''),
        'description'      => trim($body['description'] ?? ''),
        'cost'             => floatval($body['cost'] ?? 0),
        'supplier'         => trim($body['supplier'] ?? ''),
        'supplierLinks'    => sanitizeSupplierLinks($body['supplierLinks'] ?? []),
        'allowedWarehouses' => is_array($body['allowedWarehouses'] ?? null) ? ($body['allowedWarehouses'] ?? []) : (json_decode($body['allowedWarehouses'] ?? '[]', true) ?: []),
        'picking'          => trim($body['picking'] ?? ''),
        'location'         => trim($body['location'] ?? ''),
        'rack'             => trim($body['rack'] ?? ''),
        'shelf'            => trim($body['shelf'] ?? ''),
        'datasheetUrl'     => trim($body['datasheetUrl'] ?? ''),
        'msdsUrl'          => trim($body['msdsUrl'] ?? ''),
        // Packaging (legacy kept + new fields)
        'unitType'         => trim($body['unitType'] ?? 'piece'),
        'unitsPerBox'      => intval($body['unitsPerBox'] ?? 0),
        'boxesPerPallet'   => intval($body['boxesPerPallet'] ?? 0),
        'volume'           => floatval($body['volume'] ?? 0),
        'packBaseUnit'     => trim($body['packBaseUnit'] ?? ''),
        'packCartonQty'    => intval($body['packCartonQty'] ?? 0),
        'packPaletteQty'   => intval($body['packPaletteQty'] ?? 0),
        'packBidonQty'     => floatval($body['packBidonQty'] ?? 0),
        'packFutQty'       => floatval($body['packFutQty'] ?? 0),
        'packAerosolQty'   => floatval($body['packAerosolQty'] ?? 0),
        'packSacQty'       => floatval($body['packSacQty'] ?? 0),
        'packSeauQty'      => floatval($body['packSeauQty'] ?? 0),
        'packRouleauQty'   => floatval($body['packRouleauQty'] ?? 0),
        'packBobineQty'    => floatval($body['packBobineQty'] ?? 0),
        'packTubeQty'      => floatval($body['packTubeQty'] ?? 0),
        'packBoiteQty'     => floatval($body['packBoiteQty'] ?? 0),
        'packPaquetQty'    => floatval($body['packPaquetQty'] ?? 0),
        'costByUnit'       => is_array($body['costByUnit'] ?? null) ? ($body['costByUnit'] ?? []) : [],
        'trackLot'         => (bool)($body['trackLot'] ?? false),
        'trackExpiry'      => (bool)($body['trackExpiry'] ?? false),
        'shelfLifeDays'    => ($body['shelfLifeDays'] ?? null) !== null && ($body['shelfLifeDays'] ?? '') !== '' ? intval($body['shelfLifeDays']) : null,
        'safetyStock'      => ($body['safetyStock'] ?? null) !== null && ($body['safetyStock'] ?? '') !== '' ? intval($body['safetyStock']) : null,
        'cump'             => floatval($body['cump'] ?? floatval($body['cost'] ?? 0)),
        'image'            => $body['image'] ?? '',
        'defaultWarehouse' => $body['defaultWarehouse'] ?? null,
        'eans'             => sanitizeEans($body['eans'] ?? []),
        'version'          => 1,
    ]);

    // Versioning trace (Lot 4)
    recordProductVersion((int)($item['id'] ?? 0), 1, 'create', [], $item);
    
    if(!empty($item['defaultWarehouse'])){
        $stk = loadData('stock');
        $stk[] = [
            'id'           => genId(),
            'productId'    => $item['id'],
            'warehouseId'  => (int)$item['defaultWarehouse'],
            'quantity'     => 0,
            'minStock'     => null,
            'maxStock'     => null,
            'createdAt'    => date('c'),
        ];
        saveData('stock', $stk);
    }
    jsonResponse($item, 201);

case 'PUT':
    $id = $body['id'] ?? null;
    if(!$id) jsonError('ID manquant.');
    $fields = [];
    foreach([
        'name','sku','barcode','brand','category','description','cost','supplier','supplierLinks','picking','location','rack','shelf','datasheetUrl','msdsUrl',
        'unitType','unitsPerBox','boxesPerPallet','volume',
        'packBaseUnit','packCartonQty','packPaletteQty','packBidonQty','packFutQty',
        'packAerosolQty','packSacQty','packSeauQty','packRouleauQty','packBobineQty','packTubeQty','packBoiteQty','packPaquetQty',
        'costByUnit','trackLot','trackExpiry','shelfLifeDays','safetyStock','cump',
        'image','defaultWarehouse','eans'
    ] as $k){
        if(array_key_exists($k, $body)) $fields[$k] = $body[$k];
    }
    
    // Handle allowedWarehouses specially - decode JSON string if needed
    if(array_key_exists('allowedWarehouses', $body)) {
        $aw = $body['allowedWarehouses'];
        if(is_string($aw)) {
            $fields['allowedWarehouses'] = json_decode($aw, true) ?: [];
        } elseif(is_array($aw)) {
            $fields['allowedWarehouses'] = $aw;
        } else {
            $fields['allowedWarehouses'] = [];
        }
    }
    
    $before = findById(loadData('products'), $id);
    if(!$before) jsonError('Non trouvé.', 404);

    // Sanitize EANs if present
    if(array_key_exists('eans',$fields)) $fields['eans'] = sanitizeEans($fields['eans']);
    if(array_key_exists('supplierLinks',$fields)) $fields['supplierLinks'] = sanitizeSupplierLinks($fields['supplierLinks']);

    // Increment product version (Lot 4)
    $nextVersion = (int)($before['version'] ?? 1) + 1;
    $fields['version'] = $nextVersion;

    if(isset($fields['sku'])){
        foreach(loadData('products') as $p){
            if((string)($p['id']??'') !== (string)$id && ($p['sku']??'') === $fields['sku']){
                jsonError('SKU déjà utilisé.');
            }
        }
    }
    $up = updateItem('products', $id, $fields);
    if($up){
        recordProductVersion((int)$id, $nextVersion, 'update', $before, $up);
        jsonResponse($up);
    }
    jsonError('Non trouvé.', 404);

case 'DELETE':
    $id = $body['id'] ?? $_GET['id'] ?? null;
    if(!$id) jsonError('ID manquant.');
    $p = findById(loadData('products'), $id);
    if($p && !empty($p['image'])){
        $path = APP_ROOT.'/'.$p['image'];
        if(file_exists($path)) unlink($path);
    }
    $s = array_filter(loadData('stock'), fn($x) => (string)($x['productId']??'') !== (string)$id);
    saveData('stock', array_values($s));
    $a = array_filter(loadData('alerts'), fn($x) => (string)($x['productId']??'') !== (string)$id);
    saveData('alerts', array_values($a));
    deleteItem('products', $id);
    jsonResponse(['ok' => true]);

default:
    jsonError('Méthode non supportée.', 405);
}
?>
