<?php
namespace Modules\Estoque\Controllers;

use Gestoreasy\Core\Http\Response;
use Gestoreasy\Core\Tenant\ConnectionManager;
use Gestoreasy\Core\Audit\AuditLogger;

class MovementsController
{
    public function list($request)
    {
        $pdo = ConnectionManager::forTenant();
        $page = max(1, (int)($request->query['page'] ?? 1));
        $per = min(100, max(1, (int)($request->query['per_page'] ?? 20)));
        $offset = ($page - 1) * $per;

        // Filtros: busca por produto/código, tipo (in/out), período (created_at)
        $q = trim((string)($request->query['q'] ?? ''));
        $type = (string)($request->query['type'] ?? '');
        $start = trim((string)($request->query['start'] ?? ''));
        $end = trim((string)($request->query['end'] ?? ''));

        $where = ['1=1'];
        $params = [];
        if ($q !== '') {
            // Buscar por product_id, código ou descrição do produto
            $where[] = '(m.product_id LIKE :q OR p.codigo LIKE :q OR p.descricao LIKE :q)';
            $params[':q'] = '%' . $q . '%';
        }
        if ($type !== '' && in_array($type, ['in', 'out'], true)) {
            // Compatível com registros antigos (entrada_*/saida_*) e novos ('in'/'out')
            $where[] = '(m.tipo = :type OR m.tipo LIKE :typeLike)';
            $params[':type'] = $type;
            $params[':typeLike'] = $type === 'in' ? 'entrada%' : 'saida%';
        }
        if ($start !== '') { $where[] = 'm.created_at >= :start'; $params[':start'] = $start; }
        if ($end !== '') { $where[] = 'm.created_at <= :end'; $params[':end'] = $end; }
        $whereSql = implode(' AND ', $where);

        $sql = "SELECT m.id, m.product_id, p.codigo AS product_code, p.descricao AS product_name, m.tipo, m.quantidade, m.motivo, m.stock_before, m.stock_after, m.origin_type, m.origin_number, m.created_at
                FROM inventory_movements m
                LEFT JOIN products p ON p.id = m.product_id
                WHERE $whereSql
                ORDER BY m.created_at DESC, m.id DESC
                LIMIT :limit OFFSET :offset";
        $stmt = $pdo->prepare($sql);
        foreach ($params as $k => $v) { $stmt->bindValue($k, $v); }
        $stmt->bindValue(':limit', $per, \PDO::PARAM_INT);
        $stmt->bindValue(':offset', $offset, \PDO::PARAM_INT);
        $stmt->execute();
        $rows = $stmt->fetchAll();

        // Contagem total com os mesmos filtros (sem paginação)
        $cntSql = "SELECT COUNT(*) AS total
                   FROM inventory_movements m
                   LEFT JOIN products p ON p.id = m.product_id
                   WHERE $whereSql";
        $cst = $pdo->prepare($cntSql);
        foreach ($params as $k => $v) { $cst->bindValue($k, $v); }
        $cst->execute();
        $cnt = $cst->fetch();

        return Response::json(['data' => $rows, 'page' => $page, 'per_page' => $per, 'total' => (int)($cnt['total'] ?? 0)]);
    }

    public function exportCsv($request)
    {
        $pdo = ConnectionManager::forTenant();
        $sep = (string)($request->query['sep'] ?? ';');
        if (!in_array($sep, [';', ',', '\t'], true)) { $sep = ';'; }
        if ($sep === '\t') { $sep = "\t"; }
        $q = trim((string)($request->query['q'] ?? ''));
        $type = (string)($request->query['type'] ?? '');
        $start = (string)($request->query['start'] ?? '');
        $end = (string)($request->query['end'] ?? '');

        $where = ['1=1'];
        $params = [];
        if ($q !== '') {
            // Buscar por product_id ou por código do produto
            $where[] = '(m.product_id LIKE :q OR p.codigo LIKE :q OR p.descricao LIKE :q)';
            $params[':q'] = '%' . $q . '%';
        }
        if ($type !== '' && in_array($type, ['in', 'out'], true)) {
            // Compatível com registros antigos (entrada_*/saida_*) e novos ('in'/'out')
            if ($type === 'in') {
                $where[] = '(m.tipo = :type OR m.tipo LIKE :typeLike)';
                $params[':type'] = 'in';
                $params[':typeLike'] = 'entrada%';
            } else {
                $where[] = '(m.tipo = :type OR m.tipo LIKE :typeLike)';
                $params[':type'] = 'out';
                $params[':typeLike'] = 'saida%';
            }
        }
        if ($start !== '') { $where[] = 'm.created_at >= :start'; $params[':start'] = $start; }
        if ($end !== '') { $where[] = 'm.created_at <= :end'; $params[':end'] = $end; }
        $whereSql = implode(' AND ', $where);

        $sql = "SELECT m.id, m.product_id, p.codigo, p.descricao, m.tipo, m.quantidade, m.motivo, m.stock_before, m.stock_after, m.origin_type, m.origin_number, m.created_at
                FROM inventory_movements m
                LEFT JOIN products p ON p.id = m.product_id
                WHERE $whereSql
                ORDER BY m.created_at DESC";
        $stmt = $pdo->prepare($sql);
        foreach ($params as $k => $v) { $stmt->bindValue($k, $v); }
        $stmt->execute();
        $rows = $stmt->fetchAll();

        $bom = chr(0xEF) . chr(0xBB) . chr(0xBF);
        $out = fopen('php://temp', 'r+');
        fputs($out, $bom);
        $headers = ['ID', 'Produto ID', 'Código', 'Descrição', 'Tipo', 'Quantidade', 'Motivo', 'Estoque Antes', 'Estoque Depois', 'Documento/Origem', 'Data'];
        fputcsv($out, $headers, $sep);
        foreach ($rows as $r) {
            $dt = isset($r['created_at']) ? (string)$r['created_at'] : '';
            // Normalizar possível formato ISO para simples 'Y-m-d H:i:s'
            $dt = str_replace('T', ' ', $dt);
            fputcsv($out, [
                (int)($r['id'] ?? 0),
                (int)($r['product_id'] ?? 0),
                $r['codigo'] ?? null,
                $r['descricao'] ?? null,
                $r['tipo'] ?? null,
                isset($r['quantidade']) ? (float)$r['quantidade'] : null,
                $r['motivo'] ?? null,
                isset($r['stock_before']) ? (float)$r['stock_before'] : null,
                isset($r['stock_after']) ? (float)$r['stock_after'] : null,
                // Documento/Origem: número simples
                $r['origin_number'] ?? null,
                $dt ? substr($dt, 0, 19) : null,
            ], $sep);
        }
        rewind($out);
        $csv = stream_get_contents($out);
        fclose($out);
        return new Response(200, $csv, [
            'Content-Type' => 'text/csv; charset=utf-8',
            'Content-Disposition' => 'attachment; filename="estoque_movimentos.csv"'
        ]);
    }

    public function entrada($request)
    {
        return $this->mov($request, 'in');
    }

    public function saida($request)
    {
        return $this->mov($request, 'out');
    }

    private function mov($request, string $type)
    {
        $pdo = ConnectionManager::forTenant();
        $p = $request->body;
        $pid = (int)($p['product_id'] ?? 0);
        $qty = (float)($p['quantity'] ?? 0);
        $reason = trim((string)($p['reason'] ?? ''));
        if ($pid <= 0 || $qty <= 0) { return Response::json(['error' => 'product_id e quantity são obrigatórios'], 422); }
        // Ler estoque atual do produto
        $pst = $pdo->prepare('SELECT id, estoque FROM products WHERE id = ? LIMIT 1');
        $pst->execute([$pid]);
        $prod = $pst->fetch();
        if (!$prod) { return new Response(404, ['error' => ['code' => 'product_not_found', 'message' => 'Produto não encontrado', 'product_id' => $pid]]); }
        $before = (float)($prod['estoque'] ?? 0);
        $delta = ($type === 'in') ? $qty : -$qty;
        // Política de estoque negativo: respeitar atributo injetado pelo middleware, com fallback para configuração em DB
        $allowNegAttr = $request->get('stock.allow_negative', null);
        $allowNegative = is_bool($allowNegAttr) ? $allowNegAttr : false;
        if ($allowNegAttr === null) {
            try {
                $row = $pdo->query('SELECT permitir_estoque_negativo FROM inventory_settings ORDER BY id ASC LIMIT 1')->fetch();
                $allowNegative = ((int)($row['permitir_estoque_negativo'] ?? 0)) === 1;
            } catch (\Throwable $e) {
                $allowNegative = false;
            }
        }
        if ($type !== 'in' && ($before + $delta) < 0 && !$allowNegative) {
            return new Response(422, ['error' => ['code' => 'insufficient_stock', 'message' => 'Estoque insuficiente', 'product_id' => $pid, 'available' => $before, 'required' => $qty]]);
        }
        $after = $before + $delta;
        // Atualizar estoque do produto
        $upd = $pdo->prepare('UPDATE products SET estoque = :estoque WHERE id = :id');
        $upd->execute([':estoque' => $after, ':id' => $pid]);
        // Inserir movimento com motivo e estoque antes/depois. Quantidade guarda o delta.
        $ins = $pdo->prepare('INSERT INTO inventory_movements (product_id, tipo, quantidade, motivo, stock_before, stock_after) VALUES (:pid, :tipo, :qtd, :motivo, :before, :after)');
        $ins->execute([':pid' => $pid, ':tipo' => $type, ':qtd' => $delta, ':motivo' => ($reason !== '' ? $reason : null), ':before' => $before, ':after' => $after]);
        $newId = (int)$pdo->lastInsertId();
        AuditLogger::logCreate('inventory_movements', $newId, ['product_id' => $pid, 'tipo' => $type, 'quantidade' => $delta, 'motivo' => $reason, 'stock_before' => $before, 'stock_after' => $after]);
        return Response::json(['data' => ['id' => $newId]]);
    }
}