<?php

/**

 * Fichier  : api/orders.php

 * Auteur   : KREMER Régis

 * Version  : 2.2.0

 * Date     : 2026-02-21

 * Description : API REST — CRUD des bons de commande fournisseurs

 *

 * Changelog :

 *   v2.2.0 (2026-02-21) — Création : bons de commande avec statuts draft/sent/partial/received/cancelled

 *

 * Statuts :

 *   draft     → brouillon, éditable

 *   sent      → envoyé au fournisseur

 *   partial   → réception partielle

 *   received  → entièrement reçu (génère des mouvements 'entry')

 *   cancelled → annulé

 */

require_once dirname(__DIR__).'/config.php';



// -----------------------------------------------------------------------------

// Compat : certains modules utilisent toBaseQty() pour convertir en unité de base.

// Sur StockMaster (stock en JSON), si la fonction n'existe pas, on reste en "as-is".

// -----------------------------------------------------------------------------

if (!function_exists('toBaseQty')) {

    function toBaseQty($product, $unit, $qty) {

        // Delegate to config.php toBaseQuantity() for proper unit conversion

        return toBaseQuantity($product, (string)$unit, (float)$qty);

    }

}



// ----------------------------------------------------------------------------

// Compat : findStockIndex() — retourne l'index d'une ligne stock pour (productId, warehouseId)

// Certains anciens modules peuvent l'avoir dans config.php; sinon on fournit une version locale.

// ----------------------------------------------------------------------------

if (!function_exists('findStockIndex')) {

    function findStockIndex($stock, $productId, $warehouseId) {

        $pid = (int)$productId;

        $wid = (int)$warehouseId;

        foreach (($stock ?? []) as $i => $s) {

            if ((int)($s['productId'] ?? 0) === $pid && (int)($s['warehouseId'] ?? 0) === $wid) {

                return $i;

            }

        }

        return null;

    }

}



startSession(); requireAuth();

header('Content-Type: application/json; charset=utf-8');



// --- Robust JSON error handling (prevents empty/invalid responses on fatal errors) ---

// Convert PHP warnings/notices into exceptions so they are caught by the try/catch below.

set_error_handler(function($severity, $message, $file, $line) {

    if (!(error_reporting() & $severity)) return false;

    throw new ErrorException($message, 0, $severity, $file, $line);

});



// If a fatal error happens (e.g. undefined function), return JSON instead of an empty body.

register_shutdown_function(function() {

    $err = error_get_last();

    if (!$err) return;

    $fatal = [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR];

    if (!in_array($err['type'] ?? 0, $fatal, true)) return;

    if (headers_sent()) return;

    http_response_code(500);

    header('Content-Type: application/json; charset=utf-8');

    echo json_encode([

        'error'   => 'Erreur serveur (fatal)',

        'details' => ($err['message'] ?? 'Erreur inconnue'),

        'file'    => basename($err['file'] ?? ''),

        'line'    => $err['line'] ?? null,

    ], JSON_UNESCAPED_UNICODE);

});



$method = $_SERVER['REQUEST_METHOD'];

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



if ($method !== 'GET') csrfCheck();



// Write operations restricted to admin

if (in_array($method, ['POST','PUT','DELETE'])) requireAdmin();



try {

    switch ($method) {



        // ── GET : liste ou détail ────────────────────────────────────────────

        case 'GET':

            $orders = loadData('orders');

            // Filter by warehouse permissions

            $allowedWh = getUserAllowedWarehouses();

            if ($allowedWh !== null) {

                $orders = array_values(array_filter($orders, function($o) use ($allowedWh) {

                    // An order may span several warehouses; check if any line belongs to an allowed warehouse

                    foreach (($o['lines'] ?? []) as $line) {

                        if (in_array((int)($line['warehouseId'] ?? 0), $allowedWh)) return true;

                    }

                    return false;

                }));

            }



            if (isset($_GET['id'])) {

                $id = $_GET['id'];

                $found = null;

                foreach ($orders as $o) { if ((string)($o['id'] ?? '') === (string)$id) { $found = $o; break; } }

                if (!$found) jsonError('Bon de commande introuvable.', 404);

                jsonResponse($found);

            }



            // Optional status filter

            $status = $_GET['status'] ?? '';

            if ($status) $orders = array_values(array_filter($orders, fn($o) => ($o['status'] ?? '') === $status));



            // Sort newest first

            usort($orders, fn($a, $b) => strcmp($b['createdAt'] ?? '', $a['createdAt'] ?? ''));

            jsonResponse($orders);



        // ── POST : créer un bon de commande ──────────────────────────────────

        case 'POST':

            $supplierId = $body['supplierId'] ?? null;

            $lines      = $body['lines']      ?? [];   // [{productId, warehouseId, quantity, unitCost, unit}]

            $note       = trim($body['note']  ?? '');

            $ref        = trim($body['ref']   ?? '');  // Référence interne (libre)



            if (!$supplierId) jsonError('supplierId requis.');

            if (empty($lines)) jsonError('Au moins une ligne requise.');



            // Validate lines

            foreach ($lines as $i => $line) {

                if (empty($line['productId'])) jsonError("Ligne $i : productId manquant.");

                if (empty($line['warehouseId'])) jsonError("Ligne $i : warehouseId manquant.");

                if ((float)($line['quantity'] ?? 0) <= 0) jsonError("Ligne $i : quantité invalide.");

                if (!userCanAccessWarehouse($line['warehouseId'])) jsonError("Accès refusé au magasin de la ligne $i.", 403);

            }



            // Generate order number: BC-YYYYMMDD-XXXX

            $orderNum = 'BC-' . date('Ymd') . '-' . strtoupper(substr(uniqid(), -4));



            $order = addItem('orders', [

                'orderNumber' => $orderNum,

                'ref'         => $ref,

                'supplierId'  => $supplierId,

                'status'      => 'draft',

                'validationStatus' => 'pending', // pending|approved|refused

                'validationNote'   => '',

                'validatedAt'      => null,

                'refusedAt'        => null,

                'storedAt'         => null,

                'events'           => [[

                    't'    => date('c'),

                    'type' => 'created',

                    'by'   => currentUser()['name'] ?? currentUser()['username'] ?? 'Inconnu',

                    'note' => '',

                ]],

                'lines'       => array_map(fn($l) => [

                    'productId'   => (int)($l['productId'] ?? 0),

                    'warehouseId' => (int)($l['warehouseId'] ?? 0),

                    'quantity'    => (float)($l['quantity'] ?? 0),

                    'quantityReceived' => 0,

                    'unitCost'    => (float)($l['unitCost'] ?? 0),

                    'unit'        => $l['unit'] ?? 'piece',

                    'supplierSku' => trim($l['supplierSku'] ?? ''),

                    'note'        => trim($l['note'] ?? ''),

                ], $lines),

                'note'        => $note,

                'userId'      => currentUser()['id'],

                'userName'    => currentUser()['name'] ?? currentUser()['username'] ?? 'Inconnu',

                'sentAt'      => null,

                'receivedAt'  => null,

            ]);



            jsonResponse($order, 201);



        // ── PUT : modifier statut, lignes, ou réceptionner ───────────────────

        case 'PUT':

            $id = $body['id'] ?? null;

            if (!$id) jsonError('id requis.');



            $orders = loadData('orders');

            $idx    = null;

            foreach ($orders as $i => $o) {

                if ((string)($o['id'] ?? '') === (string)$id) { $idx = $i; break; }

            }

            if ($idx === null) jsonError('Bon de commande introuvable.', 404);



            $order  = $orders[$idx];

            $action = $body['action'] ?? 'update';



            // ── Action: change status ──

            if ($action === 'status') {

                $newStatus = $body['status'] ?? '';

                $allowed   = ['draft', 'sent', 'partial', 'received', 'cancelled'];

                if (!in_array($newStatus, $allowed)) jsonError('Statut invalide.');



                // Cannot go back from received/cancelled

                if (in_array($order['status'] ?? '', ['received', 'cancelled'])) {

                    jsonError('Bon de commande clôturé, modification impossible.');

                }



                $orders[$idx]['status'] = $newStatus;

                if ($newStatus === 'sent') {

                    $orders[$idx]['sentAt'] = date('c');

                    if (empty($orders[$idx]['validationStatus'])) $orders[$idx]['validationStatus'] = 'pending';

                    $orders[$idx]['events'] = $orders[$idx]['events'] ?? [];

                    $orders[$idx]['events'][] = ['t'=>date('c'),'type'=>'sent','by'=>currentUser()['name'] ?? currentUser()['username'] ?? 'Inconnu','note'=>''];

                }

                if ($newStatus === 'received') {

                    $orders[$idx]['receivedAt'] = date('c');

                }

                if ($newStatus === 'cancelled') {

                    $orders[$idx]['events'] = $orders[$idx]['events'] ?? [];

                    $orders[$idx]['events'][] = ['t'=>date('c'),'type'=>'cancelled','by'=>currentUser()['name'] ?? currentUser()['username'] ?? 'Inconnu','note'=>trim($body['note'] ?? '')];

                }



                saveData('orders', $orders);

                jsonResponse($orders[$idx]);

            }



            

            // ── Action: approval (admin only) ──

            if ($action === 'approval') {

                requireAdmin();

                $vs = $body['validationStatus'] ?? '';

                if (!in_array($vs, ['pending','approved','refused'])) jsonError('validationStatus invalide.');

                if (($order['status'] ?? '') !== 'sent') jsonError("Validation possible uniquement sur un BC 'Envoyé'.");

                $note = trim($body['note'] ?? '');

                $orders[$idx]['validationStatus'] = $vs;

                $orders[$idx]['validationNote']   = $note;

                if ($vs === 'approved') {

                    $orders[$idx]['validatedAt'] = date('c');

                    $orders[$idx]['refusedAt']   = null;

                } elseif ($vs === 'refused') {

                    $orders[$idx]['refusedAt']   = date('c');

                    $orders[$idx]['validatedAt'] = null;

                }

                $orders[$idx]['events'] = $orders[$idx]['events'] ?? [];

                $orders[$idx]['events'][] = ['t'=>date('c'),'type'=>$vs,'by'=>currentUser()['name'] ?? currentUser()['username'] ?? 'Inconnu','note'=>$note];

                saveData('orders', $orders);

                jsonResponse($orders[$idx]);

            }



            // ── Action: store (after received) ──

            if ($action === 'store') {

                requireAdmin();

                if (($order['status'] ?? '') !== 'received') jsonError('Rangement possible uniquement après réception complète.');

                $orders[$idx]['storedAt'] = date('c');

                $orders[$idx]['events'] = $orders[$idx]['events'] ?? [];

                $orders[$idx]['events'][] = ['t'=>date('c'),'type'=>'stored','by'=>currentUser()['name'] ?? currentUser()['username'] ?? 'Inconnu','note'=>''];

                saveData('orders', $orders);

                jsonResponse($orders[$idx]);

            }



// ── Action: receive lines (partial or full) ──

            if ($action === 'receive') {

                // Require admin validation before any stock entry

                if (($order['validationStatus'] ?? 'pending') !== 'approved') {

                    jsonError("Bon de commande non validé. Un admin doit valider avant réception.", 400);

                }

                if (!in_array(($order['status'] ?? ''), ['sent','partial'])) {

                    jsonError("Réception possible uniquement sur un BC 'Envoyé' ou 'Partiel'.", 400);

                }

                if (in_array($order['status'] ?? '', ['received', 'cancelled'])) {

                    jsonError('Bon de commande clôturé.');

                }



                $receivedLines = $body['lines'] ?? []; // [{lineIndex, quantityReceived, lotNumber, expiryDate, unitCost}]

                if (empty($receivedLines)) jsonError('Lignes de réception requises.');



                $mvToCreate  = [];

                $allReceived = true;



                foreach ($receivedLines as $rl) {

                    $li  = (int)($rl['lineIndex'] ?? -1);

                    $qr  = (float)($rl['quantityReceived'] ?? 0);

                    if ($li < 0 || !isset($order['lines'][$li])) continue;

                    if ($qr <= 0) continue;



                    $line = $order['lines'][$li];

                    $orders[$idx]['lines'][$li]['quantityReceived'] =

                        ($order['lines'][$li]['quantityReceived'] ?? 0) + $qr;



                    // Prepare movement entry

                    $mvToCreate[] = [

                        'productId'   => $line['productId'],

                        'warehouseId' => $line['warehouseId'],

                        'quantity'    => $qr,

                        'unit'        => $line['unit'] ?? 'piece',

                        'unitCost'    => (float)($rl['unitCost'] ?? $line['unitCost'] ?? 0),

                        'lotNumber'   => trim($rl['lotNumber'] ?? ''),

                        'expiryDate'  => trim($rl['expiryDate'] ?? ''),

                        'orderId'     => $id,

                        'orderNumber' => $order['orderNumber'] ?? '',

                    ];

                }



                // Check if fully received

                foreach ($orders[$idx]['lines'] as $line) {

                    if (($line['quantityReceived'] ?? 0) < $line['quantity']) {

                        $allReceived = false;

                        break;

                    }

                }



                $orders[$idx]['status']     = $allReceived ? 'received' : 'partial';

                $orders[$idx]['events'] = $orders[$idx]['events'] ?? [];

                $orders[$idx]['events'][] = ['t'=>date('c'),'type'=>($allReceived?'received':'partial'),'by'=>currentUser()['name'] ?? currentUser()['username'] ?? 'Inconnu','note'=>''];

                if ($allReceived) $orders[$idx]['receivedAt'] = date('c');

                saveData('orders', $orders);



                // Create entry movements for each received line

                $movements = loadData('movements');

                $stock     = loadData('stock');

                $products  = loadData('products');

                $user      = currentUser();

                $now       = date('c');



                foreach ($mvToCreate as $mv) {

                    $pid  = (int)$mv['productId'];

                    $wid  = (int)$mv['warehouseId'];

                    $qty  = (float)$mv['quantity'];

                    $unit = $mv['unit'];

                    $prod = findById($products, $pid);

                    if (!$prod) continue;



                    $baseQty     = toBaseQty($prod, $unit, $qty);

                    $unitCost    = (float)$mv['unitCost'];

                    $baseUnitCost = toBaseUnitCost($prod, $unit, $unitCost);



                    // Update stock

                    $si = findStockIndex($stock, $pid, $wid);

                    if ($si === null) {

                        $lotEntry = !empty($mv['lotNumber'])
                            ? [['lotNumber'=>$mv['lotNumber'],'expiryDate'=>$mv['expiryDate'],'quantity'=>$baseQty,'createdAt'=>$now]]
                            : [];

                        $stock[] = [

                            'id'            => genId(),

                            'productId'     => $pid,

                            'warehouseId'   => $wid,

                            'quantity'      => $baseQty,

                            'maxAuthorized' => $baseQty,

                            'minStock'      => null,

                            'maxStock'      => null,

                            'safetyStock'   => null,

                            'lots'          => $lotEntry,

                            'locations'     => ['default' => ['quantity' => $baseQty, 'lots' => $lotEntry]],

                            'lastMovementAt'=> $now,

                            'createdAt'     => $now,

                        ];

                    } else {

                        $stock[$si]['quantity']      = ($stock[$si]['quantity'] ?? 0) + $baseQty;

                        $stock[$si]['maxAuthorized'] = max($stock[$si]['maxAuthorized'] ?? 0, $stock[$si]['quantity']);

                        $stock[$si]['lastMovementAt'] = $now;

                        // Sync locations.default quantity
                        if (!isset($stock[$si]['locations']) || !is_array($stock[$si]['locations'])) {
                            $stock[$si]['locations'] = ['default' => ['quantity' => 0, 'lots' => []]];
                        }
                        if (!isset($stock[$si]['locations']['default'])) {
                            $stock[$si]['locations']['default'] = ['quantity' => 0, 'lots' => []];
                        }
                        $stock[$si]['locations']['default']['quantity'] = ($stock[$si]['locations']['default']['quantity'] ?? 0) + $baseQty;

                        // Add lot if tracked

                        if (!empty($mv['lotNumber'])) {

                            $lots = $stock[$si]['lots'] ?? [];

                            $li2  = null;

                            foreach ($lots as $lIdx => $l) {

                                if (($l['lotNumber'] ?? '') === $mv['lotNumber']) { $li2 = $lIdx; break; }

                            }

                            if ($li2 !== null) {

                                $lots[$li2]['quantity'] += $baseQty;

                            } else {

                                $lots[] = ['lotNumber'=>$mv['lotNumber'],'expiryDate'=>$mv['expiryDate'],'quantity'=>$baseQty,'createdAt'=>$now];

                            }

                            $stock[$si]['lots'] = $lots;

                            // Also sync locations.default.lots
                            if (!isset($stock[$si]['locations']['default']['lots'])) {
                                $stock[$si]['locations']['default']['lots'] = [];
                            }
                            $defLots =& $stock[$si]['locations']['default']['lots'];
                            $found2 = false;
                            foreach ($defLots as $di => $dl) {
                                if (($dl['lotNumber'] ?? '') === $mv['lotNumber']) {
                                    $defLots[$di]['quantity'] += $baseQty; $found2 = true; break;
                                }
                            }
                            if (!$found2) {
                                $defLots[] = ['lotNumber'=>$mv['lotNumber'],'expiryDate'=>$mv['expiryDate'],'quantity'=>$baseQty,'createdAt'=>$now];
                            }
                            unset($defLots);

                        }

                    }



                    // CUMP update

                    if ($baseUnitCost > 0) {

                        $currentStock = ($si !== null ? ($stock[$si]['quantity'] ?? 0) : $baseQty);

                        $previousQty  = max(0, $currentStock - $baseQty);

                        $previousCump = (float)($prod['cump'] ?? $prod['cost'] ?? 0);

                        $newCump      = $previousQty > 0

                            ? (($previousQty * $previousCump) + ($baseQty * $baseUnitCost)) / ($previousQty + $baseQty)

                            : $baseUnitCost;

                        $products = array_map(function($p) use ($pid, $newCump) {

                            if ((int)($p['id'] ?? 0) === $pid) $p['cump'] = round($newCump, 6);

                            return $p;

                        }, $products);

                    }



                    // Create movement record

                    $movements[] = [

                        'id'           => genId(),

                        'productId'    => $pid,

                        'warehouseId'  => $wid,

                        'type'         => 'entry',

                        'quantity'     => $baseQty,

                        'unit'         => $unit,

                        'baseQuantity' => $baseQty,

                        'unitCost'     => $unitCost,

                        'baseUnitCost' => $baseUnitCost,

                        'lotNumber'    => $mv['lotNumber'],

                        'expiryDate'   => $mv['expiryDate'],

                        'orderId'      => $id,

                        'orderNumber'  => $mv['orderNumber'],

                        'reason'       => 'Réception BC ' . $mv['orderNumber'],

                        'operator'     => $user['name'] ?? $user['username'] ?? 'Inconnu',

                        'userId'       => $user['id'],

                        'user'         => $user['name'] ?? $user['username'],

                        'date'         => $now,

                        'createdAt'    => $now,

                    ];

                }



                saveData('stock', $stock);

                saveData('movements', $movements);

                saveData('products', $products);



                jsonResponse($orders[$idx]);

            }



            // ── Action: update draft (edit lines/note) ──

            if ($action === 'update') {

                if (($order['status'] ?? '') !== 'draft') jsonError('Seuls les brouillons sont modifiables.');



                if (isset($body['note'])) $orders[$idx]['note'] = trim($body['note']);

                if (isset($body['ref']))  $orders[$idx]['ref']  = trim($body['ref']);

                if (isset($body['lines'])) {

                    $orders[$idx]['lines'] = array_map(fn($l) => [

                        'productId'        => (int)($l['productId'] ?? 0),

                        'warehouseId'      => (int)($l['warehouseId'] ?? 0),

                        'quantity'         => (float)($l['quantity'] ?? 0),

                        'quantityReceived' => (float)($l['quantityReceived'] ?? 0),

                        'unitCost'         => (float)($l['unitCost'] ?? 0),

                        'unit'             => $l['unit'] ?? 'piece',

                        'supplierSku'      => trim($l['supplierSku'] ?? ''),

                        'note'             => trim($l['note'] ?? ''),

                    ], $body['lines']);

                }

                saveData('orders', $orders);

                jsonResponse($orders[$idx]);

            }



            jsonError('Action inconnue.');



        // ── DELETE : supprimer (brouillons seulement) ────────────────────────

        case 'DELETE':

            $id = $body['id'] ?? $_GET['id'] ?? null;

            if (!$id) jsonError('id requis.');



            $orders = loadData('orders');

            $found  = null;

            foreach ($orders as $o) { if ((string)($o['id'] ?? '') === (string)$id) { $found = $o; break; } }



            if (!$found) jsonError('Bon de commande introuvable.', 404);

            if (!in_array($found['status'] ?? '', ['draft', 'cancelled'])) {

                jsonError('Seuls les brouillons ou annulés peuvent être supprimés.');

            }



            deleteItem('orders', $id);

            jsonResponse(['ok' => true]);



        default:

            jsonError('Méthode non supportée.', 405);

    }



} catch (Throwable $e) {

    http_response_code(500);

    header('Content-Type: application/json; charset=utf-8');

    echo json_encode([

        'error' => $e->getMessage(),

        'type'  => get_class($e),

    ], JSON_UNESCAPED_UNICODE);

    exit;

}

