<?php

namespace App\Services;

use App\Models\InventoryMovement;
use App\Models\InventoryProduct;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;

/**
 * Aplica varias líneas de ajuste (CSV) en una sola transacción.
 * Columnas: sku, quantity (delta firmado; mismo criterio que movimiento tipo adjustment).
 */
class InventoryBulkAdjustCsvService
{
    public function __construct(
        private InventoryMovementService $movementService
    ) {}

    /**
     * @return array{processed: int, reference: string}
     *
     * @throws InvalidArgumentException
     */
    public function apply(UploadedFile $file, int $userId): array
    {
        $rows = $this->parseAndValidate($file);
        $reference = 'bulk_csv:'.now()->format('Y-m-d H:i:s');

        return DB::transaction(function () use ($rows, $userId, $reference): array {
            $n = 0;
            foreach ($rows as $row) {
                $this->movementService->record(
                    $row['product'],
                    InventoryMovement::TYPE_ADJUSTMENT,
                    $row['quantity'],
                    $reference,
                    $userId
                );
                $n++;
            }

            return ['processed' => $n, 'reference' => $reference];
        });
    }

    /**
     * @return list<array{product: InventoryProduct, quantity: string, line: int}>
     */
    private function parseAndValidate(UploadedFile $file): array
    {
        $path = $file->getRealPath();
        if ($path === false || ! is_readable($path)) {
            throw new InvalidArgumentException('No se pudo leer el archivo CSV.');
        }

        $handle = fopen($path, 'rb');
        if ($handle === false) {
            throw new InvalidArgumentException('No se pudo abrir el archivo CSV.');
        }

        $first = fgets($handle);
        if ($first === false) {
            fclose($handle);
            throw new InvalidArgumentException('El CSV está vacío.');
        }

        $first = preg_replace('/^\xEF\xBB\xBF/', '', $first) ?? $first;
        $header = str_getcsv($first);
        $map = $this->headerMap($header);

        if ($map['sku'] === null || $map['quantity'] === null) {
            fclose($handle);
            throw new InvalidArgumentException('El CSV debe incluir columnas sku y quantity.');
        }

        $parsed = [];
        $lineNo = 1;
        $seenSkus = [];

        while (($line = fgets($handle)) !== false) {
            $lineNo++;
            $line = trim($line);
            if ($line === '') {
                continue;
            }
            $cells = str_getcsv($line);
            $sku = isset($cells[$map['sku']]) ? trim((string) $cells[$map['sku']]) : '';
            $qtyRaw = isset($cells[$map['quantity']]) ? trim((string) $cells[$map['quantity']]) : '';

            if ($sku === '' && $qtyRaw === '') {
                continue;
            }
            if ($sku === '') {
                fclose($handle);
                throw new InvalidArgumentException("Línea {$lineNo}: falta SKU.");
            }
            if ($qtyRaw === '') {
                fclose($handle);
                throw new InvalidArgumentException("Línea {$lineNo}: falta cantidad.");
            }
            if (! preg_match('/^-?\d+(\.\d+)?$/', $qtyRaw)) {
                fclose($handle);
                throw new InvalidArgumentException("Línea {$lineNo}: cantidad no numérica.");
            }
            if (isset($seenSkus[$sku])) {
                fclose($handle);
                throw new InvalidArgumentException("SKU duplicado en el archivo: {$sku}.");
            }
            $seenSkus[$sku] = true;

            $parsed[] = ['sku' => $sku, 'quantity' => $qtyRaw, 'line' => $lineNo];
        }

        fclose($handle);

        if ($parsed === []) {
            throw new InvalidArgumentException('No hay filas de datos en el CSV.');
        }

        $skus = array_column($parsed, 'sku');
        $products = InventoryProduct::query()
            ->whereIn('sku', $skus)
            ->get()
            ->keyBy('sku');

        $out = [];
        foreach ($parsed as $row) {
            $product = $products->get($row['sku']);
            if ($product === null) {
                throw new InvalidArgumentException("Línea {$row['line']}: SKU no encontrado ({$row['sku']}).");
            }
            $out[] = [
                'product' => $product,
                'quantity' => $row['quantity'],
                'line' => $row['line'],
            ];
        }

        return $out;
    }

    /**
     * @param  list<string>  $header
     * @return array{sku: ?int, quantity: ?int}
     */
    private function headerMap(array $header): array
    {
        $skuIdx = null;
        $qtyIdx = null;
        foreach ($header as $i => $cell) {
            $k = strtolower(trim((string) $cell));
            if (in_array($k, ['sku', 'codigo', 'código'], true)) {
                $skuIdx = (int) $i;
            }
            if (in_array($k, ['quantity', 'cantidad', 'qty', 'ajuste'], true)) {
                $qtyIdx = (int) $i;
            }
        }

        return ['sku' => $skuIdx, 'quantity' => $qtyIdx];
    }
}
