<?php

namespace WMS\Services;

use WMS\Core\Database;
use WMS\Repositories\PackingRepository;
use WMS\Repositories\PickListRepository;
use WMS\Repositories\ItemRepository;
use WMS\Repositories\StockMovementRepository;
use WMS\Integrations\Sap\SapServiceLayerClient;

/**
 * Servicio de Packing (Empaque).
 *
 * Flujo:
 * 1. Se crea orden de empaque desde pick list completada
 * 2. El operador escanea/verifica cada item contra la pick list
 * 3. Se empaca en contenedores (cajas, pallets)
 * 4. Al completar se crea DeliveryNote en SAP (BaseType=17, SalesOrder)
 */
class PackingService
{
    private PackingRepository $packingRepo;
    private PickListRepository $pickListRepo;
    private ItemRepository $itemRepo;
    private StockMovementRepository $movementRepo;

    public function __construct()
    {
        $this->packingRepo  = new PackingRepository();
        $this->pickListRepo = new PickListRepository();
        $this->itemRepo     = new ItemRepository();
        $this->movementRepo = new StockMovementRepository();
    }

    /**
     * Crea una orden de empaque desde una pick list completada.
     */
    public function createPackingOrder(array $data, int $userId): array
    {
        $pickListId = (int) $data['pick_list_id'];
        $pickList = $this->pickListRepo->getWithLines($pickListId);

        if (!$pickList) {
            throw new \RuntimeException('Pick list no encontrada');
        }
        if (!in_array($pickList['status'], ['COMPLETED', 'IN_PROGRESS'])) {
            throw new \RuntimeException('La pick list debe estar completada o en progreso para iniciar empaque');
        }

        // Verificar que no exista ya una orden de packing para esta pick list
        $db = Database::getConnection();
        $stmt = $db->prepare(
            "SELECT id, pack_number FROM packing_orders WHERE pick_list_id = ? AND status != 'CANCELLED'"
        );
        $stmt->execute([$pickListId]);
        $existing = $stmt->fetch();
        if ($existing) {
            throw new \RuntimeException(
                "Ya existe la orden de empaque {$existing['pack_number']} para esta pick list"
            );
        }

        $packNumber = Database::nextDocNumber('PACK');

        $db->beginTransaction();

        try {
            $packingId = $this->packingRepo->insert([
                'pack_number'    => $packNumber,
                'pick_list_id'   => $pickListId,
                'warehouse_id'   => $pickList['warehouse_id'],
                'status'         => 'OPEN',
                'customer_code'  => $pickList['customer_code'] ?? null,
                'customer_name'  => $pickList['customer_name'] ?? null,
                'sap_doc_entry'  => $pickList['sap_doc_entry'] ?? null,
                'sap_doc_num'    => $pickList['sap_doc_num'] ?? null,
                'notes'          => $data['notes'] ?? null,
                'created_by'     => $userId,
            ]);

            // Crear líneas de empaque desde las líneas pickeadas
            $lineNum = 1;
            foreach ($pickList['lines'] as $pickLine) {
                if (($pickLine['status'] ?? '') !== 'PICKED' || (float) $pickLine['picked_qty'] <= 0) {
                    continue;
                }

                $stmt = $db->prepare(
                    "INSERT INTO packing_lines
                     (packing_order_id, line_num, item_id, batch_id, quantity, uom, status)
                     VALUES (?, ?, ?, ?, ?, ?, 'PENDING')"
                );
                $stmt->execute([
                    $packingId,
                    $lineNum,
                    $pickLine['item_id'],
                    $pickLine['batch_id'] ?? null,
                    (float) $pickLine['picked_qty'],
                    $pickLine['uom'] ?? 'UN',
                ]);
                $lineNum++;
            }

            $db->commit();

            return $this->packingRepo->getWithLines($packingId);
        } catch (\Throwable $e) {
            $db->rollBack();
            throw $e;
        }
    }

    /**
     * Escanea y verifica un item durante el empaque.
     */
    public function scanItem(int $packingId, array $data): array
    {
        $packing = $this->packingRepo->findById($packingId);
        if (!$packing || $packing['status'] === 'COMPLETED' || $packing['status'] === 'CANCELLED') {
            throw new \RuntimeException('Orden de empaque no válida');
        }

        $db = Database::getConnection();

        // Buscar item por código o barcode
        $item = null;
        if (!empty($data['item_code'])) {
            $item = $this->itemRepo->findByCode($data['item_code']);
        } elseif (!empty($data['barcode'])) {
            $stmt = $db->prepare("SELECT * FROM items WHERE barcode = ?");
            $stmt->execute([$data['barcode']]);
            $item = $stmt->fetch() ?: null;
        }

        if (!$item) {
            throw new \RuntimeException('Artículo no encontrado');
        }

        // Verificar que el item esté en la orden de empaque
        $stmt = $db->prepare(
            "SELECT * FROM packing_lines
             WHERE packing_order_id = ? AND item_id = ? AND status != 'PACKED'
             ORDER BY line_num LIMIT 1"
        );
        $stmt->execute([$packingId, $item['id']]);
        $line = $stmt->fetch();

        if (!$line) {
            throw new \RuntimeException("Artículo {$item['item_code']} no esperado en esta orden o ya empacado");
        }

        return [
            'line'     => $line,
            'item'     => $item,
            'expected' => (float) $line['quantity'],
            'packed'   => (float) ($line['packed_qty'] ?? 0),
            'remaining'=> (float) $line['quantity'] - (float) ($line['packed_qty'] ?? 0),
        ];
    }

    /**
     * Confirma el empaque de una línea (o cantidad parcial).
     */
    public function packLine(int $packingId, int $lineNum, array $data, int $userId): array
    {
        $packing = $this->packingRepo->findById($packingId);
        if (!$packing || $packing['status'] === 'COMPLETED' || $packing['status'] === 'CANCELLED') {
            throw new \RuntimeException('Orden de empaque no válida');
        }

        $db = Database::getConnection();
        $db->beginTransaction();

        try {
            $stmt = $db->prepare(
                "SELECT * FROM packing_lines WHERE packing_order_id = ? AND line_num = ?"
            );
            $stmt->execute([$packingId, $lineNum]);
            $line = $stmt->fetch();

            if (!$line) {
                throw new \RuntimeException("Línea {$lineNum} no encontrada");
            }

            $packedQty = (float) ($data['quantity'] ?? $line['quantity']);
            $containerId = $data['container_id'] ?? null;

            $newPackedQty = (float) ($line['packed_qty'] ?? 0) + $packedQty;
            $lineStatus = $newPackedQty >= (float) $line['quantity'] ? 'PACKED' : 'PENDING';

            $db->prepare(
                "UPDATE packing_lines
                 SET packed_qty = ?, container_id = COALESCE(?, container_id), status = ?
                 WHERE id = ?"
            )->execute([$newPackedQty, $containerId, $lineStatus, $line['id']]);

            // Actualizar estado de la orden
            $db->prepare(
                "UPDATE packing_orders SET status = 'IN_PROGRESS' WHERE id = ? AND status = 'OPEN'"
            )->execute([$packingId]);

            $db->commit();

            return $this->packingRepo->getWithLines($packingId);
        } catch (\Throwable $e) {
            $db->rollBack();
            throw $e;
        }
    }

    /**
     * Busca otros contenedores (packing orders) del mismo cliente.
     * Util para agrupar o reasignar items durante el empaque.
     */
    public function getCustomerContainers(int $packingId): array
    {
        $packing = $this->packingRepo->findById($packingId);
        if (!$packing) {
            throw new \RuntimeException('Orden de empaque no encontrada');
        }

        $customerCode = $packing['customer_code'] ?? null;
        $customerName = $packing['customer_name'] ?? null;

        if (!$customerCode && !$customerName) {
            return ['containers' => [], 'message' => 'No hay cliente asociado a esta orden'];
        }

        $db = Database::getConnection();

        // Buscar otras packing orders del mismo cliente (excluyendo la actual)
        $conditions = ['po.id != ?'];
        $params = [$packingId];

        if ($customerCode) {
            $conditions[] = 'po.customer_code = ?';
            $params[] = $customerCode;
        } else {
            $conditions[] = 'po.customer_name = ?';
            $params[] = $customerName;
        }

        $where = implode(' AND ', $conditions);

        $stmt = $db->prepare(
            "SELECT po.id, po.pack_number, po.status, po.customer_name, po.customer_code,
                    po.created_at, COUNT(pol.id) AS line_count,
                    GROUP_CONCAT(DISTINCT pol.container_id) AS container_ids
             FROM packing_orders po
             LEFT JOIN packing_lines pol ON pol.packing_order_id = po.id
             WHERE {$where}
             GROUP BY po.id
             ORDER BY po.created_at DESC
             LIMIT 20"
        );
        $stmt->execute($params);
        $results = $stmt->fetchAll(\PDO::FETCH_ASSOC);

        return [
            'current_packing'  => $packing['pack_number'] ?? $packingId,
            'customer_code'    => $customerCode,
            'customer_name'    => $customerName,
            'other_containers' => $results,
            'count'            => count($results),
        ];
    }

    /**
     * Completa la orden de empaque.
     * Crea DeliveryNote en SAP vinculado al SalesOrder original (BaseType=17).
     */
    public function completePacking(int $packingId, int $userId): array
    {
        $db = Database::getConnection();

        // Verificar que todas las líneas estén empacadas
        $stmt = $db->prepare(
            "SELECT COUNT(*) FROM packing_lines
             WHERE packing_order_id = ? AND status NOT IN ('PACKED')"
        );
        $stmt->execute([$packingId]);
        $pending = (int) $stmt->fetchColumn();

        if ($pending > 0) {
            throw new \RuntimeException("Hay {$pending} líneas pendientes de empacar");
        }

        $this->packingRepo->update($packingId, ['status' => 'COMPLETED']);

        // Crear DeliveryNote en SAP
        try {
            $packing = $this->packingRepo->getWithLines($packingId);
            if (!empty($packing['sap_doc_entry'])) {
                $sapData = [
                    'CardCode'      => $packing['customer_code'],
                    'DocDate'       => date('Y-m-d'),
                    'DocumentLines' => [],
                ];

                foreach ($packing['lines'] as $line) {
                    $item = $this->itemRepo->findById($line['item_id']);
                    $sapLine = [
                        'ItemCode'  => $item['sap_item_code'],
                        'Quantity'  => (float) $line['packed_qty'],
                        'BaseType'  => 17, // SalesOrder
                        'BaseEntry' => (int) $packing['sap_doc_entry'],
                        'BaseLine'  => (int) ($line['sap_line_num'] ?? 0),
                    ];

                    if (!empty($line['batch_number'])) {
                        $sapLine['BatchNumbers'] = [[
                            'BatchNumber' => $line['batch_number'],
                            'Quantity'    => (float) $line['packed_qty'],
                        ]];
                    }

                    $sapData['DocumentLines'][] = $sapLine;
                }

                $sap = new SapServiceLayerClient();
                $result = $sap->post('/DeliveryNotes', $sapData);

                // Log sync success
                if (isset($result['DocEntry'])) {
                    $db->prepare(
                        "UPDATE packing_orders SET sap_delivery_entry = ?, sap_delivery_num = ? WHERE id = ?"
                    )->execute([$result['DocEntry'], $result['DocNum'] ?? null, $packingId]);
                }

                $db->prepare(
                    "INSERT INTO sap_sync_log
                     (sync_type, direction, sap_doc_entry, sap_doc_type, wms_reference_id, status)
                     VALUES (?, ?, ?, ?, ?, ?)"
                )->execute(['DOC_PACKING', 'WMS_TO_SAP', $result['DocEntry'] ?? null, 'DeliveryNote', $packingId, 'SUCCESS']);
            }
        } catch (\Throwable $e) {
            // Log sync error but don't fail the packing completion
            $db->prepare(
                "INSERT INTO sap_sync_log
                 (sync_type, direction, sap_doc_type, wms_reference_id, status, error_message)
                 VALUES (?, ?, ?, ?, ?, ?)"
            )->execute(['DOC_PACKING', 'WMS_TO_SAP', 'DeliveryNote', $packingId, 'ERROR', $e->getMessage()]);
        }

        return $this->packingRepo->getWithLines($packingId);
    }
}
