<?php
namespace Modules\Wms\Controllers;

use Gestoreasy\Core\Http\Request;
use Gestoreasy\Core\Http\Response;
use Gestoreasy\Core\Tenant\ConnectionManager;

class MobileApiController
{
    private static function pdo(?Request $req = null): \PDO {
        try {
            $tenant = $req ? ($req->header('x-tenant-id') ?? null) : null;
            return ConnectionManager::forTenant($tenant);
        } catch (\Throwable $e) {
            return ConnectionManager::forTenant('default');
        }
    }

    public static function listReceiving(Request $req): Response
    {
        self::ensureOpsTables($req);
        $pdo = self::pdo($req);
        $q = trim((string)($req->query['q'] ?? ''));
        $status = trim((string)($req->query['status'] ?? ''));
        $start = trim((string)($req->query['start'] ?? ''));
        $end = trim((string)($req->query['end'] ?? ''));
        $page = max(1, (int)($req->query['page'] ?? 1));
        $perPage = max(1, min(100, (int)($req->query['per_page'] ?? 50)));
        $offset = ($page - 1) * $perPage;

        $where = ['1=1'];
        $params = [];
        if ($q !== '') { $where[] = '(product_code LIKE :q)'; $params[':q'] = "%$q%"; }
        if ($status !== '') { $where[] = 'status = :status'; $params[':status'] = $status; }
        if ($start !== '') { $where[] = 'created_at >= :start'; $params[':start'] = $start; }
        if ($end !== '') { $where[] = 'created_at <= :end'; $params[':end'] = $end; }
        $whereSql = implode(' AND ', $where);

        $sql = "SELECT id, product_id, product_code, quantity, origin, status, created_at FROM wms_receiving_raw WHERE $whereSql ORDER BY created_at DESC LIMIT :limit OFFSET :offset";
        $stmt = $pdo->prepare($sql);
        foreach ($params as $k => $v) { $stmt->bindValue($k, $v); }
        $stmt->bindValue(':limit', $perPage, \PDO::PARAM_INT);
        $stmt->bindValue(':offset', $offset, \PDO::PARAM_INT);
        $stmt->execute();
        $rows = $stmt->fetchAll();
        return Response::json(['rows' => $rows, 'page' => $page, 'per_page' => $perPage]);
    }

    public static function listShipping(Request $req): Response
    {
        self::ensureOpsTables($req);
        $pdo = self::pdo($req);
        $q = trim((string)($req->query['q'] ?? ''));
        $status = trim((string)($req->query['status'] ?? ''));
        $start = trim((string)($req->query['start'] ?? ''));
        $end = trim((string)($req->query['end'] ?? ''));
        $page = max(1, (int)($req->query['page'] ?? 1));
        $perPage = max(1, min(100, (int)($req->query['per_page'] ?? 50)));
        $offset = ($page - 1) * $perPage;

        $where = ['1=1'];
        $params = [];
        if ($q !== '') { $where[] = '(product_code LIKE :q)'; $params[':q'] = "%$q%"; }
        if ($status !== '') { $where[] = 'status = :status'; $params[':status'] = $status; }
        if ($start !== '') { $where[] = 'created_at >= :start'; $params[':start'] = $start; }
        if ($end !== '') { $where[] = 'created_at <= :end'; $params[':end'] = $end; }
        $whereSql = implode(' AND ', $where);

        $sql = "SELECT id, product_id, product_code, quantity, dest, status, created_at FROM wms_shipping_raw WHERE $whereSql ORDER BY created_at DESC LIMIT :limit OFFSET :offset";
        $stmt = $pdo->prepare($sql);
        foreach ($params as $k => $v) { $stmt->bindValue($k, $v); }
        $stmt->bindValue(':limit', $perPage, \PDO::PARAM_INT);
        $stmt->bindValue(':offset', $offset, \PDO::PARAM_INT);
        $stmt->execute();
        $rows = $stmt->fetchAll();
        return Response::json(['rows' => $rows, 'page' => $page, 'per_page' => $perPage]);
    }

    private static function ensureOpsTables(Request $req): void
    {
        $pdo = self::pdo($req);
        // Tabela de recebimento bruto (raw)
        $pdo->exec("CREATE TABLE IF NOT EXISTS wms_receiving_raw (
            id INT AUTO_INCREMENT PRIMARY KEY,
            tenant_id INT NULL,
            device_id INT NULL,
            product_id INT NULL,
            product_code VARCHAR(64) NULL,
            quantity DECIMAL(12,3) NOT NULL,
            origin VARCHAR(128) NULL,
            status ENUM('received','validated','invalid') NOT NULL DEFAULT 'received',
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            INDEX idx_rec_status_created (status, created_at)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");

        // Tabela de expedição bruto (raw)
        $pdo->exec("CREATE TABLE IF NOT EXISTS wms_shipping_raw (
            id INT AUTO_INCREMENT PRIMARY KEY,
            tenant_id INT NULL,
            device_id INT NULL,
            product_id INT NULL,
            product_code VARCHAR(64) NULL,
            quantity DECIMAL(12,3) NOT NULL,
            dest VARCHAR(128) NULL,
            status ENUM('received','validated','invalid') NOT NULL DEFAULT 'received',
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            INDEX idx_ship_status_created (status, created_at)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
    }

    private static function resolveProductId(\PDO $pdo, ?string $product): ?int
    {
        $code = trim((string)($product ?? ''));
        if ($code === '') return null;
        // Tentar por ID direto
        if (ctype_digit($code)) {
            $id = (int)$code;
            $st = $pdo->prepare('SELECT id FROM products WHERE id = ?');
            $st->execute([$id]);
            $row = $st->fetch();
            if ($row && (int)$row['id'] > 0) return (int)$row['id'];
        }
        // Tentar por código do produto
        $st = $pdo->prepare('SELECT id FROM products WHERE codigo = ? LIMIT 1');
        $st->execute([$code]);
        $row = $st->fetch();
        return $row ? (int)$row['id'] : null;
    }

    private static function ensureDefaultWarehouseAndBins(\PDO $pdo): array
    {
        // Garantir um armazém padrão
        $wid = null;
        try {
            $row = $pdo->query('SELECT id FROM warehouses ORDER BY id ASC LIMIT 1')->fetch();
            $wid = (int)($row['id'] ?? 0);
        } catch (\Throwable $e) { $wid = 0; }
        if ($wid <= 0) {
            try {
                $pdo->exec("CREATE TABLE IF NOT EXISTS warehouses (
                    id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
                    name VARCHAR(128) NOT NULL,
                    region VARCHAR(64) NULL,
                    status ENUM('active','inactive') NOT NULL DEFAULT 'active',
                    location VARCHAR(128) NULL,
                    zones JSON NULL,
                    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
                    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
            } catch (\Throwable $e) {}
            $ins = $pdo->prepare('INSERT INTO warehouses (name, status, created_at) VALUES (?,?,NOW())');
            $ins->execute(['Default', 'active']);
            $wid = (int)$pdo->lastInsertId();
        }

        // Garantir bins de doca e externo
        $codes = ['RECV_DOCK' => 'Doca de recebimento', 'SHIP_DOCK' => 'Doca de expedição', 'EXTERNAL' => 'Externo'];
        $ids = [];
        foreach ($codes as $code => $desc) {
            $st = $pdo->prepare('SELECT id FROM bins WHERE code = ? LIMIT 1');
            $st->execute([$code]);
            $row = $st->fetch();
            $id = (int)($row['id'] ?? 0);
            if ($id <= 0) {
                try {
                    $pdo->exec("CREATE TABLE IF NOT EXISTS bins (
                        id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
                        warehouse_id INT UNSIGNED NOT NULL,
                        code VARCHAR(64) NOT NULL,
                        description VARCHAR(255) NULL,
                        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
                        INDEX idx_bins_code (code),
                        INDEX idx_bins_wh (warehouse_id)
                    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
                } catch (\Throwable $e) {}
                $ins = $pdo->prepare('INSERT INTO bins (warehouse_id, code, description, created_at) VALUES (?,?,?,NOW())');
                $ins->execute([$wid, $code, $desc]);
                $id = (int)$pdo->lastInsertId();
            }
            $ids[$code] = $id;
        }
        return ['warehouse_id' => $wid, 'bins' => $ids];
    }

    private static function ensureTables(Request $req): void
    {
        $pdo = self::pdo($req);
        // Tabela de dispositivos móveis (compartilhada com MobileController)
        $pdo->exec("CREATE TABLE IF NOT EXISTS wms_mobile_devices (
            id INT AUTO_INCREMENT PRIMARY KEY,
            tenant_id INT NULL,
            name VARCHAR(128) NOT NULL,
            api_key VARCHAR(128) NULL,
            last_seen DATETIME NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            UNIQUE KEY uniq_api_key (api_key)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");

        // Seed: criar um dispositivo padrão para testes locais, caso a tabela esteja vazia
        try {
            $hasDevices = (int)$pdo->query("SELECT COUNT(*) FROM wms_mobile_devices")->fetchColumn();
            if ($hasDevices === 0) {
                $ins = $pdo->prepare('INSERT INTO wms_mobile_devices (tenant_id, name, api_key) VALUES (NULL, ?, ?)');
                $ins->execute(['Device DEV-ABC-123', 'DEV-ABC-123']);
            }
        } catch (\Throwable $_) { /* ignore seed errors */ }

        // Eventos genéricos recebidos do coletor PWA
        $pdo->exec("CREATE TABLE IF NOT EXISTS wms_mobile_events (
            id INT AUTO_INCREMENT PRIMARY KEY,
            tenant_id INT NULL,
            device_id INT NULL,
            type VARCHAR(32) NOT NULL,
            payload_json JSON NOT NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");

        // Tabelas usadas para inventário (alinhadas ao MobileController)
        $pdo->exec("CREATE TABLE IF NOT EXISTS wms_inventory_queue (
            id INT AUTO_INCREMENT PRIMARY KEY,
            tenant_id INT NULL,
            device_id INT NULL,
            batch_id VARCHAR(64) NULL,
            payload_json JSON NOT NULL,
            status ENUM('received','validated','confirmed','processing','done','error') NOT NULL DEFAULT 'received',
            attempt_count INT NOT NULL DEFAULT 0,
            locked_at DATETIME NULL,
            result_json JSON NULL,
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            INDEX idx_status_locked (status, locked_at),
            INDEX idx_batch (batch_id)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");

        $pdo->exec("CREATE TABLE IF NOT EXISTS wms_inventory_raw (
            id INT AUTO_INCREMENT PRIMARY KEY,
            tenant_id INT NULL,
            session_id INT NULL,
            device_id INT NULL,
            product_id INT NULL,
            product_code VARCHAR(64) NULL,
            bin_id INT NULL,
            bin_code VARCHAR(64) NULL,
            quantity DECIMAL(12,3) NOT NULL,
            batch_id VARCHAR(64) NULL,
            status ENUM('received','validated','invalid') NOT NULL DEFAULT 'received',
            created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            INDEX idx_batch_status (batch_id, status)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;");
    }

    private static function resolveDevice(Request $req): ?array
    {
        $pdo = self::pdo($req);
        $headerKey = $req->header('x-device-key') ?: $req->header('X-Device-Key');
        $queryKey = $req->query['device_key'] ?? null;
        $apiKey = $headerKey ?: $queryKey;
        if ($apiKey) {
            $stmt = $pdo->prepare('SELECT * FROM wms_mobile_devices WHERE api_key = ?');
            $stmt->execute([$apiKey]);
            $device = $stmt->fetch();
            if ($device) {
                $upd = $pdo->prepare('UPDATE wms_mobile_devices SET last_seen = NOW() WHERE id = ?');
                $upd->execute([$device['id']]);
                return $device;
            }
        }
        return null;
    }

    public static function ping(Request $req): Response
    {
        self::ensureTables($req);
        $device = self::resolveDevice($req);
        return Response::json([
            'status' => 'ok',
            'device_valid' => $device !== null,
            'time' => date('c'),
        ]);
    }

    public static function events(Request $req): Response
    {
        self::ensureTables($req);
        self::ensureOpsTables($req);
        $pdo = self::pdo($req);
        $device = self::resolveDevice($req);

        $type = (string)($req->body['type'] ?? '');
        $payload = $req->body['payload'] ?? null;
        if ($type === '' || $payload === null) {
            return Response::json(['error' => 'Parâmetros obrigatórios: type, payload'], 400);
        }

        // Persistir evento genérico
        $insEvt = $pdo->prepare('INSERT INTO wms_mobile_events (tenant_id, device_id, type, payload_json) VALUES (NULL, ?, ?, ?)');
        $insEvt->execute([
            $device['id'] ?? null,
            $type,
            json_encode($payload),
        ]);

        // Tratamento básico para inventário: enfileia item único
        if ($type === 'inventory') {
            $productCode = (string)($payload['product'] ?? '');
            $binCode = (string)($payload['bin'] ?? '');
            $qty = (float)($payload['qty'] ?? 0);
            if ($productCode === '' || $qty <= 0) {
                return Response::json(['error' => 'Inventário requer product e qty válidos'], 422);
            }

            $batchId = 'pwa_' . substr(bin2hex(random_bytes(8)), 0, 16);

            // Salvar raw
            $insRaw = $pdo->prepare('INSERT INTO wms_inventory_raw (tenant_id, device_id, product_id, product_code, bin_id, bin_code, quantity, batch_id, status) VALUES (NULL, ?, NULL, ?, NULL, ?, ?, ?, ?)');
            $insRaw->execute([
                $device['id'] ?? null,
                $productCode,
                $binCode ?: null,
                $qty,
                $batchId,
                'received',
            ]);

            // Enfileiar para processamento posterior
            $payloadQueue = ['items' => [[
                'product_id' => null,
                'product_code' => $productCode,
                'bin_id' => null,
                'bin_code' => $binCode ?: null,
                'quantity' => $qty,
            ]]];
            $q = $pdo->prepare('INSERT INTO wms_inventory_queue (tenant_id, device_id, batch_id, payload_json, status) VALUES (NULL, ?, ?, ?, ?)');
            $q->execute([
                $device['id'] ?? null,
                $batchId,
                json_encode($payloadQueue),
                'received',
            ]);

            return Response::json(['accepted' => 1, 'type' => $type, 'batch_id' => $batchId], 202);
        }

        // Recebimento: registrar raw e, se produto resolvido, lançar movimento para doca
        if ($type === 'receiving') {
            $productCode = (string)($payload['product'] ?? '');
            $qty = (float)($payload['qty'] ?? 0);
            $origin = trim((string)($payload['origin'] ?? ''));
            if ($productCode === '' || $qty <= 0) {
                return Response::json(['error' => 'Recebimento requer product e qty válidos'], 422);
            }
            $pid = self::resolveProductId($pdo, $productCode);
            $ins = $pdo->prepare('INSERT INTO wms_receiving_raw (tenant_id, device_id, product_id, product_code, quantity, origin, status) VALUES (NULL, ?, ?, ?, ?, ?, ?)');
            $ins->execute([$device['id'] ?? null, $pid, $productCode, $qty, $origin ?: null, 'received']);

            // Se produto resolvido, movimentar para doca de recebimento
            if ($pid && $qty > 0) {
                $ctx = self::ensureDefaultWarehouseAndBins($pdo);
                $fromId = $ctx['bins']['EXTERNAL'] ?? null;
                $toId = $ctx['bins']['RECV_DOCK'] ?? null;
                if ($fromId && $toId) {
                    $mv = $pdo->prepare('INSERT INTO bin_movements (product_id, from_bin_id, to_bin_id, quantity) VALUES (?,?,?,?)');
                    $mv->execute([$pid, $fromId, $toId, $qty]);
                }
            }
            return Response::json(['accepted' => 1, 'type' => $type], 202);
        }

        // Expedição: registrar raw e, se produto resolvido, movimentar da doca de expedição para externo
        if ($type === 'shipping') {
            $productCode = (string)($payload['product'] ?? '');
            $qty = (float)($payload['qty'] ?? 0);
            $dest = trim((string)($payload['dest'] ?? ''));
            if ($productCode === '' || $qty <= 0) {
                return Response::json(['error' => 'Expedição requer product e qty válidos'], 422);
            }
            $pid = self::resolveProductId($pdo, $productCode);
            $ins = $pdo->prepare('INSERT INTO wms_shipping_raw (tenant_id, device_id, product_id, product_code, quantity, dest, status) VALUES (NULL, ?, ?, ?, ?, ?, ?)');
            $ins->execute([$device['id'] ?? null, $pid, $productCode, $qty, $dest ?: null, 'received']);

            if ($pid && $qty > 0) {
                $ctx = self::ensureDefaultWarehouseAndBins($pdo);
                $fromId = $ctx['bins']['SHIP_DOCK'] ?? null;
                $toId = $ctx['bins']['EXTERNAL'] ?? null;
                if ($fromId && $toId) {
                    $mv = $pdo->prepare('INSERT INTO bin_movements (product_id, from_bin_id, to_bin_id, quantity) VALUES (?,?,?,?)');
                    $mv->execute([$pid, $fromId, $toId, $qty]);
                }
            }
            return Response::json(['accepted' => 1, 'type' => $type], 202);
        }

        // Tipos não tratados ainda: apenas registrar
        return Response::json(['accepted' => 1, 'type' => $type], 202);
    }
}