<?php
namespace Modules\Vendas\Controllers;

use Gestoreasy\Core\Http\Request;
use Gestoreasy\Core\Http\Response;
use Gestoreasy\Core\Tenant\ConnectionManager;
use Gestoreasy\Core\Audit\AuditLogger;
use Gestoreasy\Core\Auth\UserContext;
use Gestoreasy\Core\Mail\Mailer;

class SalesQuotesController {
    public function list(Request $r): Response {
        $pdo = ConnectionManager::forTenant();
        $page = max(1, (int)($r->query['page'] ?? 1));
        // Suportar tanto per_page quanto limit vindos da view
        $per = (int)($r->query['limit'] ?? $r->query['per_page'] ?? 20);
        $per = min(100, max(1, $per));
        $offset = ($page - 1) * $per;

        // Filtros
        $search = trim((string)($r->query['search'] ?? ''));
        $status = trim((string)($r->query['status'] ?? ''));

        $where = [];
        $params = [];
        if ($search !== '') {
            $where[] = '(numero LIKE ? OR CAST(cliente_id AS CHAR) LIKE ?)';
            $params[] = '%'.$search.'%';
            $params[] = '%'.$search.'%';
        }
        if ($status !== '') {
            $where[] = 'status = ?';
            $params[] = $status;
        }
        $whereSql = count($where) > 0 ? ('WHERE '.implode(' AND ', $where)) : '';

        $sql = "SELECT id, numero, cliente_id, valor_total, status, validade, created_at
                FROM sales_quotes
                $whereSql
                ORDER BY id DESC
                LIMIT $per OFFSET $offset";
        $stmt = $pdo->prepare($sql);
        $stmt->execute($params);
        $rows = $stmt->fetchAll();

        // Total para paginação com mesmos filtros
        $countSql = "SELECT COUNT(*) AS c FROM sales_quotes $whereSql";
        $cStmt = $pdo->prepare($countSql);
        $cStmt->execute($params);
        $total = (int)($cStmt->fetch()['c'] ?? 0);
        $totalPages = (int)ceil($total / $per);

        AuditLogger::logRead('sales_quotes', null, ['endpoint' => 'vendas.orcamentos.list', 'page' => $page, 'per_page' => $per, 'search' => $search, 'status' => $status]);
        return Response::json(['data' => $rows, 'pagination' => ['total' => $total, 'page' => $page, 'pages' => $totalPages, 'per_page' => $per]]);
    }

    public function getById(Request $r): Response {
        $pdo = ConnectionManager::forTenant();
        $params = $r->get('params', []);
        $id = (int)($params[0] ?? 0);
        if ($id <= 0) return new Response(422, ['error' => ['code' => 'invalid_id', 'message' => 'ID inválido']]);
        $stmt = $pdo->prepare('SELECT id, numero, cliente_id, valor_total, status, validade, created_at FROM sales_quotes WHERE id = ? LIMIT 1');
        $stmt->execute([$id]);
        $row = $stmt->fetch();
        if (!$row) return new Response(404, ['error' => ['code' => 'not_found', 'message' => 'Orçamento não encontrado']]);
        $items = [];
        try {
            $it = $pdo->prepare('SELECT product_id, quantidade, preco, total FROM sales_quote_items WHERE quote_id = ? ORDER BY id ASC');
            $it->execute([$id]);
            $items = $it->fetchAll();
        } catch (\Throwable $e) {}
        AuditLogger::logRead('sales_quotes', $id, ['endpoint' => 'vendas.orcamentos.get_by_id']);
        return Response::json(['data' => ['quote' => $row, 'items' => $items]]);
    }

    public function create(Request $r): Response {
        $pdo = ConnectionManager::forTenant();
        $data = $r->body;
        $clienteId = (int)($data['cliente_id'] ?? 0);
        $valorTotal = (float)($data['valor_total'] ?? 0);
        $validade = isset($data['validade']) ? (string)$data['validade'] : null;
        $items = is_array($data['items'] ?? null) ? $data['items'] : [];
        // Ajustes globais e frete vindos da view
        $descGlobal = (float)($data['pagamento']['desconto_percent'] ?? 0);
        $acrescGlobal = (float)($data['pagamento']['acrescimo_percent'] ?? 0);
        $freteValor = (float)($data['entrega']['frete'] ?? 0);
        $freteSomar = (bool)($data['entrega']['frete_incluir_total'] ?? false);
        if ($clienteId <= 0) return new Response(422, ['error' => ['code' => 'cliente_id_required', 'message' => 'cliente_id é obrigatório']]);
        // Validar cliente
        $cst = $pdo->prepare('SELECT id FROM clients WHERE id = ? LIMIT 1');
        $cst->execute([$clienteId]);
        if (!$cst->fetch()) return new Response(404, ['error' => ['code' => 'client_not_found', 'message' => 'Cliente não encontrado']]);

        $summary = [];
        if (count($items) > 0) {
            $valorTotal = 0.0;
            foreach ($items as $it) {
                $pid = (int)($it['product_id'] ?? 0);
                $qtd = (float)($it['quantidade'] ?? 0);
                if ($pid <= 0 || $qtd <= 0) return new Response(422, ['error' => ['code' => 'invalid_item', 'message' => 'Item inválido: product_id e quantidade > 0 requeridos']]);
                $st = $pdo->prepare('SELECT id, preco FROM products WHERE id = ? LIMIT 1');
                $st->execute([$pid]);
                $prod = $st->fetch();
                if (!$prod) return new Response(404, ['error' => ['code' => 'product_not_found', 'message' => 'Produto não encontrado']]);
                $precoOverride = isset($it['preco']) ? (float)$it['preco'] : null;
                $unit = ($precoOverride !== null && $precoOverride > 0) ? $precoOverride : (float)$prod['preco'];
                $descItemPct = (float)($it['desconto_percent'] ?? 0);
                $unitAdj = $unit * (1 - ($descItemPct/100.0));
                $linhaTotal = round($qtd * $unitAdj, 2);
                $valorTotal += $linhaTotal;
                $summary[] = ['product_id' => $pid, 'quantidade' => $qtd, 'preco' => $unit, 'total' => $linhaTotal];
            }
            // Aplicar ajustes globais e frete
            $valorTotal = round($valorTotal * (1 - ($descGlobal/100.0)), 2);
            $valorTotal = round($valorTotal * (1 + ($acrescGlobal/100.0)), 2);
            if ($freteSomar) { $valorTotal = round($valorTotal + $freteValor, 2); }
        } else {
            if ($valorTotal <= 0) return new Response(422, ['error' => ['code' => 'invalid_total', 'message' => 'valor_total deve ser > 0 quando itens não forem informados']]);
        }

        // Numeração automática simplificada: OR- + sequência numérica 9 dígitos crescente
        try {
            $stmtSeq = $pdo->query("SELECT MAX(CAST(SUBSTRING(numero, 4) AS UNSIGNED)) AS max_seq FROM sales_quotes WHERE numero LIKE 'OR-%'");
            $rowSeq = $stmtSeq->fetch();
            $next = ((int)($rowSeq['max_seq'] ?? 0)) + 1;
            $numero = 'OR-' . str_pad((string)$next, 9, '0', STR_PAD_LEFT);
        } catch (\Throwable $e) {
            // Fallback em caso de erro: usar ID após inserção
            $numero = null;
        }
        if ($numero === null) {
            // Inserir provisoriamente e depois atualizar com OR-<id>
            $ins = $pdo->prepare('INSERT INTO sales_quotes (numero, cliente_id, valor_total, status, validade) VALUES (?,?,?,?,?)');
            $ins->execute(['', $clienteId, $valorTotal, 'rascunho', $validade]);
            $newId = (int)$pdo->lastInsertId();
            $numero = 'OR-' . str_pad((string)$newId, 9, '0', STR_PAD_LEFT);
            $up = $pdo->prepare('UPDATE sales_quotes SET numero = ? WHERE id = ?');
            $up->execute([$numero, $newId]);
        } else {
            $ins = $pdo->prepare('INSERT INTO sales_quotes (numero, cliente_id, valor_total, status, validade) VALUES (?,?,?,?,?)');
            $ins->execute([$numero, $clienteId, $valorTotal, 'rascunho', $validade]);
            $newId = (int)$pdo->lastInsertId();
        }

        if (count($summary) > 0) {
            try {
                $itIns = $pdo->prepare('INSERT INTO sales_quote_items (quote_id, product_id, quantidade, preco, total) VALUES (?,?,?,?,?)');
                foreach ($summary as $s) {
                    $itIns->execute([$newId, (int)$s['product_id'], (float)$s['quantidade'], (float)$s['preco'], (float)$s['total']]);
                }
            } catch (\Throwable $e) {}
        }

        AuditLogger::logCreate('sales_quotes', $newId, ['numero' => $numero, 'cliente_id' => $clienteId, 'valor_total' => $valorTotal, 'status' => 'rascunho', 'validade' => $validade]);
        return Response::json(['ok' => true, 'id' => $newId, 'numero' => $numero], 201);
    }

    public function update(Request $r): Response {
        $pdo = ConnectionManager::forTenant();
        $params = $r->get('params', []);
        $id = (int)($params[0] ?? 0);
        if ($id <= 0) return new Response(422, ['error' => ['code' => 'invalid_id', 'message' => 'ID inválido']]);
        $data = $r->body;
        $clienteId = isset($data['cliente_id']) ? (int)$data['cliente_id'] : null;
        $valorTotal = isset($data['valor_total']) ? (float)$data['valor_total'] : null;
        $status = $data['status'] ?? null;
        $validade = $data['validade'] ?? null;
        $items = is_array($data['items'] ?? null) ? $data['items'] : [];
        // Ajustes globais e frete vindos da view
        $descGlobal = (float)($data['pagamento']['desconto_percent'] ?? 0);
        $acrescGlobal = (float)($data['pagamento']['acrescimo_percent'] ?? 0);
        $freteValor = (float)($data['entrega']['frete'] ?? 0);
        $freteSomar = (bool)($data['entrega']['frete_incluir_total'] ?? false);
        $beforeStmt = $pdo->prepare('SELECT id, numero, cliente_id, valor_total, status, validade FROM sales_quotes WHERE id = ? LIMIT 1');
        $beforeStmt->execute([$id]);
        $before = $beforeStmt->fetch();
        if (!$before) return new Response(404, ['error' => ['code' => 'not_found', 'message' => 'Orçamento não encontrado']]);
        try {
            $pdo->beginTransaction();
            // Se itens foram enviados, recalcular total e substituir itens
            if (count($items) > 0) {
                $valorTotalCalc = 0.0;
                $summary = [];
                foreach ($items as $it) {
                    $pid = (int)($it['product_id'] ?? 0);
                    $qtd = (float)($it['quantidade'] ?? 0);
                    if ($pid <= 0 || $qtd <= 0) {
                        if ($pdo->inTransaction()) { $pdo->rollBack(); }
                        return new Response(422, ['error' => ['code' => 'invalid_item', 'message' => 'Item inválido: product_id e quantidade > 0 requeridos']]);
                    }
                    $st = $pdo->prepare('SELECT id, preco FROM products WHERE id = ? LIMIT 1');
                    $st->execute([$pid]);
                    $prod = $st->fetch();
                    if (!$prod) {
                        if ($pdo->inTransaction()) { $pdo->rollBack(); }
                        return new Response(404, ['error' => ['code' => 'product_not_found', 'message' => 'Produto não encontrado']]);
                    }
                    $precoOverride = isset($it['preco']) ? (float)$it['preco'] : null;
                    $unit = ($precoOverride !== null && $precoOverride > 0) ? $precoOverride : (float)$prod['preco'];
                    $descItemPct = (float)($it['desconto_percent'] ?? 0);
                    $unitAdj = $unit * (1 - ($descItemPct/100.0));
                    $linhaTotal = round($qtd * $unitAdj, 2);
                    $valorTotalCalc += $linhaTotal;
                    $summary[] = ['product_id' => $pid, 'quantidade' => $qtd, 'preco' => $unit, 'total' => $linhaTotal];
                }
                // Substituir itens
                try {
                    $pdo->prepare('DELETE FROM sales_quote_items WHERE quote_id = ?')->execute([$id]);
                } catch (\Throwable $e) {}
                try {
                    $itIns = $pdo->prepare('INSERT INTO sales_quote_items (quote_id, product_id, quantidade, preco, total) VALUES (?,?,?,?,?)');
                    foreach ($summary as $s) {
                        $itIns->execute([$id, (int)$s['product_id'], (float)$s['quantidade'], (float)$s['preco'], (float)$s['total']]);
                    }
                } catch (\Throwable $e) {}
                // Aplicar ajustes globais e frete
                $valorTotalCalc = round($valorTotalCalc * (1 - ($descGlobal/100.0)), 2);
                $valorTotalCalc = round($valorTotalCalc * (1 + ($acrescGlobal/100.0)), 2);
                if ($freteSomar) { $valorTotalCalc = round($valorTotalCalc + $freteValor, 2); }
                // Se valorTotal não foi informado, usar calculado
                if ($valorTotal === null) { $valorTotal = $valorTotalCalc; }
            }

            $stmt = $pdo->prepare('UPDATE sales_quotes SET cliente_id = COALESCE(?, cliente_id), valor_total = COALESCE(?, valor_total), status = COALESCE(?, status), validade = COALESCE(?, validade) WHERE id = ?');
            $stmt->execute([$clienteId, $valorTotal, $status, $validade, $id]);
            if ($pdo->inTransaction()) { $pdo->commit(); }
        } catch (\Throwable $e) {
            if ($pdo->inTransaction()) { $pdo->rollBack(); }
            return new Response(500, ['error' => ['code' => 'update_failed', 'message' => $e->getMessage()]]);
        }
        $after = $before;
        if ($clienteId !== null) $after['cliente_id'] = $clienteId;
        if ($valorTotal !== null) $after['valor_total'] = $valorTotal;
        if ($status !== null) $after['status'] = $status;
        if ($validade !== null) $after['validade'] = $validade;
        AuditLogger::logUpdate('sales_quotes', $id, $before ?: [], $after ?: [], ['endpoint' => 'vendas.orcamentos.update']);
        return Response::json(['ok' => true]);
    }

    public function delete(Request $r): Response {
        $pdo = ConnectionManager::forTenant();
        $params = $r->get('params', []);
        $id = (int)($params[0] ?? 0);
        if ($id <= 0) return new Response(422, ['error' => ['code' => 'invalid_id', 'message' => 'ID inválido']]);
        $beforeStmt = $pdo->prepare('SELECT id, numero, cliente_id, valor_total, status FROM sales_quotes WHERE id = ? LIMIT 1');
        $beforeStmt->execute([$id]);
        $before = $beforeStmt->fetch();
        if (!$before) return new Response(404, ['error' => ['code' => 'not_found', 'message' => 'Orçamento não encontrado']]);
        try {
            $pdo->prepare('DELETE FROM sales_quote_items WHERE quote_id = ?')->execute([$id]);
        } catch (\Throwable $e) {}
        $stmt = $pdo->prepare('DELETE FROM sales_quotes WHERE id = ?');
        $stmt->execute([$id]);
        AuditLogger::logDelete('sales_quotes', $id, $before ?: [], ['endpoint' => 'vendas.orcamentos.delete']);
        return Response::json(['ok' => true]);
    }

    // Enviar orçamento por e-mail
    public function sendEmail(Request $r): Response {
        $pdo = ConnectionManager::forTenant();
        $params = $r->get('params', []);
        $id = (int)($params[0] ?? 0);
        if ($id <= 0) return new Response(422, ['error' => ['code' => 'invalid_id', 'message' => 'ID inválido']]);

        // Buscar dados do orçamento
        $stmt = $pdo->prepare('SELECT id, numero, cliente_id, valor_total, status, validade, created_at FROM sales_quotes WHERE id = ? LIMIT 1');
        $stmt->execute([$id]);
        $quote = $stmt->fetch();
        if (!$quote) return new Response(404, ['error' => ['code' => 'not_found', 'message' => 'Orçamento não encontrado']]);

        // Itens (tolerante)
        $items = [];
        try {
            $it = $pdo->prepare('SELECT product_id, quantidade, preco, total FROM sales_quote_items WHERE quote_id = ? ORDER BY id ASC');
            $it->execute([$id]);
            $items = $it->fetchAll();
        } catch (\Throwable $e) {}

        $data = $r->body ?? [];
        $to = trim((string)($data['to'] ?? ''));
        $subject = trim((string)($data['subject'] ?? ''));
        $body = (string)($data['body'] ?? '');

        if ($to === '') {
            try {
                $cstmt = $pdo->prepare('SELECT email, nome, nome_fantasia, razao_social FROM clients WHERE id = ? LIMIT 1');
                $cstmt->execute([(int)($quote['cliente_id'] ?? 0)]);
                $client = $cstmt->fetch();
                if (!empty($client['email'])) { $to = (string)$client['email']; }
            } catch (\Throwable $e) { /* ignore */ }
        }
        if ($to === '') return new Response(422, ['error' => ['code' => 'email_required', 'message' => 'Destinatário (to) é obrigatório ou deve existir no cadastro do cliente']]);

        if ($subject === '') { $subject = 'Orçamento ' . (string)($quote['numero'] ?? $quote['id']); }

        if ($body === '') {
            $lines = [];
            $lines[] = 'Olá,';
            $lines[] = '';
            $lines[] = 'Segue o orçamento:';
            $lines[] = 'Número: ' . (string)($quote['numero'] ?? $quote['id']);
            $lines[] = 'Status: ' . (string)($quote['status'] ?? '');
            $lines[] = 'Criado em: ' . (string)($quote['created_at'] ?? '');
            $lines[] = 'Validade: ' . (string)($quote['validade'] ?? '');
            $lines[] = 'Valor total: R$ ' . number_format((float)($quote['valor_total'] ?? 0), 2, ',', '.');
            if (is_array($items) && count($items) > 0) {
                $lines[] = '';
                $lines[] = 'Itens:';
                foreach ($items as $it) {
                    $lines[] = sprintf('- Produto #%s • Qtd: %s • Preço: R$ %s • Total: R$ %s',
                        (string)($it['product_id'] ?? ''),
                        (string)($it['quantidade'] ?? ''),
                        number_format((float)($it['preco'] ?? 0), 2, ',', '.'),
                        number_format((float)($it['total'] ?? 0), 2, ',', '.')
                    );
                }
            }
            $lines[] = '';
            $lines[] = 'Qualquer dúvida, estamos à disposição.';
            $body = implode("\n", $lines);
        }

        $ok = Mailer::send($to, $subject, $body);
        AuditLogger::log('send_email', 'sales_quotes', (int)$quote['id'], null, ['to' => $to, 'subject' => $subject], ['endpoint' => 'vendas.orcamentos.email_send']);
        return Response::json(['ok' => (bool)$ok]);
    }

    /**
     * Aprova uma cotação
     */
    public function aprovar(Request $r): Response {
        $pdo = ConnectionManager::forTenant();
        $params = $r->get('params', []);
        $id = (int)($params[0] ?? 0);
        if ($id <= 0) return new Response(422, ['error' => ['code' => 'invalid_id', 'message' => 'ID inválido']]);

        $user = UserContext::get($r);
        $userId = $user['sub'] ?? null;

        try {
            $pdo->beginTransaction();

            // Buscar cotação
            $stmt = $pdo->prepare('SELECT * FROM sales_quotes WHERE id = ?');
            $stmt->execute([$id]);
            $quote = $stmt->fetch();

            if (!$quote) {
                $pdo->rollBack();
                return new Response(404, ['error' => ['code' => 'not_found', 'message' => 'Cotação não encontrada']]);
            }

            // Atualizar status
            $stmt = $pdo->prepare('
                UPDATE sales_quotes 
                SET status = "aprovado", 
                    aprovado_por = ?,
                    aprovado_em = NOW()
                WHERE id = ?
            ');
            $stmt->execute([$userId, $id]);

            $pdo->commit();

            AuditLogger::logUpdate('sales_quotes', $id, ['status' => $quote['status']], ['status' => 'aprovado'], ['endpoint' => 'vendas.orcamentos.aprovar']);
            return Response::json(['ok' => true, 'message' => 'Cotação aprovada com sucesso']);
        } catch (\Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            return new Response(500, ['error' => ['code' => 'approve_failed', 'message' => $e->getMessage()]]);
        }
    }

    /**
     * Cria nova versão de uma cotação
     */
    public function criarVersao(Request $r): Response {
        $pdo = ConnectionManager::forTenant();
        $params = $r->get('params', []);
        $id = (int)($params[0] ?? 0);
        if ($id <= 0) return new Response(422, ['error' => ['code' => 'invalid_id', 'message' => 'ID inválido']]);

        $data = $r->body ?? [];

        try {
            $pdo->beginTransaction();

            // Buscar cotação original
            $stmt = $pdo->prepare('SELECT * FROM sales_quotes WHERE id = ?');
            $stmt->execute([$id]);
            $quoteOriginal = $stmt->fetch();

            if (!$quoteOriginal) {
                $pdo->rollBack();
                return new Response(404, ['error' => ['code' => 'not_found', 'message' => 'Cotação não encontrada']]);
            }

            // Buscar próxima versão
            $stmt = $pdo->prepare('SELECT MAX(versao) as max_versao FROM sales_quotes WHERE id = ? OR quote_pai_id = ?');
            $stmt->execute([$id, $id]);
            $result = $stmt->fetch();
            $novaVersao = ((int)($result['max_versao'] ?? 0)) + 1;

            // Salvar dados da versão atual no histórico
            $dadosVersao = [
                'numero' => $quoteOriginal['numero'],
                'cliente_id' => $quoteOriginal['cliente_id'],
                'valor_total' => $quoteOriginal['valor_total'],
                'status' => $quoteOriginal['status'],
                'validade' => $quoteOriginal['validade'],
                'observacoes' => $quoteOriginal['observacoes'] ?? null,
                'items' => []
            ];

            // Buscar itens
            $stmt = $pdo->prepare('SELECT * FROM sales_quote_items WHERE quote_id = ?');
            $stmt->execute([$id]);
            $items = $stmt->fetchAll();
            foreach ($items as $item) {
                $dadosVersao['items'][] = $item;
            }

            // Salvar no histórico
            $stmt = $pdo->prepare('
                INSERT INTO sales_quote_versions (quote_id, versao, dados_json, criado_por)
                VALUES (?, ?, ?, ?)
            ');
            $stmt->execute([$id, $quoteOriginal['versao'], json_encode($dadosVersao), $quoteOriginal['aprovado_por']]);

            // Criar nova versão
            $quotePaiId = $quoteOriginal['quote_pai_id'] ?? $id;
            
            // Gerar novo número ou manter o mesmo
            $novoNumero = $quoteOriginal['numero'] . '-v' . $novaVersao;

            // Verificar se colunas existem antes de inserir
            try {
                $stmt = $pdo->prepare('
                    INSERT INTO sales_quotes (
                        numero, cliente_id, valor_total, status, validade, observacoes,
                        versao, quote_pai_id, desconto_percent, desconto_valor,
                        acrescimo_percent, acrescimo_valor, frete
                    ) VALUES (?, ?, ?, "rascunho", ?, ?, ?, ?, ?, ?, ?, ?, ?)
                ');
                $stmt->execute([
                    $novoNumero,
                    $quoteOriginal['cliente_id'],
                    $quoteOriginal['valor_total'],
                    $quoteOriginal['validade'] ?? null,
                    $data['observacoes'] ?? $quoteOriginal['observacoes'] ?? null,
                    $novaVersao,
                    $quotePaiId,
                    $data['desconto_percent'] ?? $quoteOriginal['desconto_percent'] ?? 0,
                    $data['desconto_valor'] ?? $quoteOriginal['desconto_valor'] ?? 0,
                    $data['acrescimo_percent'] ?? $quoteOriginal['acrescimo_percent'] ?? 0,
                    $data['acrescimo_valor'] ?? $quoteOriginal['acrescimo_valor'] ?? 0,
                    $data['frete'] ?? $quoteOriginal['frete'] ?? 0
                ]);
            } catch (\Exception $e) {
                // Fallback: inserir sem campos novos
                $stmt = $pdo->prepare('
                    INSERT INTO sales_quotes (numero, cliente_id, valor_total, status, validade)
                    VALUES (?, ?, ?, "rascunho", ?)
                ');
                $stmt->execute([
                    $novoNumero,
                    $quoteOriginal['cliente_id'],
                    $quoteOriginal['valor_total'],
                    $quoteOriginal['validade'] ?? null
                ]);
            }
            $novaCotacaoId = (int)$pdo->lastInsertId();

            // Copiar itens
            if (count($items) > 0) {
                try {
                    // Tentar com campos novos
                    $stmt = $pdo->prepare('
                        INSERT INTO sales_quote_items (
                            quote_id, product_id, quantidade, preco, total,
                            desconto_percent, desconto_valor, observacoes
                        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                    ');
                    foreach ($items as $item) {
                        $stmt->execute([
                            $novaCotacaoId,
                            $item['product_id'],
                            $item['quantidade'],
                            $item['preco'],
                            $item['total'],
                            $item['desconto_percent'] ?? 0,
                            $item['desconto_valor'] ?? 0,
                            $item['observacoes'] ?? null
                        ]);
                    }
                } catch (\Exception $e) {
                    // Fallback: inserir sem campos novos
                    $stmt = $pdo->prepare('
                        INSERT INTO sales_quote_items (quote_id, product_id, quantidade, preco, total)
                        VALUES (?, ?, ?, ?, ?)
                    ');
                    foreach ($items as $item) {
                        $stmt->execute([
                            $novaCotacaoId,
                            $item['product_id'],
                            $item['quantidade'],
                            $item['preco'],
                            $item['total']
                        ]);
                    }
                }
            }

            $pdo->commit();

            AuditLogger::logCreate('sales_quotes', $novaCotacaoId, [
                'numero' => $novoNumero,
                'versao' => $novaVersao,
                'quote_pai_id' => $quotePaiId
            ], ['endpoint' => 'vendas.orcamentos.criar_versao']);

            return Response::json([
                'ok' => true,
                'id' => $novaCotacaoId,
                'numero' => $novoNumero,
                'versao' => $novaVersao
            ], 201);
        } catch (\Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            return new Response(500, ['error' => ['code' => 'version_create_failed', 'message' => $e->getMessage()]]);
        }
    }

    /**
     * Lista templates de cotações
     */
    public function listTemplates(Request $r): Response {
        $pdo = ConnectionManager::forTenant();
        $stmt = $pdo->prepare('SELECT * FROM sales_quote_templates WHERE ativo = 1 ORDER BY nome ASC');
        $stmt->execute();
        $templates = $stmt->fetchAll();

        return Response::json(['data' => $templates]);
    }

    /**
     * Aplica template a uma cotação
     */
    public function aplicarTemplate(Request $r): Response {
        $pdo = ConnectionManager::forTenant();
        $params = $r->get('params', []);
        $quoteId = (int)($params[0] ?? 0);
        $templateId = (int)($params[1] ?? 0);

        if ($quoteId <= 0 || $templateId <= 0) {
            return new Response(422, ['error' => ['code' => 'invalid_params', 'message' => 'quote_id e template_id são obrigatórios']]);
        }

        // Buscar template
        try {
            $stmt = $pdo->prepare('SELECT * FROM sales_quote_templates WHERE id = ? AND ativo = 1');
            $stmt->execute([$templateId]);
            $template = $stmt->fetch();
        } catch (\Exception $e) {
            return new Response(404, ['error' => ['code' => 'template_not_found', 'message' => 'Template não encontrado']]);
        }

        if (!$template) {
            return new Response(404, ['error' => ['code' => 'template_not_found', 'message' => 'Template não encontrado']]);
        }

        $templateData = json_decode($template['template_json'], true);

        // Aplicar template à cotação
        try {
            $pdo->beginTransaction();

            $updates = [];
            $params = [$quoteId];

            if (isset($templateData['condicoes_pagamento'])) {
                $updates[] = 'condicoes_pagamento = ?';
                $params[] = $templateData['condicoes_pagamento'];
            }
            if (isset($templateData['prazo_entrega'])) {
                $updates[] = 'prazo_entrega = ?';
                $params[] = $templateData['prazo_entrega'];
            }
            if (isset($templateData['observacoes'])) {
                $updates[] = 'observacoes = ?';
                $params[] = $templateData['observacoes'];
            }

            if (count($updates) > 0) {
                $updates[] = 'template_id = ?';
                $params[] = $templateId;
                $params[0] = $quoteId; // Reset primeiro param

                $sql = 'UPDATE sales_quotes SET ' . implode(', ', $updates) . ' WHERE id = ?';
                $stmt = $pdo->prepare($sql);
                $stmt->execute($params);
            }

            $pdo->commit();

            AuditLogger::logUpdate('sales_quotes', $quoteId, [], ['template_id' => $templateId], ['endpoint' => 'vendas.orcamentos.aplicar_template']);
            return Response::json(['ok' => true, 'message' => 'Template aplicado com sucesso']);
        } catch (\Throwable $e) {
            if ($pdo->inTransaction()) $pdo->rollBack();
            return new Response(500, ['error' => ['code' => 'template_apply_failed', 'message' => $e->getMessage()]]);
        }
    }

    public function convertToOrder(Request $r): Response {
        $pdo = ConnectionManager::forTenant();
        $params = $r->get('params', []);
        $id = (int)($params[0] ?? 0);
        if ($id <= 0) return new Response(422, ['error' => ['code' => 'invalid_id', 'message' => 'ID inválido']]);

        // Carregar orçamento e itens
        $qst = $pdo->prepare('SELECT id, numero, cliente_id, valor_total, status FROM sales_quotes WHERE id = ? LIMIT 1');
        $qst->execute([$id]);
        $quote = $qst->fetch();
        if (!$quote) return new Response(404, ['error' => ['code' => 'not_found', 'message' => 'Orçamento não encontrado']]);
        $it = $pdo->prepare('SELECT product_id, quantidade, preco, total FROM sales_quote_items WHERE quote_id = ? ORDER BY id ASC');
        $it->execute([$id]);
        $items = $it->fetchAll();

        // Conversão estrita: tudo igual ao orçamento, alterando apenas referências
        try {
            // Garantir coluna de vínculo com orçamento (fora da transação para evitar implicit commits)
            $hasQuoteOrigCol = false;
            try {
                $chkCol = $pdo->prepare("SHOW COLUMNS FROM sales_orders LIKE 'quote_id_origem'");
                $chkCol->execute();
                $hasQuoteOrigCol = (bool)$chkCol->fetch();
                if (!$hasQuoteOrigCol) {
                    try {
                        $pdo->query('ALTER TABLE sales_orders ADD COLUMN quote_id_origem INT NULL');
                        // Rechecar após tentativa de alteração
                        $chkCol2 = $pdo->prepare("SHOW COLUMNS FROM sales_orders LIKE 'quote_id_origem'");
                        $chkCol2->execute();
                        $hasQuoteOrigCol = (bool)$chkCol2->fetch();
                    } catch (\Throwable $e2) {
                        $hasQuoteOrigCol = false; // manter falso caso não crie
                    }
                }
            } catch (\Throwable $e) { $hasQuoteOrigCol = false; }

            $pdo->beginTransaction();

            // Gerar número do pedido no padrão sequencial PD-##########
            $numero = null;
            try {
                $stmtSeq = $pdo->query("SELECT MAX(CAST(SUBSTRING(numero, 4) AS UNSIGNED)) AS max_seq FROM sales_orders WHERE numero LIKE 'PD-%'");
                $rowSeq = $stmtSeq->fetch();
                $next = ((int)($rowSeq['max_seq'] ?? 0)) + 1;
                $numero = 'PD-' . str_pad((string)$next, 10, '0', STR_PAD_LEFT);
            } catch (\Throwable $e) { $numero = null; }
            if ($numero === null) { $numero = ''; }

            // Criar pedido copiando valores do orçamento
            if ($hasQuoteOrigCol) {
                $ins = $pdo->prepare('INSERT INTO sales_orders (numero, cliente_id, valor_total, status, quote_id_origem) VALUES (?,?,?,?,?)');
                $ins->execute([$numero, (int)$quote['cliente_id'], (float)$quote['valor_total'], 'aberto', (int)$quote['id']]);
            } else {
                $ins = $pdo->prepare('INSERT INTO sales_orders (numero, cliente_id, valor_total, status) VALUES (?,?,?,?)');
                $ins->execute([$numero, (int)$quote['cliente_id'], (float)$quote['valor_total'], 'aberto']);
            }
            $orderId = (int)$pdo->lastInsertId();
            if ($numero === '') {
                $numero = 'PD-' . str_pad((string)$orderId, 10, '0', STR_PAD_LEFT);
                $pdo->prepare('UPDATE sales_orders SET numero = ? WHERE id = ?')->execute([$numero, $orderId]);
            }
            if ($orderId <= 0) {
                if ($pdo->inTransaction()) { $pdo->rollBack(); }
                return new Response(500, ['error' => ['code' => 'order_create_failed', 'message' => 'Falha ao criar pedido']]);
            }

            // Copiar itens
            if (is_array($items) && count($items) > 0) {
                try {
                    $itIns = $pdo->prepare('INSERT INTO sales_order_items (order_id, product_id, quantidade, preco, total) VALUES (?,?,?,?,?)');
                    foreach ($items as $s) {
                        $itIns->execute([$orderId, (int)$s['product_id'], (float)$s['quantidade'], (float)$s['preco'], (float)$s['total']]);
                    }
                } catch (\Throwable $e) {
                    if ($pdo->inTransaction()) { $pdo->rollBack(); }
                    return new Response(500, ['error' => ['code' => 'items_copy_failed', 'message' => 'Falha ao copiar itens do orçamento para o pedido']]);
                }
            }

            // Validação estrita: comparar cabeçalho e itens (exceto referências)
            try {
                $hdrStmt = $pdo->prepare('SELECT id, numero, cliente_id, valor_total, status, quote_id_origem FROM sales_orders WHERE id = ? LIMIT 1');
                $hdrStmt->execute([$orderId]);
                $order = $hdrStmt->fetch();
            } catch (\Throwable $e) {
                // Fallback sem quote_id_origem
                $hdrStmt = $pdo->prepare('SELECT id, numero, cliente_id, valor_total, status FROM sales_orders WHERE id = ? LIMIT 1');
                $hdrStmt->execute([$orderId]);
                $order = $hdrStmt->fetch();
            }
            $dif = [];
            if ((int)($order['cliente_id'] ?? 0) !== (int)($quote['cliente_id'] ?? 0)) { $dif[] = 'cliente_id'; }
            if (abs((float)($order['valor_total'] ?? 0) - (float)($quote['valor_total'] ?? 0)) > 0.0001) { $dif[] = 'valor_total'; }
            if ($hasQuoteOrigCol) {
                if ((int)($order['quote_id_origem'] ?? 0) !== (int)($quote['id'] ?? 0)) { $dif[] = 'quote_id_origem'; }
            }

            // Itens
            $oi = $pdo->prepare('SELECT product_id, quantidade, preco, total FROM sales_order_items WHERE order_id = ? ORDER BY product_id ASC, quantidade ASC');
            $oi->execute([$orderId]);
            $orderItems = $oi->fetchAll() ?: [];
            $quoteItems = $items ?: [];

            $normalize = function(array $list): array {
                $out = [];
                foreach ($list as $x) {
                    $out[] = [
                        'product_id' => (int)($x['product_id'] ?? 0),
                        'quantidade' => (float)($x['quantidade'] ?? 0),
                        'preco' => (float)($x['preco'] ?? 0),
                        'total' => (float)($x['total'] ?? 0),
                    ];
                }
                usort($out, function($a,$b){ return $a['product_id'] <=> $b['product_id']; });
                return $out;
            };

            $nq = $normalize($quoteItems);
            $no = $normalize($orderItems);
            if (count($nq) !== count($no)) { $dif[] = 'items_count'; }
            $max = min(count($nq), count($no));
            for ($i=0; $i<$max; $i++) {
                foreach (['product_id','quantidade','preco','total'] as $k) {
                    if (in_array($k, ['preco', 'total', 'quantidade'])) {
                        if (abs($nq[$i][$k] - $no[$i][$k]) > 0.0001) { $dif[] = 'item_'.$i.'_'.$k; }
                    } else {
                        if ($nq[$i][$k] !== $no[$i][$k]) { $dif[] = 'item_'.$i.'_'.$k; }
                    }
                }
            }

            if (count($dif) > 0) {
                $user = UserContext::get($r) ?? [];
                AuditLogger::log('convert_quote_to_order_failed', 'sales_quotes', (int)$quote['id'], null, ['order_id_attempt' => $orderId, 'differences' => $dif], ['endpoint' => 'vendas.orcamentos.convert', 'user_id' => ($user['sub'] ?? null)]);
                if ($pdo->inTransaction()) { $pdo->rollBack(); }
                return new Response(409, ['error' => ['code' => 'strict_compare_failed', 'message' => 'Conversão estrita falhou: divergências detectadas', 'differences' => $dif]]);
            }

            // Atualizar status do orçamento para 'convertido' e finalizar
            $pdo->prepare('UPDATE sales_quotes SET status = ? WHERE id = ?')->execute(['convertido', $id]);
            if ($pdo->inTransaction()) { $pdo->commit(); }

            $user = UserContext::get($r) ?? [];
            AuditLogger::logCreate('sales_orders', $orderId, ['numero' => $numero, 'from_quote' => $quote['numero'], 'quote_id_origem' => (int)$quote['id']], ['endpoint' => 'vendas.orcamentos.convert', 'user_id' => ($user['sub'] ?? null)]);
            AuditLogger::log('convert_quote_to_order', 'sales_quotes', (int)$quote['id'], null, ['from_quote' => $quote['numero'], 'to_order' => $numero, 'strict' => true, 'differences' => []], ['endpoint' => 'vendas.orcamentos.convert', 'user_id' => ($user['sub'] ?? null)]);
            return Response::json(['ok' => true, 'order_id' => $orderId, 'order_numero' => $numero, 'quote_id' => (int)$quote['id'], 'quote_numero' => (string)$quote['numero']], 201);
        } catch (\Throwable $e) {
            if ($pdo->inTransaction()) { $pdo->rollBack(); }
            return new Response(500, ['error' => ['code' => 'convert_unexpected_error', 'message' => $e->getMessage()]]);
        }
    }
}