<?php

declare(strict_types=1);

namespace App\Controllers;

use Exception;
use InvalidArgumentException;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Shared\Date as SpreadsheetDate;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use RuntimeException;

class EstoqueController extends BaseController
{
    public function index(): void
    {
        // Verificar permissão de visualização
        if (!$this->canView('estoque')) {
            $this->response->forbidden('Você não tem permissão para visualizar estoque.');
            return;
        }

        try {
            $companyId = $this->getCompanyId();

            // Estatísticas rápidas baseadas em estoque_movimentos
            // Produtos com estoque baixo (baseado em movimentações)
            $stmt = $this->db->prepare("
                SELECT p.*,
                       COALESCE(SUM(CASE WHEN em.type = 'entrada' THEN em.quantity ELSE -em.quantity END), 0) as estoque_atual
                FROM produtos p
                LEFT JOIN estoque_movimentos em ON em.product_id = p.id AND em.company_id = p.company_id
                WHERE p.company_id = :company_id AND p.is_active = 1
                GROUP BY p.id
                HAVING estoque_atual <= p.min_stock AND p.min_stock > 0
            ");
            $stmt->execute(['company_id' => $companyId]);
            $listaEstoqueBaixo = $stmt->fetchAll();
            $produtosEstoqueBaixo = count($listaEstoqueBaixo);

            // Produtos próximos a vencer (lotes que vencem em até 30 dias)
            $stmt = $this->db->prepare("
                SELECT DISTINCT em.product_id
                FROM estoque_movimentos em
                WHERE em.company_id = :company_id
                AND em.validade IS NOT NULL
                AND em.validade BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 30 DAY)
                AND em.type = 'entrada'
            ");
            $stmt->execute(['company_id' => $companyId]);
            $idsProximosVencer = $stmt->fetchAll(\PDO::FETCH_COLUMN);
            $produtosProximosVencer = count($idsProximosVencer);

            // Produtos vencidos (lotes com validade vencida)
            $stmt = $this->db->prepare("
                SELECT DISTINCT em.product_id
                FROM estoque_movimentos em
                WHERE em.company_id = :company_id
                AND em.validade IS NOT NULL
                AND em.validade < CURDATE()
                AND em.type = 'entrada'
            ");
            $stmt->execute(['company_id' => $companyId]);
            $idsVencidos = $stmt->fetchAll(\PDO::FETCH_COLUMN);
            $produtosVencidos = count($idsVencidos);

            // Buscar detalhes dos produtos próximos a vencer (com informações de lotes)
            $listaProximosVencer = [];
            if (!empty($idsProximosVencer)) {
                $in = implode(',', array_fill(0, count($idsProximosVencer), '?'));
                $stmt = $this->db->prepare("
                    SELECT DISTINCT
                        p.id,
                        p.name,
                        p.sku,
                        p.unit,
                        em.validade,
                        em.lote,
                        em.quantity as quantidade_lote
                    FROM produtos p
                    INNER JOIN estoque_movimentos em ON em.product_id = p.id AND em.company_id = p.company_id
                    WHERE p.company_id = ?
                    AND p.id IN ($in)
                    AND em.validade IS NOT NULL
                    AND em.validade BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 30 DAY)
                    AND em.type = 'entrada'
                    ORDER BY p.name ASC, em.validade ASC
                ");
                $stmt->execute(array_merge([$companyId], $idsProximosVencer));
                $listaProximosVencer = $stmt->fetchAll();
            }

            // Buscar detalhes dos produtos vencidos (com informações de lotes)
            $listaVencidos = [];
            if (!empty($idsVencidos)) {
                $in = implode(',', array_fill(0, count($idsVencidos), '?'));
                $stmt = $this->db->prepare("
                    SELECT DISTINCT
                        p.id,
                        p.name,
                        p.sku,
                        p.unit,
                        em.validade,
                        em.lote,
                        em.quantity as quantidade_lote
                    FROM produtos p
                    INNER JOIN estoque_movimentos em ON em.product_id = p.id AND em.company_id = p.company_id
                    WHERE p.company_id = ?
                    AND p.id IN ($in)
                    AND em.validade IS NOT NULL
                    AND em.validade < CURDATE()
                    AND em.type = 'entrada'
                    ORDER BY p.name ASC, em.validade ASC
                ");
                $stmt->execute(array_merge([$companyId], $idsVencidos));
                $listaVencidos = $stmt->fetchAll();
            }

            // Verificar configuração de uso de lote
            $mostrarCardLotes = true;
            try {
                $stmt = $this->db->prepare("
                    SELECT valor
                    FROM parametros
                    WHERE empresa_id = :empresa_id
                      AND chave = 'usar_lote_estoque'
                    LIMIT 1
                ");
                $stmt->execute(['empresa_id' => $companyId]);
                $paramSelecionado = $stmt->fetchColumn();

                if ($paramSelecionado === false || (int) $paramSelecionado !== 1) {
                    $mostrarCardLotes = false;
                }
            } catch (Exception $e) {
                error_log('Erro ao verificar parâmetro usar_lote_estoque: ' . $e->getMessage());
            }

            $this->view('estoque/index', [
                'produtosEstoqueBaixo' => $produtosEstoqueBaixo,
                'produtosProximosVencer' => $produtosProximosVencer,
                'produtosVencidos' => $produtosVencidos,
                'listaEstoqueBaixo' => $listaEstoqueBaixo,
                'listaProximosVencer' => $listaProximosVencer,
                'listaVencidos' => $listaVencidos,
                'mostrarCardLotes' => $mostrarCardLotes,
                'pageTitle' => 'Gestão de Estoque',
                'activeMenu' => 'estoque'
            ]);

        } catch (Exception $e) {
            error_log("Erro ao carregar estoque: " . $e->getMessage());
            $this->error('Erro ao carregar módulo de estoque');
        }
    }

    public function movimentacoes(): void
    {
        try {
            $companyId = $this->getCompanyId();
            $search = $this->request->get('search', '');
            $type = $this->request->get('type', '');

            $query = "
                SELECT em.*, p.name as product_name, le.name as location_name
                FROM estoque_movimentos em
                LEFT JOIN produtos p ON em.product_id = p.id
                LEFT JOIN locais_estoque le ON em.location_id = le.id
                WHERE em.company_id = :company_id
            ";

            $params = ['company_id' => $companyId];

            if (!empty($search)) {
                $query .= " AND (p.name LIKE :search OR em.reason LIKE :search)";
                $params['search'] = "%{$search}%";
            }

            if (!empty($type)) {
                $query .= " AND em.type = :type";
                $params['type'] = $type;
            }

            $query .= " ORDER BY em.created_at DESC";

            $stmt = $this->db->prepare($query);
            $stmt->execute($params);
            $movimentos = $stmt->fetchAll();

            $this->view('estoque/movimentacoes', [
                'movimentos' => $movimentos,
                'search' => $search,
                'type' => $type,
                'pageTitle' => 'Movimentações de Estoque',
                'activeMenu' => 'estoque'
            ]);

        } catch (Exception $e) {
            error_log("Erro ao carregar movimentações: " . $e->getMessage());
            $this->error('Erro ao carregar movimentações');
        }
    }

    public function indexInventarios(): void
    {
        try {
            $companyId = $this->getCompanyId();

            $filtrosInventario = [
                'status' => $this->request->get('status', ''),
                'responsavel' => $this->request->get('responsavel', ''),
                'local' => $this->request->get('inventario_local', ''),
                'data_inicio' => $this->request->get('data_inicio', ''),
                'data_fim' => $this->request->get('data_fim', ''),
                'busca' => trim((string) $this->request->get('busca', '')),
            ];

            $estatisticasInventario = [
                'total' => 0,
                'finalizado' => 0,
                'em_andamento' => 0,
                'cancelado' => 0,
            ];
            $inventariosListagem = [];
            $responsaveisInventario = [];
            $inventarioTabelasAtivas = $this->tabelaExiste('inventarios');
            $inventarioItensAtivos = $inventarioTabelasAtivas && $this->tabelaExiste('inventario_itens');

            if ($inventarioTabelasAtivas) {
                try {
                    $stmt = $this->db->prepare("SELECT status, COUNT(*) AS total FROM inventarios WHERE company_id = :company_id GROUP BY status");
                    $stmt->execute(['company_id' => $companyId]);
                    while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
                        $status = strtolower((string) ($row['status'] ?? ''));
                        $total = (int) ($row['total'] ?? 0);
                        $estatisticasInventario['total'] += $total;
                        if (isset($estatisticasInventario[$status])) {
                            $estatisticasInventario[$status] = $total;
                        }
                    }
                } catch (Exception $e) {
                    error_log('Inventário: falha ao calcular estatísticas: ' . $e->getMessage());
                }

                try {
                    $stmt = $this->db->prepare("SELECT DISTINCT i.created_by, COALESCE(u.name, CONCAT('Usuário #', i.created_by)) AS nome FROM inventarios i LEFT JOIN users u ON i.created_by = u.id WHERE i.company_id = :company_id AND i.created_by IS NOT NULL ORDER BY nome ASC");
                    $stmt->execute(['company_id' => $companyId]);
                    $responsaveisInventario = $stmt->fetchAll() ?: [];
                } catch (Exception $e) {
                    error_log('Inventário: falha ao carregar responsáveis: ' . $e->getMessage());
                }

                try {
                    $paramsInventarios = ['company_id' => $companyId];
                    $where = 'WHERE i.company_id = :company_id';

                    if ($filtrosInventario['status'] !== '') {
                        $where .= ' AND i.status = :status';
                        $paramsInventarios['status'] = $filtrosInventario['status'];
                    }

                    if ($filtrosInventario['responsavel'] !== '') {
                        $where .= ' AND i.created_by = :responsavel';
                        $paramsInventarios['responsavel'] = (int) $filtrosInventario['responsavel'];
                    }

                    if ($filtrosInventario['local'] !== '') {
                        if ($filtrosInventario['local'] === 'sem_local') {
                            $where .= ' AND i.local_estoque_id IS NULL';
                        } else {
                            $where .= ' AND i.local_estoque_id = :local_id';
                            $paramsInventarios['local_id'] = (int) $filtrosInventario['local'];
                        }
                    }

                    $dataInicio = $this->converterDataFiltro($filtrosInventario['data_inicio'], '00:00:00');
                    if ($dataInicio) {
                        $where .= ' AND i.created_at >= :data_inicio';
                        $paramsInventarios['data_inicio'] = $dataInicio;
                    }

                    $dataFim = $this->converterDataFiltro($filtrosInventario['data_fim'], '23:59:59');
                    if ($dataFim) {
                        $where .= ' AND i.created_at <= :data_fim';
                        $paramsInventarios['data_fim'] = $dataFim;
                    }

                    if ($filtrosInventario['busca'] !== '') {
                        $where .= ' AND (i.notes LIKE :busca OR i.id = :busca_id)';
                        $paramsInventarios['busca'] = '%' . $filtrosInventario['busca'] . '%';
                        $paramsInventarios['busca_id'] = (int) preg_replace('/\D/', '', $filtrosInventario['busca']);
                    }

                    $selectResumo = '0 AS total_itens, 0 AS itens_ajustados, 0 AS diferenca_total';
                    $joinResumo = '';
                    if ($inventarioItensAtivos) {
                        $selectResumo = 'COALESCE(res.total_itens, 0) AS total_itens, COALESCE(res.itens_ajustados, 0) AS itens_ajustados, COALESCE(res.diferenca_total, 0) AS diferenca_total';
                        $joinResumo = "
                            LEFT JOIN (
                                SELECT inventario_id, COUNT(*) AS total_itens,
                                       SUM(CASE WHEN ABS(difference) > 0 THEN 1 ELSE 0 END) AS itens_ajustados,
                                       SUM(difference) AS diferenca_total
                                FROM inventario_itens
                                GROUP BY inventario_id
                            ) res ON res.inventario_id = i.id
                        ";
                    }

                    $queryInventarios = "
                        SELECT i.id, i.date, i.status, i.notes, i.created_at, i.updated_at, i.local_estoque_id, i.created_by,
                               COALESCE(u.name, CONCAT('Usuário #', i.created_by)) AS usuario_nome,
                               COALESCE(le.name, 'Todos os locais') AS local_name,
                               {$selectResumo}
                        FROM inventarios i
                        LEFT JOIN users u ON i.created_by = u.id
                        LEFT JOIN locais_estoque le ON i.local_estoque_id = le.id
                        {$joinResumo}
                        {$where}
                        ORDER BY i.created_at DESC
                        LIMIT 150
                    ";

                    $stmt = $this->db->prepare($queryInventarios);
                    $stmt->execute($paramsInventarios);
                    $inventariosListagem = $stmt->fetchAll() ?: [];
                } catch (Exception $e) {
                    error_log('Inventário: falha ao listar inventários: ' . $e->getMessage());
                }
            }

            $locais = [];
            try {
                $stmt = $this->db->prepare("SELECT id, name FROM locais_estoque WHERE company_id = :company_id AND is_active = 1 ORDER BY name ASC");
                $stmt->execute(['company_id' => $companyId]);
                $locais = $stmt->fetchAll();
            } catch (Exception $e) {
                error_log("Locais de estoque não disponíveis: " . $e->getMessage());
            }

            $this->view('estoque/inventario/index', [
                'inventarioTabelasAtivas' => $inventarioTabelasAtivas,
                'inventarioItensAtivos' => $inventarioItensAtivos,
                'inventarios' => $inventariosListagem,
                'estatisticasInventario' => $estatisticasInventario,
                'responsaveisInventario' => $responsaveisInventario,
                'filtrosInventario' => $filtrosInventario,
                'locais' => $locais,
                'mostrarCampoLote' => $this->deveExibirCampoLote($companyId),
                'pageTitle' => 'Inventário de Estoque',
                'activeMenu' => 'estoque'
            ]);
        } catch (Exception $e) {
            error_log("Erro ao carregar inventário: " . $e->getMessage());
            error_log("Stack trace: " . $e->getTraceAsString());
            $this->error('Erro ao carregar inventário: ' . $e->getMessage());
        }
    }

    public function createInventario(): void
    {
        try {
            $companyId = $this->getCompanyId();

            $locais = [];
            try {
                $stmt = $this->db->prepare("SELECT id, name FROM locais_estoque WHERE company_id = :company_id AND is_active = 1 ORDER BY name ASC");
                $stmt->execute(['company_id' => $companyId]);
                $locais = $stmt->fetchAll();
            } catch (Exception $e) {
                error_log("Locais de estoque não disponíveis: " . $e->getMessage());
            }

            $produtosAutocomplete = [];
            try {
                $stmt = $this->db->prepare("
                    SELECT
                        p.id,
                        p.name,
                        p.sku,
                        COALESCE(SUM(CASE WHEN em.type = 'entrada' THEN em.quantity ELSE -em.quantity END), 0) AS estoque_sistema
                    FROM produtos p
                    LEFT JOIN estoque_movimentos em ON em.product_id = p.id AND em.company_id = p.company_id
                    WHERE p.company_id = :company_id
                      AND p.is_active = 1
                    GROUP BY p.id
                    ORDER BY p.name ASC
                    LIMIT 500
                ");
                $stmt->execute(['company_id' => $companyId]);
                $produtosAutocomplete = $stmt->fetchAll() ?: [];
            } catch (Exception $e) {
                error_log('Inventário: falha ao carregar produtos para autocomplete: ' . $e->getMessage());
            }

            $this->view('estoque/inventario/create', [
                'locais' => $locais,
                'produtos' => $produtosAutocomplete,
                'mostrarCampoLote' => $this->deveExibirCampoLote($companyId),
                'pageTitle' => 'Nova Contagem de Inventário',
                'activeMenu' => 'estoque'
            ]);
        } catch (Exception $e) {
            error_log("Erro ao carregar formulário de inventário: " . $e->getMessage());
            $this->error('Erro ao carregar formulário de inventário.');
        }
    }

    public function storeInventario(): void
    {
        try {
            $ajustes = $this->request->post('ajustes') ?? [];
            $locationIdRaw = $this->request->post('location_id');
            $notes = trim((string) ($this->request->post('notes') ?? ''));
            $locationId = ($locationIdRaw === '' || $locationIdRaw === null) ? null : (int) $locationIdRaw;

            $resultado = $this->registrarInventario($ajustes, $locationId, $notes);

            if ($this->request->isAjax() || $this->request->isJson()) {
                $this->success($resultado['mensagem'], [
                    'redirect' => '/estoque/inventario'
                ]);
            } else {
                $this->session->flash('success_message', $resultado['mensagem']);
                $this->redirect('/estoque/inventario');
            }
        } catch (InvalidArgumentException $e) {
            $this->error($e->getMessage());
        } catch (Exception $e) {
            error_log('Erro ao criar inventário: ' . $e->getMessage());
            $this->error('Erro ao realizar inventário: ' . $e->getMessage());
        }
    }

    public function buscarProdutosInventario(): void
    {
        try {
            $companyId = $this->getCompanyId();
            $termo = trim((string) $this->request->get('q', ''));

            if ($termo === '') {
                $this->success('Informe um termo de busca', [
                    'produtos' => []
                ]);
                return;
            }

            $consulta = "%{$termo}%";
            $stmt = $this->db->prepare("
                SELECT
                    p.id,
                    p.name,
                    p.sku,
                    COALESCE(SUM(CASE WHEN em.type = 'entrada' THEN em.quantity ELSE -em.quantity END), 0) AS estoque_sistema
                FROM produtos p
                LEFT JOIN estoque_movimentos em ON em.product_id = p.id AND em.company_id = p.company_id
                WHERE p.company_id = :company_id
                  AND p.is_active = 1
                  AND (p.name LIKE :busca OR p.sku LIKE :busca OR p.barcode LIKE :busca)
                GROUP BY p.id
                ORDER BY p.name ASC
                LIMIT 20
            ");
            $stmt->execute([
                'company_id' => $companyId,
                'busca' => $consulta
            ]);

            $produtos = array_map(static function ($item) {
                return [
                    'id' => (int) ($item['id'] ?? 0),
                    'name' => $item['name'] ?? '',
                    'sku' => $item['sku'] ?? '',
                    'estoque_sistema' => (float) ($item['estoque_sistema'] ?? 0)
                ];
            }, $stmt->fetchAll() ?: []);

            $this->success('Produtos carregados', [
                'produtos' => $produtos
            ]);
        } catch (Exception $e) {
            error_log('Inventário: falha ao buscar produtos: ' . $e->getMessage());
            $this->error('Erro ao buscar produtos.');
        }
    }

    public function exportarInventarioTemplate(): void
    {
        try {
            // Permitir escolher explicitamente a empresa pelo parâmetro empresa_id
            $empresaIdParam = $this->request->get('empresa_id');
            $companyId = $empresaIdParam ? (int) $empresaIdParam : $this->getCompanyId();

            error_log('Inventário: Exportar template - empresa_id recebido: ' . ($empresaIdParam ?? 'null') . ', companyId final: ' . ($companyId ?? 'null'));

            if (!$companyId) {
                throw new RuntimeException('Empresa não identificada.');
            }

            // Usar o ID da empresa diretamente na planilha
            $empresaId = $companyId;

            $produtos = $this->obterProdutosParaInventario($companyId);

            $spreadsheet = new Spreadsheet();
            $sheet = $spreadsheet->getActiveSheet();
            $sheet->setTitle('Inventario');

            // Cabeçalho para contagem cega (sem quantidade_sistema e sem product_id)
            $headers = [
                'A' => 'empresa',
                'B' => 'produto_sku',
                'C' => 'produto_nome',
                'D' => 'quantidade_fisica',
                'E' => 'lote',
                'F' => 'numero_serie',
                'G' => 'fabricacao',
                'H' => 'validade',
                'I' => 'observacao',
            ];

            foreach ($headers as $col => $header) {
                $sheet->setCellValue($col . '1', strtoupper($header));
                $sheet->getStyle($col . '1')->getFont()->setBold(true);
            }

            $row = 2;
            foreach ($produtos as $produto) {
                // Coluna de empresa preenchida com o ID da empresa para todas as linhas
                $sheet->setCellValue('A' . $row, $empresaId);
                $sheet->setCellValueExplicit('B' . $row, (string) $produto['sku'], \PhpOffice\PhpSpreadsheet\Cell\DataType::TYPE_STRING);
                $sheet->setCellValue('C' . $row, $produto['name']);
                // quantidade_fisica vazia (contagem será preenchida pelo usuário)
                $sheet->setCellValue('D' . $row, null);
                $sheet->setCellValue('E' . $row, null);
                $sheet->setCellValue('F' . $row, null);
                $sheet->setCellValue('G' . $row, null);
                $sheet->setCellValue('H' . $row, null);
                $sheet->setCellValue('I' . $row, null);
                $row++;
            }

            foreach (array_keys($headers) as $col) {
                $sheet->getColumnDimension($col)->setAutoSize(true);
            }

            $sheet->freezePane('A2');

            $filename = 'modelo_inventario_' . date('Ymd_His') . '.xlsx';

            header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
            header('Content-Disposition: attachment; filename="' . $filename . '"');
            header('Cache-Control: max-age=0');

            $writer = new Xlsx($spreadsheet);
            $writer->save('php://output');
            exit;
        } catch (Exception $e) {
            error_log('Inventário: falha ao exportar template: ' . $e->getMessage());

            if ($this->request->isAjax() || $this->request->isJson()) {
                $this->error('Erro ao exportar modelo de inventário.');
            } else {
                $this->session->flash('error_message', 'Erro ao exportar modelo de inventário.');
                $this->redirect('/estoque/inventario');
            }
        }
    }

    public function importarInventarioPlanilha(): void
    {
        // Forçar detecção AJAX se tiver o header
        $isAjax = $this->request->isAjax() ||
            $this->request->isJson() ||
            (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');

        // Se for AJAX, sempre retornar JSON
        if ($isAjax) {
            header('Content-Type: application/json; charset=utf-8');
        }

        try {
            // Verificar arquivo
            if (!isset($_FILES['arquivo_inventario']) || empty($_FILES['arquivo_inventario']['tmp_name'])) {
                throw new InvalidArgumentException('Selecione um arquivo para importação.');
            }

            $arquivo = $_FILES['arquivo_inventario'];

            if (!is_uploaded_file($arquivo['tmp_name'])) {
                throw new InvalidArgumentException('Erro ao fazer upload do arquivo.');
            }

            $extensao = strtolower(pathinfo($arquivo['name'], PATHINFO_EXTENSION));
            if (!in_array($extensao, ['xlsx', 'xls', 'csv'], true)) {
                throw new InvalidArgumentException('Formato de arquivo inválido. Utilize uma planilha .xlsx.');
            }

            $empresaIdSessao = $this->getCompanyId();
            if (!$empresaIdSessao) {
                throw new RuntimeException('Empresa não identificada para importação.');
            }

            $locationIdRaw = $this->request->post('import_location_id');
            $notes = trim((string) ($this->request->post('import_notes') ?? ''));
            $locationId = ($locationIdRaw === null || $locationIdRaw === '') ? null : (int) $locationIdRaw;

            $reader = IOFactory::createReaderForFile($arquivo['tmp_name']);
            $spreadsheet = $reader->load($arquivo['tmp_name']);
            $sheet = $spreadsheet->getActiveSheet();

            $highestRow = $sheet->getHighestDataRow();
            $highestColumn = $sheet->getHighestDataColumn();
            $highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);

            if ($highestRow < 2) {
                throw new InvalidArgumentException('A planilha não contém dados para importar.');
            }

            // PEGAR company_id DA CÉLULA A2
            $companyId = $empresaIdSessao;
            $empresaIdA2 = $sheet->getCellByColumnAndRow(1, 2)->getValue();
            if ($empresaIdA2 !== null && $empresaIdA2 !== '') {
                $companyId = (int) $empresaIdA2;
                // Validar se a empresa existe
                $stmt = $this->db->prepare("SELECT id FROM empresas WHERE id = :id LIMIT 1");
                $stmt->execute(['id' => $companyId]);
                if (!$stmt->fetch()) {
                    throw new InvalidArgumentException("Empresa ID {$companyId} não encontrada.");
                }
            }

            // Criar inventário com company_id de A2
            $colunasInventarios = $this->tabelaExiste('inventarios') ? $this->colunasTabela('inventarios') : [];
            $colunasInventarioItens = $this->tabelaExiste('inventario_itens') ? $this->colunasTabela('inventario_itens') : [];
            $userId = $this->getUserId();
            $inventarioId = null;

            $this->db->beginTransaction();

            try {
                // Criar inventário
                if (!empty($colunasInventarios)) {
                    $dadosInventario = [];
                    if (in_array('company_id', $colunasInventarios, true)) {
                        $dadosInventario['company_id'] = $companyId;
                    }
                    if (in_array('local_estoque_id', $colunasInventarios, true) && $locationId) {
                        $dadosInventario['local_estoque_id'] = $locationId;
                    }
                    if (in_array('date', $colunasInventarios, true)) {
                        $dadosInventario['date'] = date('Y-m-d');
                    }
                    if (in_array('status', $colunasInventarios, true)) {
                        $dadosInventario['status'] = 'finalizado';
                    }
                    if ($notes !== '' && in_array('notes', $colunasInventarios, true)) {
                        $dadosInventario['notes'] = $notes;
                    }
                    if ($userId && in_array('created_by', $colunasInventarios, true)) {
                        $dadosInventario['created_by'] = $userId;
                    }

                    if (!empty($dadosInventario)) {
                        $colunas = array_keys($dadosInventario);
                        $placeholders = array_map(static function ($coluna) {
                            return ':' . $coluna;
                        }, $colunas);

                        $sql = sprintf(
                            'INSERT INTO inventarios (%s) VALUES (%s)',
                            implode(', ', $colunas),
                            implode(', ', $placeholders)
                        );

                        $stmt = $this->db->prepare($sql);
                        $stmt->execute($dadosInventario);
                        $inventarioId = (int) $this->db->lastInsertId();
                    }
                }

                // Processar produtos e criar itens
                if ($inventarioId && !empty($colunasInventarioItens)) {
                    $cabecalhoMapeado = $this->mapearCabecalhoPlanilha($sheet, $highestColumnIndex);
                    $totalItens = 0;

                    for ($row = 2; $row <= $highestRow; $row++) {
                        $dadosLinha = $this->extrairDadosLinhaPlanilha($sheet, $row, $highestColumnIndex, $cabecalhoMapeado);
                        if ($dadosLinha === null) {
                            continue;
                        }

                        if (empty($dadosLinha['product_id']) && empty($dadosLinha['sku'])) {
                            continue;
                        }

                        $produto = $this->resolverProdutoPorLinha($companyId, $dadosLinha);
                        if (!$produto) {
                            continue;
                        }

                        $quantidadeFisica = $dadosLinha['quantidade_fisica'] ?? 0;
                        $quantidadeSistema = $dadosLinha['quantidade_sistema'] ?? $this->obterEstoqueAtualProduto($produto['id'], $companyId);
                        $diferenca = $quantidadeFisica - $quantidadeSistema;

                        // Criar item em inventario_itens
                        $dadosItem = [];
                        if (in_array('inventario_id', $colunasInventarioItens, true)) {
                            $dadosItem['inventario_id'] = $inventarioId;
                        }
                        if (in_array('item_id', $colunasInventarioItens, true)) {
                            $dadosItem['item_id'] = $produto['id'];
                        }
                        if (in_array('system_quantity', $colunasInventarioItens, true)) {
                            $dadosItem['system_quantity'] = $quantidadeSistema;
                        }
                        if (in_array('counted_quantity', $colunasInventarioItens, true)) {
                            $dadosItem['counted_quantity'] = $quantidadeFisica;
                        }
                        if (in_array('difference', $colunasInventarioItens, true)) {
                            $dadosItem['difference'] = $diferenca;
                        }
                        if (in_array('lote', $colunasInventarioItens, true) && !empty($dadosLinha['lote'])) {
                            $dadosItem['lote'] = $dadosLinha['lote'];
                        }
                        if (in_array('fabricacao', $colunasInventarioItens, true) && !empty($dadosLinha['fabricacao'])) {
                            $dadosItem['fabricacao'] = $dadosLinha['fabricacao'];
                        }
                        if (in_array('validade', $colunasInventarioItens, true) && !empty($dadosLinha['validade'])) {
                            $dadosItem['validade'] = $dadosLinha['validade'];
                        }
                        if (in_array('numero_serie', $colunasInventarioItens, true) && !empty($dadosLinha['numero_serie'])) {
                            $dadosItem['numero_serie'] = $dadosLinha['numero_serie'];
                        }
                        if (in_array('company_id', $colunasInventarioItens, true)) {
                            $dadosItem['company_id'] = $companyId;
                        }
                        if (in_array('created_by', $colunasInventarioItens, true) && $userId) {
                            $dadosItem['created_by'] = $userId;
                        }
                        if (in_array('notes', $colunasInventarioItens, true) && !empty($dadosLinha['notes'])) {
                            $dadosItem['notes'] = $dadosLinha['notes'];
                        }

                        if (!empty($dadosItem)) {
                            $colunas = array_keys($dadosItem);
                            $placeholders = array_map(static function ($coluna) {
                                return ':' . $coluna;
                            }, $colunas);

                            $sql = sprintf(
                                'INSERT INTO inventario_itens (%s) VALUES (%s)',
                                implode(', ', $colunas),
                                implode(', ', $placeholders)
                            );

                            $stmt = $this->db->prepare($sql);
                            $stmt->execute($dadosItem);
                            $totalItens++;
                        }

                        // Criar movimento em estoque_movimentos se houver diferença
                        if ($diferenca != 0.0) {
                            $tipo = $diferenca > 0 ? 'entrada' : 'saida';
                            $quantidadeAbs = abs($diferenca);

                            $colunasMovimentos = $this->colunasTabela('estoque_movimentos');
                            $dadosMovimento = [
                                'company_id' => $companyId,
                                'product_id' => $produto['id'],
                                'type' => $tipo,
                                'quantity' => $quantidadeAbs,
                                'reason' => 'Ajuste de inventário - Contagem física',
                                'reference_id' => $inventarioId,
                                'reference_type' => 'inventario'
                            ];

                            if (in_array('location_id', $colunasMovimentos, true) && $locationId) {
                                $dadosMovimento['location_id'] = $locationId;
                            }
                            if (in_array('numero_serie', $colunasMovimentos, true) && !empty($dadosLinha['numero_serie'])) {
                                $dadosMovimento['numero_serie'] = $dadosLinha['numero_serie'];
                            }
                            if (in_array('lote', $colunasMovimentos, true) && !empty($dadosLinha['lote'])) {
                                $dadosMovimento['lote'] = $dadosLinha['lote'];
                            }
                            if (in_array('fabricacao', $colunasMovimentos, true) && !empty($dadosLinha['fabricacao'])) {
                                $dadosMovimento['fabricacao'] = $dadosLinha['fabricacao'];
                            }
                            if (in_array('validade', $colunasMovimentos, true) && !empty($dadosLinha['validade'])) {
                                $dadosMovimento['validade'] = $dadosLinha['validade'];
                            }

                            $colunasMov = array_keys($dadosMovimento);
                            $colunasMov[] = 'created_at';
                            $placeholdersMov = array_map(static function ($coluna) {
                                return $coluna === 'created_at' ? 'NOW()' : ':' . $coluna;
                            }, $colunasMov);

                            $sqlMov = sprintf(
                                'INSERT INTO estoque_movimentos (%s) VALUES (%s)',
                                implode(', ', $colunasMov),
                                implode(', ', $placeholdersMov)
                            );

                            $stmtMov = $this->db->prepare($sqlMov);
                            $stmtMov->execute($dadosMovimento);
                        }
                    }
                }

                $this->db->commit();

                $codigoInventario = $inventarioId ? ' Inventário #' . str_pad((string) $inventarioId, 5, '0', STR_PAD_LEFT) . '.' : '';
                $mensagem = "Inventário criado com sucesso! Company ID: {$companyId}." . $codigoInventario;
            } catch (Exception $e) {
                $this->db->rollBack();
                throw $e;
            }

            if ($isAjax) {
                $this->success($mensagem, ['redirect' => '/estoque/inventario']);
            } else {
                $this->session->flash('success_message', $mensagem);
                $this->redirect('/estoque/inventario');
            }
        } catch (InvalidArgumentException $e) {
            if ($isAjax) {
                $this->error($e->getMessage());
            } else {
                $this->session->flash('error_message', $e->getMessage());
                $this->redirect('/estoque/inventario');
            }
        } catch (Exception $e) {
            if ($isAjax) {
                $this->error('Erro ao importar inventário: ' . $e->getMessage());
            } else {
                $this->session->flash('error_message', 'Erro ao importar inventário: ' . $e->getMessage());
                $this->redirect('/estoque/inventario');
            }
        }
    }

    public function detalhesInventario(): void
    {
        try {
            $companyId = $this->getCompanyId();
            $inventarioId = (int) $this->request->get('id', 0);

            if ($inventarioId <= 0) {
                $this->error('Inventário inválido');
                return;
            }

            if (!$this->tabelaExiste('inventarios')) {
                $this->error('Funcionalidade indisponível no momento.');
                return;
            }

            $stmt = $this->db->prepare("
                SELECT
                    i.id,
                    i.date,
                    i.status,
                    i.notes,
                    i.created_at,
                    i.updated_at,
                    i.local_estoque_id,
                    COALESCE(le.name, 'Todos os locais') AS local_name,
                    COALESCE(u.name, CONCAT('Usuário #', i.created_by)) AS usuario_nome
                FROM inventarios i
                LEFT JOIN locais_estoque le ON i.local_estoque_id = le.id
                LEFT JOIN users u ON i.created_by = u.id
                WHERE i.id = :id AND i.company_id = :company_id
                LIMIT 1
            ");
            $stmt->execute([
                'id' => $inventarioId,
                'company_id' => $companyId
            ]);
            $inventario = $stmt->fetch();

            if (!$inventario) {
                $this->error('Inventário não encontrado');
                return;
            }

            $itens = [];
            if ($this->tabelaExiste('inventario_itens')) {
                $mostrarCampoLote = $this->deveExibirCampoLote($companyId);

                // Verificar se as colunas de lote existem na tabela
                $colunasTabela = $this->colunasTabela('inventario_itens');
                $temColunaLote = in_array('lote', $colunasTabela, true);
                $temColunaFabricacao = in_array('fabricacao', $colunasTabela, true);
                $temColunaValidade = in_array('validade', $colunasTabela, true);

                // Buscar itens diretamente de inventario_itens (agora com os campos de lote, fabricação e validade)
                $campos = "
                    ii.item_id,
                    ii.system_quantity,
                    ii.counted_quantity,
                    ii.difference,
                    p.name AS product_name,
                    p.sku
                ";

                if ($mostrarCampoLote && $temColunaLote) {
                    $campos .= ",
                    ii.lote,
                    ii.fabricacao,
                    ii.validade,
                    ii.numero_serie
                    ";
                } else if ($mostrarCampoLote) {
                    // Se mostrarCampoLote está habilitado mas as colunas não existem, retornar NULL
                    $campos .= ",
                    NULL AS lote,
                    NULL AS fabricacao,
                    NULL AS validade,
                    NULL AS numero_serie
                    ";
                }

                $stmt = $this->db->prepare("
                    SELECT
                        {$campos}
                    FROM inventario_itens ii
                    LEFT JOIN produtos p ON ii.item_id = p.id
                    WHERE ii.inventario_id = :inventario_id
                    ORDER BY p.name ASC
                ");
                $stmt->execute(['inventario_id' => $inventarioId]);
                $itens = $stmt->fetchAll() ?: [];
            }

            $mostrarCampoLote = $this->deveExibirCampoLote($companyId);

            $this->success('Inventário carregado com sucesso', [
                'inventario' => $inventario,
                'itens' => $itens,
                'mostrarCampoLote' => $mostrarCampoLote,
            ]);
        } catch (Exception $e) {
            error_log('Inventário: falha ao carregar detalhes: ' . $e->getMessage());
            $this->error('Erro ao carregar detalhes do inventário');
        }
    }

    public function relatorios(): void
    {
        try {
            $companyId = $this->getCompanyId();

            $this->view('estoque/relatorios', [
                'pageTitle' => 'Relatórios de Estoque',
                'activeMenu' => 'estoque'
            ]);

        } catch (Exception $e) {
            error_log("Erro ao carregar relatórios: " . $e->getMessage());
            $this->error('Erro ao carregar relatórios');
        }
    }

    /**
     * Retorna lista de empresas disponíveis para exportação de inventário
     */
    public function listarEmpresasParaInventario(): void
    {
        try {
            // Forçar retorno JSON para requisições de API
            if (
                $this->request->isAjax() || $this->request->isJson() ||
                isset($_SERVER['HTTP_X_REQUESTED_WITH']) ||
                isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false
            ) {
                header('Content-Type: application/json; charset=utf-8');
            }

            if (!$this->db) {
                $this->error('Conexão com banco de dados não disponível.');
                return;
            }

            // Verificar se a tabela empresas existe
            $stmt = $this->db->query("SHOW TABLES LIKE 'empresas'");
            if ($stmt->rowCount() === 0) {
                $this->error('Nenhuma empresa cadastrada.');
                return;
            }

            // Verificar se existe coluna 'ativo' ou 'status'
            $stmtCheck = $this->db->query("SHOW COLUMNS FROM empresas LIKE 'ativo'");
            $temColunaAtivo = $stmtCheck->rowCount() > 0;

            $stmtCheck = $this->db->query("SHOW COLUMNS FROM empresas LIKE 'status'");
            $temColunaStatus = $stmtCheck->rowCount() > 0;

            $query = "
                SELECT id, razao_social, nome_fantasia, cnpj
                FROM empresas
            ";

            if ($temColunaAtivo) {
                $query .= " WHERE (ativo = 'Sim' OR ativo = 1 OR ativo = '1')";
            } elseif ($temColunaStatus) {
                $query .= " WHERE (status = 'ativo' OR status = 1 OR status = '1')";
            }

            $query .= " ORDER BY razao_social ASC";

            $stmt = $this->db->prepare($query);
            $stmt->execute();
            $empresas = $stmt->fetchAll() ?: [];

            if (empty($empresas)) {
                $this->error('Nenhuma empresa cadastrada.');
                return;
            }

            $this->success('Empresas carregadas com sucesso', [
                'empresas' => $empresas,
            ]);
        } catch (Exception $e) {
            error_log('Inventário: falha ao listar empresas: ' . $e->getMessage());
            $this->error('Erro ao carregar empresas para exportação.');
        }
    }

    public function lotes(): void
    {
        try {
            $companyId = $this->getCompanyId();

            $this->view('estoque/lotes', [
                'pageTitle' => 'Gestão de Lotes',
                'activeMenu' => 'estoque'
            ]);

        } catch (Exception $e) {
            error_log("Erro ao carregar lotes: " . $e->getMessage());
            $this->error('Erro ao carregar gestão de lotes');
        }
    }

    public function buscarVendasExpedicao(): void
    {
        try {
            $companyId = $this->getCompanyId();

            // Buscar vendas finalizadas com chave_nfe
            $stmt = $this->db->prepare("
                SELECT
                    v.id,
                    v.sale_number,
                    v.sale_date,
                    v.customer_name,
                    v.customer_document,
                    v.total,
                    v.chave_nfe,
                    v.numero_nfe,
                    v.status,
                    v.created_at
                FROM vendas v
                WHERE v.company_id = :company_id
                  AND v.status = 'finalizado'
                  AND v.chave_nfe IS NOT NULL
                  AND v.chave_nfe != ''
                ORDER BY v.sale_date DESC, v.id DESC
                LIMIT 500
            ");
            $stmt->execute(['company_id' => $companyId]);
            $vendas = $stmt->fetchAll(\PDO::FETCH_ASSOC);

            $this->success('Vendas carregadas com sucesso', [
                'vendas' => $vendas
            ]);

        } catch (Exception $e) {
            error_log("Erro ao buscar vendas de expedição: " . $e->getMessage());
            error_log("Stack trace: " . $e->getTraceAsString());
            $this->error('Erro ao buscar vendas de expedição: ' . $e->getMessage());
        }
    }

    public function create(): void
    {
        try {
            $companyId = $this->getCompanyId();

            // Busca produtos
            $stmt = $this->db->prepare("
                SELECT id, name FROM produtos
                WHERE company_id = :company_id AND is_active = 1
                ORDER BY name ASC
            ");
            $stmt->execute(['company_id' => $companyId]);
            $produtos = $stmt->fetchAll();

            // Busca locais de estoque
            $stmt = $this->db->prepare("
                SELECT id, name FROM locais_estoque
                WHERE company_id = :company_id AND is_active = 1
                ORDER BY name ASC
            ");
            $stmt->execute(['company_id' => $companyId]);
            $locais = $stmt->fetchAll();

            $this->view('estoque/create', [
                'produtos' => $produtos,
                'locais' => $locais,
                'pageTitle' => 'Nova Movimentação',
                'activeMenu' => 'estoque'
            ]);

        } catch (Exception $e) {
            error_log("Erro ao carregar formulário de estoque: " . $e->getMessage());
            $this->error('Erro ao carregar formulário');
        }
    }

    public function store(): void
    {
        // Verificar permissão de criação
        if (!$this->canCreate('estoque')) {
            $this->response->forbidden('Você não tem permissão para criar movimentações de estoque.');
            return;
        }

        try {
            $companyId = $this->getCompanyId();

            $data = [
                'company_id' => $companyId,
                'product_id' => $this->request->post('product_id'),
                'location_id' => $this->request->post('location_id') ?: null,
                'type' => $this->request->post('type'),
                'quantity' => $this->request->post('quantity'),
                'reason' => $this->request->post('reason'),
                'reference_id' => $this->request->post('reference_id') ?: null,
                'reference_type' => $this->request->post('reference_type') ?: null,
            ];

            $this->db->beginTransaction();

            $stmt = $this->db->prepare("
                INSERT INTO estoque_movimentos (company_id, product_id, location_id, type, quantity, reason, reference_id, reference_type, created_at)
                VALUES (:company_id, :product_id, :location_id, :type, :quantity, :reason, :reference_id, :reference_type, NOW())
            ");
            $stmt->execute($data);
            $movimentoId = (int) $this->db->lastInsertId();

            // Atualiza estoque do produto
            $this->updateProductStock($data['product_id'], $data['type'], $data['quantity']);

            $this->logActivity('create', 'estoque_movimentos', $movimentoId, $data);
            $this->db->commit();

            $this->success('Movimentação criada com sucesso', [
                'id' => $movimentoId,
                'redirect' => '/estoque'
            ]);

        } catch (Exception $e) {
            $this->db->rollBack();
            error_log("Erro ao criar movimentação: " . $e->getMessage());
            $this->error('Erro ao criar movimentação');
        }
    }

    public function edit(): void
    {
        // Verificar permissão de edição
        if (!$this->canEdit('estoque')) {
            $this->response->forbidden('Você não tem permissão para editar movimentações de estoque.');
            return;
        }

        try {
            $id = (int) $this->request->get('id');
            $movimento = $this->getMovimento($id);

            if (!$movimento) {
                $this->response->notFound('Movimentação não encontrada');
                return;
            }

            $companyId = $this->getCompanyId();

            // Busca produtos
            $stmt = $this->db->prepare("
                SELECT id, name FROM produtos
                WHERE company_id = :company_id AND is_active = 1
                ORDER BY name ASC
            ");
            $stmt->execute(['company_id' => $companyId]);
            $produtos = $stmt->fetchAll();

            // Busca locais de estoque
            $stmt = $this->db->prepare("
                SELECT id, name FROM locais_estoque
                WHERE company_id = :company_id AND is_active = 1
                ORDER BY name ASC
            ");
            $stmt->execute(['company_id' => $companyId]);
            $locais = $stmt->fetchAll();

            $this->view('estoque/edit', [
                'movimento' => $movimento,
                'produtos' => $produtos,
                'locais' => $locais,
                'pageTitle' => 'Editar Movimentação',
                'activeMenu' => 'estoque'
            ]);

        } catch (Exception $e) {
            error_log("Erro ao editar movimentação: " . $e->getMessage());
            $this->error('Erro ao carregar formulário');
        }
    }

    public function update(): void
    {
        // Verificar permissão de edição
        if (!$this->canEdit('estoque')) {
            $this->response->forbidden('Você não tem permissão para editar movimentações de estoque.');
            return;
        }

        try {
            $id = (int) $this->request->post('id');
            $movimento = $this->getMovimento($id);

            if (!$movimento) {
                $this->error('Movimentação não encontrada');
                return;
            }

            $data = [
                'product_id' => $this->request->post('product_id'),
                'location_id' => $this->request->post('location_id') ?: null,
                'type' => $this->request->post('type'),
                'quantity' => $this->request->post('quantity'),
                'reason' => $this->request->post('reason'),
                'reference_id' => $this->request->post('reference_id') ?: null,
                'reference_type' => $this->request->post('reference_type') ?: null,
                'id' => $id,
                'company_id' => $this->getCompanyId()
            ];

            $this->db->beginTransaction();

            $stmt = $this->db->prepare("
                UPDATE estoque_movimentos SET
                    product_id = :product_id,
                    location_id = :location_id,
                    type = :type,
                    quantity = :quantity,
                    reason = :reason,
                    reference_id = :reference_id,
                    reference_type = :reference_type
                WHERE id = :id AND company_id = :company_id
            ");
            $stmt->execute($data);

            $this->logActivity('update', 'estoque_movimentos', $id, $data);
            $this->db->commit();

            $this->success('Movimentação atualizada com sucesso', [
                'redirect' => '/estoque'
            ]);

        } catch (Exception $e) {
            $this->db->rollBack();
            error_log("Erro ao atualizar movimentação: " . $e->getMessage());
            $this->error('Erro ao atualizar movimentação');
        }
    }

    public function delete(): void
    {
        // Verificar permissão de exclusão
        if (!$this->canDelete('estoque')) {
            $this->response->forbidden('Você não tem permissão para excluir movimentações de estoque.');
            return;
        }

        try {
            $id = (int) $this->request->post('id');
            $movimento = $this->getMovimento($id);

            if (!$movimento) {
                $this->error('Movimentação não encontrada');
                return;
            }

            $this->db->beginTransaction();

            $stmt = $this->db->prepare("DELETE FROM estoque_movimentos WHERE id = :id AND company_id = :company_id");
            $stmt->execute(['id' => $id, 'company_id' => $this->getCompanyId()]);

            $this->logActivity('delete', 'estoque_movimentos', $id, $movimento);
            $this->db->commit();

            $this->success('Movimentação excluída com sucesso');

        } catch (Exception $e) {
            $this->db->rollBack();
            error_log("Erro ao excluir movimentação: " . $e->getMessage());
            $this->error('Erro ao excluir movimentação');
        }
    }

    private function getMovimento(int $id): ?array
    {
        $stmt = $this->db->prepare("SELECT * FROM estoque_movimentos WHERE id = :id AND company_id = :company_id");
        $stmt->execute(['id' => $id, 'company_id' => $this->getCompanyId()]);
        return $stmt->fetch() ?: null;
    }

    private function updateProductStock(int $productId, string $type, float $quantity): void
    {
        $operator = $type === 'entrada' ? '+' : '-';

        $stmt = $this->db->prepare("
            UPDATE produtos
            SET stock_quantity = stock_quantity {$operator} :quantity,
                updated_at = NOW()
            WHERE id = :product_id AND company_id = :company_id
        ");
        $stmt->execute([
            'quantity' => $quantity,
            'product_id' => $productId,
            'company_id' => $this->getCompanyId()
        ]);
    }

    private function tabelaExiste(string $nome): bool
    {
        $nome = preg_replace('/[^a-zA-Z0-9_]/', '', $nome);

        try {
            $stmt = $this->db->query("SHOW TABLES LIKE '{$nome}'");
            return (bool) $stmt->fetchColumn();
        } catch (Exception $e) {
            error_log('Inventário: falha ao verificar existência da tabela "' . $nome . '": ' . $e->getMessage());
            return false;
        }
    }

    private function colunasTabela(string $tabela): array
    {
        static $cache = [];

        if (isset($cache[$tabela])) {
            return $cache[$tabela];
        }

        try {
            $stmt = $this->db->query("SHOW COLUMNS FROM {$tabela}");
            $cache[$tabela] = $stmt->fetchAll(\PDO::FETCH_COLUMN) ?: [];
        } catch (Exception $e) {
            error_log('Inventário: falha ao obter colunas de "' . $tabela . '": ' . $e->getMessage());
            $cache[$tabela] = [];
        }

        return $cache[$tabela];
    }

    private function converterDataFiltro(?string $valor, string $horaPadrao = '00:00:00'): ?string
    {
        if ($valor === null) {
            return null;
        }

        $valor = trim($valor);
        if ($valor === '') {
            return null;
        }

        $formatos = ['Y-m-d', 'd/m/Y'];
        foreach ($formatos as $formato) {
            $data = \DateTime::createFromFormat($formato, $valor);
            if ($data instanceof \DateTime) {
                [$hora, $minuto, $segundo] = array_map('intval', explode(':', $horaPadrao));
                $data->setTime($hora, $minuto, $segundo);
                return $data->format('Y-m-d H:i:s');
            }
        }

        return null;
    }

    private function getGruposItens(int $companyId): array
    {
        if (!$this->tabelaExiste('grupos_pessoas')) {
            return [];
        }

        try {
            $stmt = $this->db->prepare("SELECT id, nome, subtipo, descricao FROM grupos_pessoas WHERE company_id = :company_id AND tipo = 'itens' AND ativo = 1 ORDER BY ordem ASC, nome ASC");
            $stmt->execute(['company_id' => $companyId]);
            return $stmt->fetchAll() ?: [];
        } catch (Exception $e) {
            error_log('Inventário: falha ao carregar grupos de itens: ' . $e->getMessage());
            return [];
        }
    }

    private function getSubgruposItens(int $companyId): array
    {
        if (!$this->tabelaExiste('subgrupos_pessoas')) {
            return [];
        }

        try {
            $stmt = $this->db->prepare("SELECT sp.id, sp.nome, sp.grupo_id, gp.nome AS grupo_nome FROM subgrupos_pessoas sp INNER JOIN grupos_pessoas gp ON sp.grupo_id = gp.id WHERE sp.company_id = :company_id AND gp.tipo = 'itens' AND sp.ativo = 1 ORDER BY sp.ordem ASC, sp.nome ASC");
            $stmt->execute(['company_id' => $companyId]);
            return $stmt->fetchAll() ?: [];
        } catch (Exception $e) {
            error_log('Inventário: falha ao carregar subgrupos de itens: ' . $e->getMessage());
            return [];
        }
    }

    private function getFornecedores(int $companyId): array
    {
        if (!$this->tabelaExiste('pessoas')) {
            return [];
        }

        // Primeiro tenta coluna tipo_fornecedor; se não existir, usa fallback com type
        try {
            $stmt = $this->db->prepare("SELECT id, name, trade_name, document FROM pessoas WHERE company_id = :company_id AND tipo_fornecedor = 1 AND is_active = 1 ORDER BY name ASC");
            $stmt->execute(['company_id' => $companyId]);
            $resultado = $stmt->fetchAll();
            if (!empty($resultado)) {
                return $resultado;
            }
        } catch (Exception $e) {
            // Ignora e tenta fallback
        }

        try {
            $stmt = $this->db->prepare("SELECT id, name, trade_name, document FROM pessoas WHERE company_id = :company_id AND type IN ('fornecedor', 'ambos') AND is_active = 1 ORDER BY name ASC");
            $stmt->execute(['company_id' => $companyId]);
            return $stmt->fetchAll() ?: [];
        } catch (Exception $e) {
            error_log('Inventário: falha ao carregar fornecedores: ' . $e->getMessage());
            return [];
        }
    }

    private function deveExibirCampoLote(int $companyId): bool
    {
        try {
            $stmt = $this->db->prepare("
                SELECT valor
                FROM parametros
                WHERE empresa_id = :empresa_id
                  AND chave = 'usar_lote_estoque'
                LIMIT 1
            ");
            $stmt->execute(['empresa_id' => $companyId]);
            $valorParametro = $stmt->fetchColumn();
            return ($valorParametro !== false && (int) $valorParametro === 1);
        } catch (Exception $e) {
            error_log('Erro ao verificar parâmetro usar_lote_estoque (inventário): ' . $e->getMessage());
            return true;
        }
    }

    private function registrarInventario(array $ajustes, ?int $locationId, string $notes = '', ?int $companyId = null): array
    {
        if (empty($ajustes)) {
            throw new InvalidArgumentException('Nenhum ajuste foi informado.');
        }

        $companyId = $companyId ?? $this->getCompanyId();
        if (!$companyId) {
            throw new RuntimeException('Empresa não identificada para o inventário.');
        }

        $totalAjustes = 0;
        $inventarioId = null;
        $colunasInventarios = $this->tabelaExiste('inventarios') ? $this->colunasTabela('inventarios') : [];
        $colunasInventarioItens = $this->tabelaExiste('inventario_itens') ? $this->colunasTabela('inventario_itens') : [];
        $userId = $this->getUserId();

        try {
            $this->db->beginTransaction();

            // CRIAR REGISTRO EM INVENTARIOS - SEMPRE COM company_id DA COLUNA A
            if (!empty($colunasInventarios)) {
                $dadosInventario = [];

                if (in_array('company_id', $colunasInventarios, true)) {
                    $dadosInventario['company_id'] = $companyId;
                }
                if (in_array('local_estoque_id', $colunasInventarios, true) && $locationId) {
                    $dadosInventario['local_estoque_id'] = $locationId;
                }
                if (in_array('date', $colunasInventarios, true)) {
                    $dadosInventario['date'] = date('Y-m-d');
                }
                if (in_array('status', $colunasInventarios, true)) {
                    $dadosInventario['status'] = 'finalizado';
                }
                if ($notes !== '' && in_array('notes', $colunasInventarios, true)) {
                    $dadosInventario['notes'] = $notes;
                }
                if ($userId && in_array('created_by', $colunasInventarios, true)) {
                    $dadosInventario['created_by'] = $userId;
                }

                if (!empty($dadosInventario)) {
                    $colunas = array_keys($dadosInventario);
                    $placeholders = array_map(static function ($coluna) {
                        return ':' . $coluna;
                    }, $colunas);

                    $sql = sprintf(
                        'INSERT INTO inventarios (%s) VALUES (%s)',
                        implode(', ', $colunas),
                        implode(', ', $placeholders)
                    );

                    $stmt = $this->db->prepare($sql);
                    $stmt->execute($dadosInventario);
                    $inventarioId = (int) $this->db->lastInsertId();
                }
            }

            $itensParaRegistrar = [];

            foreach ($ajustes as $ajuste) {
                $productId = (int) ($ajuste['product_id'] ?? 0);
                if ($productId <= 0) {
                    continue;
                }

                $quantidadeSistema = $this->normalizarNumero($ajuste['quantidade_sistema'] ?? 0);
                $quantidadeFisicaBruta = $ajuste['quantidade_fisica'] ?? null;

                if ($quantidadeFisicaBruta === '' || $quantidadeFisicaBruta === null) {
                    continue;
                }

                $quantidadeFisica = $this->normalizarNumero($quantidadeFisicaBruta);
                $diferenca = $quantidadeFisica - $quantidadeSistema;

                if ($diferenca != 0.0) {
                    $tipo = $diferenca > 0 ? 'entrada' : 'saida';
                    $quantidadeAbs = abs($diferenca);

                    // Verificar quais colunas existem na tabela
                    $colunasMovimentos = $this->colunasTabela('estoque_movimentos');
                    $camposInsert = ['company_id', 'product_id', 'type', 'quantity', 'reason', 'reference_type', 'created_at'];
                    $valoresInsert = [
                        'company_id' => $companyId,
                        'product_id' => $productId,
                        'type' => $tipo,
                        'quantity' => $quantidadeAbs,
                        'reason' => 'Ajuste de inventário - Contagem física',
                        'reference_type' => 'inventario'
                    ];

                    if (in_array('location_id', $colunasMovimentos, true) && $locationId) {
                        $camposInsert[] = 'location_id';
                        $valoresInsert['location_id'] = $locationId;
                    }
                    if (in_array('reference_id', $colunasMovimentos, true) && $inventarioId) {
                        $camposInsert[] = 'reference_id';
                        $valoresInsert['reference_id'] = $inventarioId;
                    }
                    if (in_array('numero_serie', $colunasMovimentos, true) && !empty($ajuste['numero_serie'])) {
                        $camposInsert[] = 'numero_serie';
                        $valoresInsert['numero_serie'] = $ajuste['numero_serie'];
                    }
                    if (in_array('lote', $colunasMovimentos, true) && !empty($ajuste['lote'])) {
                        $camposInsert[] = 'lote';
                        $valoresInsert['lote'] = $ajuste['lote'];
                    }
                    if (in_array('fabricacao', $colunasMovimentos, true) && !empty($ajuste['fabricacao'])) {
                        $camposInsert[] = 'fabricacao';
                        $valoresInsert['fabricacao'] = $ajuste['fabricacao'];
                    }
                    if (in_array('validade', $colunasMovimentos, true) && !empty($ajuste['validade'])) {
                        $camposInsert[] = 'validade';
                        $valoresInsert['validade'] = $ajuste['validade'];
                    }

                    $placeholders = array_map(function ($campo) {
                        return $campo === 'created_at' ? 'NOW()' : ':' . $campo;
                    }, $camposInsert);

                    $sql = sprintf(
                        "INSERT INTO estoque_movimentos (%s) VALUES (%s)",
                        implode(', ', $camposInsert),
                        implode(', ', $placeholders)
                    );

                    $stmt = $this->db->prepare($sql);
                    $stmt->execute($valoresInsert);

                    $totalAjustes++;
                }

                if ($inventarioId && !empty($colunasInventarioItens)) {
                    $itensParaRegistrar[] = [
                        'inventario_id' => $inventarioId,
                        'item_id' => $productId,
                        'system_quantity' => $quantidadeSistema,
                        'counted_quantity' => $quantidadeFisica,
                        'difference' => $diferenca,
                        'lote' => $ajuste['lote'] ?? null,
                        'fabricacao' => $ajuste['fabricacao'] ?? null,
                        'validade' => $ajuste['validade'] ?? null,
                        'numero_serie' => $ajuste['numero_serie'] ?? null,
                        'company_id' => $companyId,
                        'created_by' => $userId,
                        'notes' => $ajuste['notes'] ?? null,
                    ];
                }
            }

            if ($inventarioId && !empty($itensParaRegistrar) && !empty($colunasInventarioItens)) {
                foreach ($itensParaRegistrar as $item) {
                    $dadosItem = [];
                    if (in_array('inventario_id', $colunasInventarioItens, true)) {
                        $dadosItem['inventario_id'] = $item['inventario_id'];
                    }
                    if (in_array('item_id', $colunasInventarioItens, true)) {
                        $dadosItem['item_id'] = $item['item_id'];
                    }
                    if (in_array('system_quantity', $colunasInventarioItens, true)) {
                        $dadosItem['system_quantity'] = $item['system_quantity'];
                    }
                    if (in_array('counted_quantity', $colunasInventarioItens, true)) {
                        $dadosItem['counted_quantity'] = $item['counted_quantity'];
                    }
                    if (in_array('difference', $colunasInventarioItens, true)) {
                        $dadosItem['difference'] = $item['difference'];
                    }
                    if (in_array('lote', $colunasInventarioItens, true) && !empty($item['lote'])) {
                        $dadosItem['lote'] = $item['lote'];
                    }
                    if (in_array('fabricacao', $colunasInventarioItens, true) && !empty($item['fabricacao'])) {
                        $dadosItem['fabricacao'] = $item['fabricacao'];
                    }
                    if (in_array('validade', $colunasInventarioItens, true) && !empty($item['validade'])) {
                        $dadosItem['validade'] = $item['validade'];
                    }
                    if (in_array('numero_serie', $colunasInventarioItens, true) && !empty($item['numero_serie'])) {
                        $dadosItem['numero_serie'] = $item['numero_serie'];
                    }
                    if (in_array('company_id', $colunasInventarioItens, true)) {
                        $dadosItem['company_id'] = $item['company_id'];
                    }
                    if (in_array('created_by', $colunasInventarioItens, true) && !empty($item['created_by'])) {
                        $dadosItem['created_by'] = $item['created_by'];
                    }
                    if (in_array('notes', $colunasInventarioItens, true) && !empty($item['notes'])) {
                        $dadosItem['notes'] = $item['notes'];
                    }

                    if (!empty($dadosItem)) {
                        $colunas = array_keys($dadosItem);
                        $placeholders = array_map(static function ($coluna) {
                            return ':' . $coluna;
                        }, $colunas);

                        $sql = sprintf(
                            'INSERT INTO inventario_itens (%s) VALUES (%s)',
                            implode(', ', $colunas),
                            implode(', ', $placeholders)
                        );

                        $stmt = $this->db->prepare($sql);
                        $stmt->execute($dadosItem);
                    }
                }
            }

            $this->db->commit();
        } catch (Exception $e) {
            $this->db->rollBack();
            throw $e;
        }

        $codigoInventario = $inventarioId ? ' Inventário #' . str_pad((string) $inventarioId, 5, '0', STR_PAD_LEFT) . '.' : '';
        $mensagemSucesso = "Inventário realizado com sucesso! {$totalAjustes} ajuste(s) registrado(s)." . $codigoInventario;

        return [
            'inventarioId' => $inventarioId,
            'totalAjustes' => $totalAjustes,
            'mensagem' => $mensagemSucesso,
        ];
    }

    /**
     * Retorna a lista de produtos ativos com estoque atual para a empresa
     */
    private function obterProdutosParaInventario(int $companyId): array
    {
        $stmt = $this->db->prepare("
            SELECT
                p.id,
                p.sku,
                p.name,
                COALESCE(SUM(CASE WHEN em.type = 'entrada' THEN em.quantity ELSE -em.quantity END), 0) AS estoque_sistema
            FROM produtos p
            LEFT JOIN estoque_movimentos em ON em.product_id = p.id AND em.company_id = p.company_id
            WHERE p.company_id = :company_id
              AND p.is_active = 1
            GROUP BY p.id
            ORDER BY p.name ASC
        ");
        $stmt->execute(['company_id' => $companyId]);

        return array_map(static function ($item) {
            return [
                'id' => (int) ($item['id'] ?? 0),
                'sku' => (string) ($item['sku'] ?? ''),
                'name' => (string) ($item['name'] ?? ''),
                'estoque_sistema' => (float) ($item['estoque_sistema'] ?? 0),
            ];
        }, $stmt->fetchAll() ?: []);
    }

    /**
     * Mapeia o cabeçalho da planilha para os campos esperados
     */
    private function mapearCabecalhoPlanilha($sheet, int $highestColumnIndex): array
    {
        $aliases = [
            'product_id' => ['produto_id', 'product_id', 'id_produto', 'codigo_produto'],
            'sku' => ['produto_sku', 'sku', 'codigo_sku'],
            'quantidade_sistema' => ['quantidade_sistema', 'estoque_atual', 'saldo_atual'],
            'quantidade_fisica' => ['quantidade_fisica', 'quantidade_contada', 'contagem', 'qtd_contada'],
            'lote' => ['lote'],
            'numero_serie' => ['numero_serie', 'serial', 'numero_serial'],
            'fabricacao' => ['fabricacao', 'data_fabricacao'],
            'validade' => ['validade', 'data_validade', 'vencimento'],
            'notes' => ['observacao', 'observacoes', 'notes', 'comentarios'],
        ];

        $mapeamento = [];

        for ($col = 1; $col <= $highestColumnIndex; $col++) {
            $valor = $sheet->getCellByColumnAndRow($col, 1)->getValue();
            if ($valor === null) {
                continue;
            }

            $valorNormalizado = strtolower(trim((string) $valor));
            if ($valorNormalizado === '') {
                continue;
            }

            foreach ($aliases as $campo => $listaAliases) {
                if (in_array($valorNormalizado, $listaAliases, true)) {
                    $mapeamento[$campo] = $col;
                    break;
                }
            }
        }

        return $mapeamento;
    }

    /**
     * Extrai dados de uma linha da planilha com base no cabeçalho mapeado
     */
    private function extrairDadosLinhaPlanilha($sheet, int $row, int $highestColumnIndex, array $cabecalhoMapeado): ?array
    {
        $linhaVazia = true;
        $dados = [
            'product_id' => null,
            'sku' => null,
            'quantidade_sistema' => null,
            'quantidade_fisica' => null,
            'lote' => null,
            'numero_serie' => null,
            'fabricacao' => null,
            'validade' => null,
            'notes' => null,
        ];

        foreach ($cabecalhoMapeado as $campo => $coluna) {
            if ($coluna > $highestColumnIndex) {
                continue;
            }

            $valor = $sheet->getCellByColumnAndRow($coluna, $row)->getValue();
            if ($valor !== null && $valor !== '') {
                $linhaVazia = false;
            }

            switch ($campo) {
                case 'product_id':
                    $dados['product_id'] = $valor !== null && $valor !== '' ? (int) $valor : null;
                    break;
                case 'sku':
                    $dados['sku'] = $valor !== null ? trim((string) $valor) : null;
                    break;
                case 'quantidade_sistema':
                    $dados['quantidade_sistema'] = ($valor === null || $valor === '') ? null : $this->normalizarNumero($valor);
                    break;
                case 'quantidade_fisica':
                    $dados['quantidade_fisica'] = ($valor === null || $valor === '') ? null : $this->normalizarNumero($valor);
                    break;
                case 'lote':
                    $dados['lote'] = $valor !== null ? trim((string) $valor) : null;
                    break;
                case 'numero_serie':
                    $dados['numero_serie'] = $valor !== null ? trim((string) $valor) : null;
                    break;
                case 'fabricacao':
                    $dados['fabricacao'] = $this->normalizarDataPlanilha($valor);
                    break;
                case 'validade':
                    $dados['validade'] = $this->normalizarDataPlanilha($valor);
                    break;
                case 'notes':
                    $dados['notes'] = $valor !== null ? trim((string) $valor) : null;
                    break;
            }
        }

        return $linhaVazia ? null : $dados;
    }

    /**
     * Localiza o produto com base nas informações da linha
     */
    private function resolverProdutoPorLinha(int $companyId, array $dadosLinha): ?array
    {
        if (!empty($dadosLinha['product_id'])) {
            $stmt = $this->db->prepare("SELECT id, sku, name FROM produtos WHERE id = :id AND company_id = :company_id LIMIT 1");
            $stmt->execute([
                'id' => (int) $dadosLinha['product_id'],
                'company_id' => $companyId,
            ]);
            $produto = $stmt->fetch();
            if ($produto) {
                return [
                    'id' => (int) $produto['id'],
                    'sku' => $produto['sku'] ?? null,
                    'name' => $produto['name'] ?? null,
                ];
            }
        }

        if (!empty($dadosLinha['sku'])) {
            $stmt = $this->db->prepare("SELECT id, sku, name FROM produtos WHERE sku = :sku AND company_id = :company_id LIMIT 1");
            $stmt->execute([
                'sku' => $dadosLinha['sku'],
                'company_id' => $companyId,
            ]);
            $produto = $stmt->fetch();
            if ($produto) {
                return [
                    'id' => (int) $produto['id'],
                    'sku' => $produto['sku'] ?? null,
                    'name' => $produto['name'] ?? null,
                ];
            }
        }

        return null;
    }

    /**
     * Recupera o estoque atual de um produto
     */
    private function obterEstoqueAtualProduto(int $productId, int $companyId): float
    {
        $stmt = $this->db->prepare("
            SELECT COALESCE(SUM(CASE WHEN type = 'entrada' THEN quantity ELSE -quantity END), 0) AS estoque
            FROM estoque_movimentos
            WHERE product_id = :product_id AND company_id = :company_id
        ");
        $stmt->execute([
            'product_id' => $productId,
            'company_id' => $companyId,
        ]);
        $estoque = $stmt->fetchColumn();

        return (float) ($estoque ?? 0);
    }

    /**
     * Normaliza valores numéricos provenientes da planilha
     */
    private function normalizarNumero($valor): float
    {
        if ($valor instanceof \PhpOffice\PhpSpreadsheet\RichText\RichText) {
            $valor = $valor->getPlainText();
        }

        if ($valor === null || $valor === '') {
            return 0.0;
        }

        if (is_string($valor)) {
            $valor = trim(str_replace(["\u{00A0}", ' '], '', $valor)); // remove espaços e nbsp
            $valor = str_replace(['.', ','], ['', '.'], $valor, $count);
            if ($count === 0) {
                $valor = str_replace(',', '.', $valor);
            }
        }

        return (float) $valor;
    }

    /**
     * Normaliza datas vindas da planilha para o formato Y-m-d
     */
    private function normalizarDataPlanilha($valor): ?string
    {
        if ($valor === null || $valor === '') {
            return null;
        }

        if ($valor instanceof \DateTimeInterface) {
            return $valor->format('Y-m-d');
        }

        if (is_numeric($valor)) {
            try {
                $dateTime = SpreadsheetDate::excelToDateTimeObject((float) $valor);
                return $dateTime->format('Y-m-d');
            } catch (Exception $e) {
                return null;
            }
        }

        $valor = trim((string) $valor);
        if ($valor === '') {
            return null;
        }

        $timestamp = strtotime(str_replace('/', '-', $valor));
        return $timestamp ? date('Y-m-d', $timestamp) : null;
    }
}