<?php
/**
 * BALCÃO API (robusto)
 * - Não dá erro se pagamentos/lancamentos não existirem (retorna 0)
 * - Fatura só aparece quando pedido está ENTREGUE
 *
 * Endpoints:
 *  GET  ?action=contas_abertas
 *  GET  ?action=faturas_sessoes&conta_id=123
 *  GET  ?action=pedidos_fatura&conta_id=123[&sessao_key=abc]   <-- NOVO
 *  POST ?action=registrar_pagamento        {conta_id,sessao_key,valor,metodo}
 *  POST ?action=registrar_lancamento       {conta_id,sessao_key,valor,descricao}
 *  POST ?action=fechar_conta               {conta_id} (saldo_fatura precisa ser 0)
 */

require_once __DIR__ . "/../../includes/session_staff.php"; // ajuste conforme a profundidade
require_once __DIR__ . "/../../includes/db.php";

if (file_exists(__DIR__ . "/../verifica_login.php")) {
  include __DIR__ . "/../verifica_login.php";
} elseif (file_exists(__DIR__ . "/../includes/verifica_login.php")) {
  include __DIR__ . "/../includes/verifica_login.php";
}


header("Content-Type: application/json; charset=utf-8");

$action = $_GET["action"] ?? "";

// usuário logado no balcão (para vincular lançamentos no caixa)
$__usuarioId = isset($_SESSION['staff']['id']) ? (int)$_SESSION['staff']['id'] : null;

// garante estrutura do caixa (não quebra se módulo do caixa não existir)
caixa_schema_bootstrap($pdo);


function json_input(): array {
  $raw = file_get_contents("php://input");
  $data = json_decode($raw, true);
  return is_array($data) ? $data : [];
}

function money_float($v): float {
  return (float)number_format((float)$v, 2, '.', '');
}

/**
 * Evita 500 quando tabelas opcionais não existirem.
 */
function table_exists(PDO $pdo, string $table): bool {
  static $cache = [];
  if (isset($cache[$table])) return $cache[$table];

  try {
    $st = $pdo->prepare("
      SELECT COUNT(*) AS c
      FROM information_schema.tables
      WHERE table_schema = DATABASE()
        AND table_name = ?
      LIMIT 1
    ");
    $st->execute([$table]);
    $r = $st->fetch();
    $cache[$table] = ((int)($r["c"] ?? 0) > 0);
  } catch (Throwable $e) {
    $cache[$table] = false;
  }
  return $cache[$table];
}

/* ===================== CAIXA (INTEGRAÇÃO BALCÃO) ===================== */
// Registra entradas/saídas do balcão no caixa aberto (para a tela do caixa atualizar automaticamente).
// Categorias usadas aqui: PAGAMENTO_FATURA, RECARGA_CARTAO, ESTORNO_RECARGA

function caixa_schema_bootstrap(PDO $pdo): void {
  // Só cria se a tabela 'caixas' existir (módulo do caixa instalado)
  if (!table_exists($pdo, 'caixas')) return;
  $pdo->exec("\n    CREATE TABLE IF NOT EXISTS caixa_lancamentos (\n      id INT AUTO_INCREMENT PRIMARY KEY,\n      caixa_id INT NOT NULL,\n      usuario_id INT NULL,\n      tipo ENUM('ENTRADA','SAIDA') NOT NULL DEFAULT 'ENTRADA',\n      categoria VARCHAR(40) NOT NULL DEFAULT 'OUTRO',\n      valor DECIMAL(10,2) NOT NULL,\n      descricao VARCHAR(255) NULL,\n      ref_table VARCHAR(40) NULL,\n      ref_id INT NULL,\n      criado_em DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,\n      INDEX (caixa_id),\n      INDEX (criado_em),\n      INDEX (categoria),\n      CONSTRAINT fk_caixa_lancamentos_caixa\n        FOREIGN KEY (caixa_id) REFERENCES caixas(id)\n        ON DELETE CASCADE\n    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;\n  ");
}

function caixa_get_open_id(PDO $pdo): ?int {
  if (!table_exists($pdo, 'caixas')) return null;
  $st = $pdo->query("SELECT id FROM caixas WHERE status='ABERTO' ORDER BY id DESC LIMIT 1");
  $r = $st ? $st->fetch(PDO::FETCH_ASSOC) : null;
  return $r ? (int)$r['id'] : null;
}

function caixa_add_lancamento(PDO $pdo, int $caixaId, ?int $usuarioId, string $tipo, string $categoria, float $valor, ?string $descricao=null, ?string $refTable=null, ?int $refId=null): void {
  // assume schema já existe (caixa_schema_bootstrap rodou no início do request)
  $ins = $pdo->prepare("\n    INSERT INTO caixa_lancamentos (caixa_id, usuario_id, tipo, categoria, valor, descricao, ref_table, ref_id)\n    VALUES (?,?,?,?,?,?,?,?)\n  ");
  $ins->execute([
    $caixaId,
    $usuarioId,
    $tipo,
    $categoria,
    money_float($valor),
    $descricao,
    $refTable,
    $refId
  ]);
}

/* ===================== PEDIDOS (ITENS) ===================== */
/**
 * FATURA só do ENTREGUE.
 * ANDAMENTO é apenas informativo.
 * Exclui CANCELADO/RECUSADO.
 */
function totais_pedidos_conta(PDO $pdo, int $contaId): array {
  $sql = "
    SELECT
      COALESCE(SUM(CASE WHEN p.status='ENTREGUE' THEN i.qtd*i.preco_unit ELSE 0 END),0) AS entregues,
      COALESCE(SUM(CASE WHEN p.status IN ('ENVIADO','ACEITO','EM_PREPARO') THEN i.qtd*i.preco_unit ELSE 0 END),0) AS andamento
    FROM pedidos p
    JOIN pedido_itens i ON i.pedido_id = p.id
    WHERE p.conta_id = ?
      AND p.status NOT IN ('CANCELADO','RECUSADO')
  ";
  $st = $pdo->prepare($sql);
  $st->execute([$contaId]);
  $r = $st->fetch() ?: [];
  return [
    "entregues" => money_float($r["entregues"] ?? 0),
    "andamento" => money_float($r["andamento"] ?? 0),
  ];
}

function totais_pedidos_sessao(PDO $pdo, int $contaId, string $sessaoKey): array {
  $sql = "
    SELECT
      COALESCE(SUM(CASE WHEN p.status='ENTREGUE' THEN i.qtd*i.preco_unit ELSE 0 END),0) AS entregues,
      COALESCE(SUM(CASE WHEN p.status IN ('ENVIADO','ACEITO','EM_PREPARO') THEN i.qtd*i.preco_unit ELSE 0 END),0) AS andamento
    FROM pedidos p
    JOIN pedido_itens i ON i.pedido_id = p.id
    WHERE p.conta_id = ?
      AND p.sessao_key = ?
      AND p.status NOT IN ('CANCELADO','RECUSADO')
  ";
  $st = $pdo->prepare($sql);
  $st->execute([$contaId, $sessaoKey]);
  $r = $st->fetch() ?: [];
  return [
    "entregues" => money_float($r["entregues"] ?? 0),
    "andamento" => money_float($r["andamento"] ?? 0),
  ];
}

/* ===================== AVULSOS (LANCAMENTOS) ===================== */
function total_avulsos_conta(PDO $pdo, int $contaId): float {
  if (!table_exists($pdo, "lancamentos")) return 0.0;

  $sql = "
    SELECT COALESCE(SUM(valor),0) AS total
    FROM lancamentos
    WHERE conta_id = ?
      AND status = 'CONFIRMADO'
  ";
  $st = $pdo->prepare($sql);
  $st->execute([$contaId]);
  $r = $st->fetch() ?: [];
  return money_float($r["total"] ?? 0);
}

function total_avulsos_sessao(PDO $pdo, int $contaId, string $sessaoKey): float {
  if (!table_exists($pdo, "lancamentos")) return 0.0;

  $sql = "
    SELECT COALESCE(SUM(valor),0) AS total
    FROM lancamentos
    WHERE conta_id = ?
      AND sessao_key = ?
      AND status = 'CONFIRMADO'
  ";
  $st = $pdo->prepare($sql);
  $st->execute([$contaId, $sessaoKey]);
  $r = $st->fetch() ?: [];
  return money_float($r["total"] ?? 0);
}

/* ===================== PAGAMENTOS ===================== */
function total_pago_conta(PDO $pdo, int $contaId): float {
  if (!table_exists($pdo, "pagamentos")) return 0.0;

  $sql = "
    SELECT COALESCE(SUM(valor),0) AS total
    FROM pagamentos
    WHERE conta_id = ?
      AND status = 'CONFIRMADO'
  ";
  $st = $pdo->prepare($sql);
  $st->execute([$contaId]);
  $r = $st->fetch() ?: [];
  return money_float($r["total"] ?? 0);
}

function total_pago_sessao(PDO $pdo, int $contaId, string $sessaoKey): float {
  if (!table_exists($pdo, "pagamentos")) return 0.0;

  $sql = "
    SELECT COALESCE(SUM(valor),0) AS total
    FROM pagamentos
    WHERE conta_id = ?
      AND sessao_key = ?
      AND status = 'CONFIRMADO'
  ";
  $st = $pdo->prepare($sql);
  $st->execute([$contaId, $sessaoKey]);
  $r = $st->fetch() ?: [];
  return money_float($r["total"] ?? 0);
}

/* ===================== TOTAIS (FATURA FINAL) ===================== */
function totais_conta(PDO $pdo, int $contaId): array {
  $p  = totais_pedidos_conta($pdo, $contaId);
  $av = total_avulsos_conta($pdo, $contaId);
  $pg = total_pago_conta($pdo, $contaId);

  $total_fatura = money_float($p["entregues"] + $av);
  $saldo_fatura = money_float($total_fatura - $pg);

  return [
    "pedidos_entregue"  => $p["entregues"],
    "pedidos_andamento" => $p["andamento"],
    "avulsos_total"     => $av,
    "total_fatura"      => $total_fatura,
    "total_pago"        => $pg,
    "saldo_fatura"      => $saldo_fatura
  ];
}

function totais_sessao(PDO $pdo, int $contaId, string $sessaoKey): array {
  $p  = totais_pedidos_sessao($pdo, $contaId, $sessaoKey);
  $av = total_avulsos_sessao($pdo, $contaId, $sessaoKey);
  $pg = total_pago_sessao($pdo, $contaId, $sessaoKey);

  $total_fatura = money_float($p["entregues"] + $av);
  $saldo_fatura = money_float($total_fatura - $pg);

  return [
    "pedidos_entregue"  => $p["entregues"],
    "pedidos_andamento" => $p["andamento"],
    "avulsos_total"     => $av,
    "total_fatura"      => $total_fatura,
    "total_pago"        => $pg,
    "saldo_fatura"      => $saldo_fatura
  ];
}

/* =========================================================
 * GET: contas_abertas
 * ========================================================= */
if ($action === "contas_abertas") {
  try {
    $sql = "
      SELECT c.id AS conta_id, m.numero AS mesa
      FROM contas c
      JOIN mesas m ON m.id = c.mesa_id
      WHERE c.status='ABERTA'
      ORDER BY c.id DESC
      LIMIT 200
    ";
    $rows = $pdo->query($sql)->fetchAll();

    $contas = [];
    foreach ($rows as $r) {
      $contaId = (int)$r["conta_id"];
      $t = totais_conta($pdo, $contaId);

      $contas[] = [
        "conta_id" => $contaId,
        "mesa" => (int)$r["mesa"],
        "pedidos_entregue"  => $t["pedidos_entregue"],
        "pedidos_andamento" => $t["pedidos_andamento"],
        "avulsos_total"     => $t["avulsos_total"],
        "total_fatura"      => $t["total_fatura"],
        "total_pago"        => $t["total_pago"],
        "saldo_fatura"      => $t["saldo_fatura"],
      ];
    }

    echo json_encode(["ok" => true, "contas" => $contas]);
  } catch (Throwable $e) {
    http_response_code(500);
    echo json_encode(["ok" => false, "error" => "Erro interno: " . $e->getMessage()]);
  }
  exit;
}

/* =========================================================
 * GET: faturas_sessoes
 * ========================================================= */
if ($action === "faturas_sessoes") {
  $contaId = (int)($_GET["conta_id"] ?? 0);
  if ($contaId <= 0) {
    http_response_code(400);
    echo json_encode(["ok" => false, "error" => "conta_id inválido"]);
    exit;
  }

  try {
    $st = $pdo->prepare("
      SELECT c.id, c.status, m.numero AS mesa
      FROM contas c
      JOIN mesas m ON m.id = c.mesa_id
      WHERE c.id=?
      LIMIT 1
    ");
    $st->execute([$contaId]);
    $contaRow = $st->fetch();
    if (!$contaRow) {
      http_response_code(404);
      echo json_encode(["ok" => false, "error" => "Conta não encontrada"]);
      exit;
    }

    $st = $pdo->prepare("
      SELECT p.sessao_key, MAX(p.id) AS last_pedido_id, MAX(p.cliente_id) AS cliente_id
      FROM pedidos p
      WHERE p.conta_id = ?
        AND COALESCE(p.sessao_key,'') <> ''
        AND p.status NOT IN ('CANCELADO','RECUSADO')
      GROUP BY p.sessao_key
      ORDER BY last_pedido_id DESC
      LIMIT 200
    ");
    $st->execute([$contaId]);
    $sessRows = $st->fetchAll();

    $clienteIds = [];
    foreach ($sessRows as $sr) {
      $cid = (int)($sr["cliente_id"] ?? 0);
      if ($cid > 0) $clienteIds[$cid] = true;
    }

    $clientesMap = [];
    if ($clienteIds) {
      $ids = array_keys($clienteIds);
      $in = implode(",", array_fill(0, count($ids), "?"));
      $stc = $pdo->prepare("SELECT id, nome, celular FROM clientes WHERE id IN ($in)");
      $stc->execute($ids);
      foreach ($stc->fetchAll() as $c) {
        $clientesMap[(int)$c["id"]] = [
          "nome" => $c["nome"] ?? "",
          "celular" => $c["celular"] ?? ""
        ];
      }
    }

    $contaTot = totais_conta($pdo, $contaId);

    $sessoes = [];
    foreach ($sessRows as $sr) {
      $sk = (string)$sr["sessao_key"];
      $cid = (int)($sr["cliente_id"] ?? 0);

      $nome = "Sessão " . substr($sk, 0, 6);
      if ($cid > 0 && isset($clientesMap[$cid])) {
        $nm = trim((string)$clientesMap[$cid]["nome"]);
        $cel = preg_replace('/\D+/', '', (string)$clientesMap[$cid]["celular"]);
        if ($nm !== "") $nome = $nm;
        if ($cel !== "" && strlen($cel) >= 8) $nome .= " • " . substr($cel, -8);
      }

      $t = totais_sessao($pdo, $contaId, $sk);

      $sessoes[] = [
        "sessao_key" => $sk,
        "cliente" => $nome,

        "pedidos_entregue"  => $t["pedidos_entregue"],
        "pedidos_andamento" => $t["pedidos_andamento"],
        "avulsos_total"     => $t["avulsos_total"],
        "total_fatura"      => $t["total_fatura"],
        "total_pago"        => $t["total_pago"],
        "saldo_fatura"      => $t["saldo_fatura"],
      ];
    }

    echo json_encode([
      "ok" => true,
      "mesa" => (int)$contaRow["mesa"],
      "conta_status" => $contaRow["status"],
      "conta" => $contaTot,
      "sessoes" => $sessoes
    ]);
  } catch (Throwable $e) {
    http_response_code(500);
    echo json_encode(["ok" => false, "error" => "Erro interno: " . $e->getMessage()]);
  }
  exit;
}

/* =========================================================
 * GET: pedidos_fatura  (NOVO)
 * - Mostra tudo que compõe a fatura: pedidos ENTREGUE + avulsos
 * - Pode filtrar por sessao_key
 * ========================================================= */
if ($action === "pedidos_fatura") {
  $contaId = (int)($_GET["conta_id"] ?? 0);
  $sessaoKey = trim((string)($_GET["sessao_key"] ?? ""));

  if ($contaId <= 0) {
    http_response_code(400);
    echo json_encode(["ok" => false, "error" => "conta_id inválido"]);
    exit;
  }

  try {
    // valida conta
    $st = $pdo->prepare("SELECT c.id, c.status, m.numero AS mesa FROM contas c JOIN mesas m ON m.id=c.mesa_id WHERE c.id=? LIMIT 1");
    $st->execute([$contaId]);
    $cr = $st->fetch();
    if (!$cr) {
      http_response_code(404);
      echo json_encode(["ok" => false, "error" => "Conta não encontrada"]);
      exit;
    }

    // pedidos ENTREGUE que viram fatura
    $params = [$contaId];
    $whereSess = "";
    if ($sessaoKey !== "") {
      $whereSess = " AND p.sessao_key = ? ";
      $params[] = $sessaoKey;
    }

    $sql = "
      SELECT
        p.id AS pedido_id,
        p.sessao_key,
        p.status,
        p.entregue_em,
        pr.nome AS produto,
        i.qtd,
        i.preco_unit,
        i.observacao
      FROM pedidos p
      JOIN pedido_itens i ON i.pedido_id = p.id
      JOIN produtos pr ON pr.id = i.produto_id
      WHERE p.conta_id = ?
        $whereSess
        AND p.status = 'ENTREGUE'
      ORDER BY p.id DESC, i.id ASC
      LIMIT 1000
    ";
    $st = $pdo->prepare($sql);
    $st->execute($params);
    $rows = $st->fetchAll();

    $pedidosMap = [];
    foreach ($rows as $r) {
      $pid = (int)$r["pedido_id"];
      if (!isset($pedidosMap[$pid])) {
        $pedidosMap[$pid] = [
          "pedido_id" => $pid,
          "sessao_key" => (string)($r["sessao_key"] ?? ""),
          "status" => (string)($r["status"] ?? ""),
          "entregue_em" => $r["entregue_em"] ?? null,
          "itens" => [],
          "subtotal" => 0.0
        ];
      }

      $qtd = (int)($r["qtd"] ?? 0);
      $pu  = (float)($r["preco_unit"] ?? 0);
      $sub = $qtd * $pu;

      $pedidosMap[$pid]["itens"][] = [
        "produto" => $r["produto"] ?? "",
        "qtd" => $qtd,
        "preco_unit" => money_float($pu),
        "subtotal" => money_float($sub),
        "observacao" => $r["observacao"] ?? ""
      ];
      $pedidosMap[$pid]["subtotal"] = money_float($pedidosMap[$pid]["subtotal"] + $sub);
    }

    // avulsos confirmados que compõem fatura
    $avulsos = [];
    if (table_exists($pdo, "lancamentos")) {
      $params2 = [$contaId];
      $whereSess2 = "";
      if ($sessaoKey !== "") {
        $whereSess2 = " AND sessao_key = ? ";
        $params2[] = $sessaoKey;
      }

      $sql2 = "
        SELECT id, sessao_key, descricao, valor, criado_em
        FROM lancamentos
        WHERE conta_id = ?
          $whereSess2
          AND status = 'CONFIRMADO'
        ORDER BY id DESC
        LIMIT 500
      ";
      $st2 = $pdo->prepare($sql2);
      $st2->execute($params2);
      foreach ($st2->fetchAll() as $a) {
        $avulsos[] = [
          "id" => (int)$a["id"],
          "sessao_key" => (string)($a["sessao_key"] ?? ""),
          "descricao" => (string)($a["descricao"] ?? ""),
          "valor" => money_float($a["valor"] ?? 0),
          "criado_em" => $a["criado_em"] ?? null
        ];
      }
    }

    // totais para o filtro atual
    $tot = ($sessaoKey !== "") ? totais_sessao($pdo, $contaId, $sessaoKey) : totais_conta($pdo, $contaId);

    echo json_encode([
      "ok" => true,
      "mesa" => (int)($cr["mesa"] ?? 0),
      "conta_id" => $contaId,
      "sessao_key" => $sessaoKey,
      "totais" => $tot,
      "pedidos" => array_values($pedidosMap),
      "avulsos" => $avulsos
    ]);
  } catch (Throwable $e) {
    http_response_code(500);
    echo json_encode(["ok" => false, "error" => "Erro interno: " . $e->getMessage()]);
  }
  exit;
}

/* =========================================================
 * POST: registrar_pagamento (parcial)
 * ========================================================= */
if ($action === "registrar_pagamento") {
  $data = json_input();

  $contaId = (int)($data["conta_id"] ?? 0);
  $sessaoKey = trim((string)($data["sessao_key"] ?? ""));
  $valor = (float)($data["valor"] ?? 0);
  $metodo = trim((string)($data["metodo"] ?? "OUTRO"));
  // opcional: no dinheiro, pode informar quanto o cliente entregou (para calcular troco)
  $valorRecebido = isset($data["valor_recebido"]) ? (float)$data["valor_recebido"] : null;

  if ($contaId <= 0 || $sessaoKey === "" || $valor <= 0) {
    http_response_code(400);
    echo json_encode(["ok" => false, "error" => "Dados inválidos"]);
    exit;
  }

  if (!table_exists($pdo, "pagamentos")) {
    http_response_code(500);
    echo json_encode(["ok" => false, "error" => "Tabela pagamentos não existe. Crie a tabela pagamentos."]);
    exit;
  }

  try {
    $st = $pdo->prepare("SELECT id, status FROM contas WHERE id=? LIMIT 1");
    $st->execute([$contaId]);
    $c = $st->fetch();
    if (!$c) {
      http_response_code(404);
      echo json_encode(["ok" => false, "error" => "Conta não encontrada"]);
      exit;
    }
    if (($c["status"] ?? "") !== "ABERTA") {
      http_response_code(403);
      echo json_encode(["ok" => false, "error" => "Conta não está aberta"]);
      exit;
    }

    $t = totais_sessao($pdo, $contaId, $sessaoKey);
    $saldo = (float)$t["saldo_fatura"];

    // ✅ dinheiro: aceita valor recebido maior e calcula troco
    $troco = 0.0;
    if (mb_strtoupper($metodo) === "DINHEIRO") {
      if ($valorRecebido === null) {
        // se não veio, assume que recebeu exatamente o valor informado
        $valorRecebido = $valor;
      }
      if ($valorRecebido + 0.0001 < $valor) {
        http_response_code(400);
        echo json_encode(["ok" => false, "error" => "No dinheiro, o valor recebido não pode ser menor que o valor a abater."]);
        exit;
      }
      $troco = max(0.0, $valorRecebido - $valor);
    }

    if ($valor > ($saldo + 0.01)) {
      http_response_code(400);
      echo json_encode(["ok" => false, "error" => "Valor maior que o saldo da fatura da sessão (saldo: {$saldo})"]);
      exit;
    }

    $caixaId = caixa_get_open_id($pdo);
    if (!$caixaId) {
      http_response_code(409);
      echo json_encode(["ok" => false, "error" => "Caixa está fechado. Abra o caixa antes de registrar pagamentos."]);
      exit;
    }

    $pdo->beginTransaction();
    $ins = $pdo->prepare("
      INSERT INTO pagamentos (conta_id, sessao_key, valor, metodo, status, confirmado_em)
      VALUES (?,?,?,?, 'CONFIRMADO', NOW())
    ");
    $ins->execute([$contaId, $sessaoKey, money_float($valor), $metodo]);
    $pagId = (int)$pdo->lastInsertId();

    // ✅ entrada de dinheiro no caixa (pagamento de fatura)
// - em DINHEIRO, entra o que foi recebido (valor_recebido) e o troco sai separado
$valorEntradaCaixa = $valor;
if (strtoupper($metodo) === 'DINHEIRO' && $valorRecebido !== null && $valorRecebido > 0) {
  $valorEntradaCaixa = $valorRecebido;
}

caixa_add_lancamento(
  $pdo,
  $caixaId,
  $__usuarioId,
  'ENTRADA',
  'PAGAMENTO_FATURA',
  $valorEntradaCaixa,
  "Pagamento fatura • conta #{$contaId} • sessão {$sessaoKey} • método {$metodo}",
  'pagamentos',
  $pagId
);

// ✅ troco: lançar como SAÍDA no caixa (categoria = TROCO)
if (strtoupper($metodo) === 'DINHEIRO' && $troco > 0.009) {
  caixa_add_lancamento(
    $pdo,
    $caixaId,
    $__usuarioId,
    'SAIDA',
    'TROCO',
    $troco,
    "Troco do pagamento em dinheiro • conta #{$contaId} • sessão {$sessaoKey}",
    'pagamentos',
    $pagId
  );
}
$pdo->commit();

    echo json_encode(["ok" => true, "troco" => $troco, "valor_recebido" => $valorRecebido]);
  } catch (Throwable $e) {
    if ($pdo->inTransaction()) $pdo->rollBack();
    http_response_code(500);
    echo json_encode(["ok" => false, "error" => "Erro interno: " . $e->getMessage()]);
  }
  exit;
}

/* =========================================================
 * POST: registrar_lancamento (avulso)
 * ========================================================= */
if ($action === "registrar_lancamento") {
  $data = json_input();

  $contaId = (int)($data["conta_id"] ?? 0);
  $sessaoKey = trim((string)($data["sessao_key"] ?? ""));
  $valor = (float)($data["valor"] ?? 0);
  $descricao = trim((string)($data["descricao"] ?? ""));

  if ($contaId <= 0 || $sessaoKey === "" || $valor <= 0 || $descricao === "") {
    http_response_code(400);
    echo json_encode(["ok" => false, "error" => "Dados inválidos"]);
    exit;
  }

  if (!table_exists($pdo, "lancamentos")) {
    http_response_code(500);
    echo json_encode(["ok" => false, "error" => "Tabela lancamentos não existe. Crie a tabela lancamentos."]);
    exit;
  }

  if (mb_strlen($descricao) > 255) $descricao = mb_substr($descricao, 0, 255);

  try {
    $st = $pdo->prepare("SELECT id, status FROM contas WHERE id=? LIMIT 1");
    $st->execute([$contaId]);
    $c = $st->fetch();
    if (!$c) {
      http_response_code(404);
      echo json_encode(["ok" => false, "error" => "Conta não encontrada"]);
      exit;
    }
    if (($c["status"] ?? "") !== "ABERTA") {
      http_response_code(403);
      echo json_encode(["ok" => false, "error" => "Conta não está aberta"]);
      exit;
    }

    $ins = $pdo->prepare("
      INSERT INTO lancamentos (conta_id, sessao_key, descricao, valor, status, criado_em)
      VALUES (?,?,?,?, 'CONFIRMADO', NOW())
    ");
    $ins->execute([$contaId, $sessaoKey, $descricao, money_float($valor)]);

    echo json_encode(["ok" => true]);
  } catch (Throwable $e) {
    http_response_code(500);
    echo json_encode(["ok" => false, "error" => "Erro interno: " . $e->getMessage()]);
  }
  exit;
}

/* =========================================================
 * POST: fechar_conta (só saldo fatura 0)
 * ========================================================= */
if ($action === "fechar_conta") {
  $data = json_input();
  $contaId = (int)($data["conta_id"] ?? 0);

  if ($contaId <= 0) {
    http_response_code(400);
    echo json_encode(["ok" => false, "error" => "conta_id inválido"]);
    exit;
  }

  try {
    $t = totais_conta($pdo, $contaId);
    $saldo = (float)$t["saldo_fatura"];

    if ($saldo > 0.01) {
      http_response_code(400);
      echo json_encode(["ok" => false, "error" => "Não é possível fechar: saldo da fatura ainda é {$saldo}"]);
      exit;
    }

    $st = $pdo->prepare("UPDATE contas SET status='FECHADA' WHERE id=?");
    $st->execute([$contaId]);

    echo json_encode(["ok" => true]);
  } catch (Throwable $e) {
    http_response_code(500);
    echo json_encode(["ok" => false, "error" => "Erro interno: " . $e->getMessage()]);
  }
  exit;
  // ... fechou a conta com sucesso ...
    $link = criar_link_extrato($pdo, $contaId);

    echo json_encode([
      "ok" => true,
      "message" => "Conta encerrada.",
      "extrato_url" => $link['url'],
      "extrato_expira_em" => $link['expira_em'],
    ], JSON_UNESCAPED_UNICODE);
    exit;

}

function criar_link_extrato(PDO $pdo, int $contaId): array {
  $token = bin2hex(random_bytes(32)); // 64 chars
  $expiraEm = (new DateTimeImmutable('+7 days'))->format('Y-m-d H:i:s');

  $st = $pdo->prepare("INSERT INTO conta_extratos (conta_id, token, expira_em) VALUES (?,?,?)");
  $st->execute([$contaId, $token, $expiraEm]);

  // ✅ ajuste o caminho do seu extrato (cliente)
  // exemplo: /lanchonete/public/cartao/extrato.php?conta=123&t=TOKEN
  $url = APP_BASE . "/cartao/extrato.php?conta=" . $contaId . "&t=" . $token;

  return ['token'=>$token, 'url'=>$url, 'expira_em'=>$expiraEm];
}



/* ===================== CARTAO (BALCAO) =====================
   Regras (ajuste solicitado):
   - Recarga SOMENTE no balcão.
   - Garçom apenas debita.
   - Estorno SOMENTE no balcão.
   - Cartão fechado NÃO permite estorno.
   - Balcão fecha o cartão.

   Tabelas esperadas (do seu PDF):
   - cartoes (codigo UNIQUE, status LIVRE|EM_USO|BLOQUEADO, nome, celular, mesa, aberto_em, fechado_em, atualizado_em)
   - cartao_movimentos (operacao C|D, motivo, valor, origem BALCAO|GARCOM|SISTEMA, ref_mov_id)
*/

function cartao_tables_ok(PDO $pdo): bool {
  return table_exists($pdo, 'cartoes') && table_exists($pdo, 'cartao_movimentos');
}

function cartao_get_by_codigo(PDO $pdo, string $codigo): ?array {
  $st = $pdo->prepare("SELECT * FROM cartoes WHERE codigo=? LIMIT 1");
  $st->execute([$codigo]);
  $r = $st->fetch();
  return $r ? $r : null;
}

function cartao_saldo_sessao(PDO $pdo, int $cartaoId, ?string $abertoEm): float {
  // saldo da sessão atual (desde aberto_em). Se aberto_em nulo, soma tudo.
  $where = "cartao_id = ?";
  $params = [$cartaoId];
  if ($abertoEm) {
    $where .= " AND criado_em >= ?";
    $params[] = $abertoEm;
  }

  $st = $pdo->prepare("\n    SELECT\n      COALESCE(SUM(CASE WHEN operacao='C' THEN valor ELSE 0 END),0) AS cred,\n      COALESCE(SUM(CASE WHEN operacao='D' THEN valor ELSE 0 END),0) AS deb\n    FROM cartao_movimentos\n    WHERE {$where}\n  ");
  $st->execute($params);
  $r = $st->fetch() ?: [];
  $saldo = (float)($r['cred'] ?? 0) - (float)($r['deb'] ?? 0);
  return money_float($saldo);
}

function cartao_assert_ok(PDO $pdo) {
  if (!cartao_tables_ok($pdo)) {
    http_response_code(500);
    echo json_encode(["ok"=>false, "error"=>"Tabelas cartoes/cartao_movimentos não encontradas."], JSON_UNESCAPED_UNICODE);
    exit;
  }
}

// GET ?action=cartao_buscar&codigo=XXXX
if ($action === 'cartao_buscar') {
  cartao_assert_ok($pdo);
  $codigo = trim((string)($_GET['codigo'] ?? ''));
  if ($codigo === '') {
    http_response_code(400);
    echo json_encode(["ok"=>false, "error"=>"codigo inválido"], JSON_UNESCAPED_UNICODE);
    exit;
  }

  $c = cartao_get_by_codigo($pdo, $codigo);
  if (!$c) {
    echo json_encode(["ok"=>true, "existe"=>false], JSON_UNESCAPED_UNICODE);
    exit;
  }

  $saldo = cartao_saldo_sessao($pdo, (int)$c['id'], $c['aberto_em'] ?? null);
  echo json_encode(["ok"=>true, "existe"=>true, "cartao"=>[
    "id"=>(int)$c['id'],
    "codigo"=>$c['codigo'],
    "status"=>$c['status'],
    "nome"=>$c['nome'],
    "celular"=>$c['celular'],
    "aberto_em"=>$c['aberto_em'],
    "fechado_em"=>$c['fechado_em'],
    "saldo"=>$saldo,
  ]], JSON_UNESCAPED_UNICODE);
  exit;
}

// GET ?action=cartao_movimentos&codigo=XXXX
if ($action === 'cartao_movimentos') {
  cartao_assert_ok($pdo);
  $codigo = trim((string)($_GET['codigo'] ?? ''));
  if ($codigo === '') {
    http_response_code(400);
    echo json_encode(["ok"=>false, "error"=>"codigo inválido"], JSON_UNESCAPED_UNICODE);
    exit;
  }

  $c = cartao_get_by_codigo($pdo, $codigo);
  if (!$c) {
    http_response_code(404);
    echo json_encode(["ok"=>false, "error"=>"Cartão não encontrado"], JSON_UNESCAPED_UNICODE);
    exit;
  }

  $aberto = $c['aberto_em'] ?? null;
  $params = [(int)$c['id']];
  $where = "m.cartao_id = ?";
  if ($aberto) {
    $where .= " AND m.criado_em >= ?";
    $params[] = $aberto;
  }

  $st = $pdo->prepare("\n    SELECT m.id, m.ref_mov_id, m.operacao, m.motivo, m.valor, m.origem, m.observacao, m.criado_em\n    FROM cartao_movimentos m\n    WHERE {$where}\n    ORDER BY m.id DESC\n    LIMIT 200\n  ");
  $st->execute($params);
  $movs = $st->fetchAll() ?: [];

  $saldo = cartao_saldo_sessao($pdo, (int)$c['id'], $aberto);
  echo json_encode(["ok"=>true, "cartao"=>[
    "id"=>(int)$c['id'],
    "codigo"=>$c['codigo'],
    "status"=>$c['status'],
    "nome"=>$c['nome'],
    "celular"=>$c['celular'],
    "aberto_em"=>$c['aberto_em'],
    "fechado_em"=>$c['fechado_em'],
    "saldo"=>$saldo,
  ], "movimentos"=>$movs], JSON_UNESCAPED_UNICODE);
  exit;
}

// POST ?action=cartao_recarregar  {codigo, valor, nome?, celular?, observacao?}
if ($action === 'cartao_recarregar') {
  cartao_assert_ok($pdo);
  $data = json_input();
  $codigo = trim((string)($data['codigo'] ?? ''));
  $valor  = (float)($data['valor'] ?? 0);
  $nome   = trim((string)($data['nome'] ?? ''));
  $cel    = trim((string)($data['celular'] ?? ''));
  $obs    = trim((string)($data['observacao'] ?? ''));

  if ($codigo === '' || $valor <= 0) {
    http_response_code(400);
    echo json_encode(["ok"=>false, "error"=>"Dados inválidos"], JSON_UNESCAPED_UNICODE);
    exit;
  }
  if (mb_strlen($obs) > 255) $obs = mb_substr($obs, 0, 255);
  if (mb_strlen($nome) > 120) $nome = mb_substr($nome, 0, 120);
  if (mb_strlen($cel) > 40) $cel = mb_substr($cel, 0, 40);

  try {
    $caixaId = caixa_get_open_id($pdo);
    if (!$caixaId) {
      http_response_code(409);
      echo json_encode(["ok"=>false, "error"=>"Caixa está fechado. Abra o caixa antes de recarregar."], JSON_UNESCAPED_UNICODE);
      exit;
    }

    $pdo->beginTransaction();

    $c = cartao_get_by_codigo($pdo, $codigo);
    if (!$c) {
      // cria e abre
      $ins = $pdo->prepare("\n        INSERT INTO cartoes (codigo, status, nome, celular, mesa, aberto_em, fechado_em, atualizado_em)\n        VALUES (?, 'EM_USO', ?, ?, NULL, NOW(), NULL, CURRENT_TIMESTAMP)\n      ");
      $ins->execute([$codigo, $nome !== '' ? $nome : null, $cel !== '' ? $cel : null]);
      $cartaoId = (int)$pdo->lastInsertId();

      $c = ["id"=>$cartaoId, "aberto_em"=>date('Y-m-d H:i:s'), "status"=>'EM_USO'];
    } else {
      // bloqueado não pode
      if (($c['status'] ?? '') === 'BLOQUEADO') {
        $pdo->rollBack();
        http_response_code(403);
        echo json_encode(["ok"=>false, "error"=>"Cartão bloqueado"], JSON_UNESCAPED_UNICODE);
        exit;
      }

      // se cartão está fechado (fechado_em preenchido), iniciar nova sessão ao recarregar
      if (!empty($c['fechado_em']) || ($c['status'] ?? '') === 'LIVRE') {
        $up = $pdo->prepare("UPDATE cartoes SET status='EM_USO', aberto_em=NOW(), fechado_em=NULL, atualizado_em=CURRENT_TIMESTAMP WHERE id=?");
        $up->execute([(int)$c['id']]);
        // recarrega c para saldo
        $c = cartao_get_by_codigo($pdo, $codigo);
      } else {
        // mantém EM_USO, só atualiza cadastro se veio algo
        $sets = [];
        $params = [];
        if ($nome !== '') { $sets[] = 'nome=?'; $params[] = $nome; }
        if ($cel !== '')  { $sets[] = 'celular=?'; $params[] = $cel; }
        if ($sets) {
          $params[] = (int)$c['id'];
          $pdo->prepare('UPDATE cartoes SET '.implode(',', $sets).', atualizado_em=CURRENT_TIMESTAMP WHERE id=?')->execute($params);
        }
      }

      $cartaoId = (int)$c['id'];
    }

    // registra recarga (C)
    $insMov = $pdo->prepare("\n      INSERT INTO cartao_movimentos (cartao_id, ref_mov_id, operacao, motivo, valor, origem, observacao, criado_em)\n      VALUES (?, NULL, 'C', 'RECARGA', ?, 'BALCAO', ?, NOW())\n    ");
    $insMov->execute([$cartaoId, money_float($valor), $obs !== '' ? $obs : null]);
    $movId = (int)$pdo->lastInsertId();

    // ✅ entrada de dinheiro no caixa (recarga de cartão no balcão)
    caixa_add_lancamento(
      $pdo,
      $caixaId,
      $__usuarioId,
      'ENTRADA',
      'RECARGA_CARTAO',
      $valor,
      "Recarga cartão {$codigo}" . ($obs !== '' ? " • {$obs}" : ""),
      'cartao_movimentos',
      $movId
    );

    $pdo->commit();

    $c2 = cartao_get_by_codigo($pdo, $codigo);
    $saldo = cartao_saldo_sessao($pdo, (int)$c2['id'], $c2['aberto_em'] ?? null);
    echo json_encode(["ok"=>true, "saldo"=>$saldo], JSON_UNESCAPED_UNICODE);
  } catch (Throwable $e) {
    if ($pdo->inTransaction()) $pdo->rollBack();
    http_response_code(500);
    echo json_encode(["ok"=>false, "error"=>"Erro interno: " . $e->getMessage()], JSON_UNESCAPED_UNICODE);
  }
  exit;
}

// POST ?action=cartao_fechar  {codigo}
if ($action === 'cartao_fechar') {
  cartao_assert_ok($pdo);
  $data = json_input();
  $codigo = trim((string)($data['codigo'] ?? ''));
  if ($codigo === '') {
    http_response_code(400);
    echo json_encode(["ok"=>false, "error"=>"codigo inválido"], JSON_UNESCAPED_UNICODE);
    exit;
  }

  $c = cartao_get_by_codigo($pdo, $codigo);
  if (!$c) {
    http_response_code(404);
    echo json_encode(["ok"=>false, "error"=>"Cartão não encontrado"], JSON_UNESCAPED_UNICODE);
    exit;
  }
  if (($c['status'] ?? '') === 'BLOQUEADO') {
    http_response_code(403);
    echo json_encode(["ok"=>false, "error"=>"Cartão bloqueado"], JSON_UNESCAPED_UNICODE);
    exit;
  }
  if (!empty($c['fechado_em']) || ($c['status'] ?? '') === 'LIVRE') {
    echo json_encode(["ok"=>true]);
    exit;
  }

  try {
    $up = $pdo->prepare("UPDATE cartoes SET status='LIVRE', fechado_em=NOW(), atualizado_em=CURRENT_TIMESTAMP WHERE id=?");
    $up->execute([(int)$c['id']]);
    echo json_encode(["ok"=>true], JSON_UNESCAPED_UNICODE);
  } catch (Throwable $e) {
    http_response_code(500);
    echo json_encode(["ok"=>false, "error"=>"Erro interno: " . $e->getMessage()], JSON_UNESCAPED_UNICODE);
  }
  exit;
}

// POST ?action=cartao_estornar  {mov_id}
if ($action === 'cartao_estornar') {
  cartao_assert_ok($pdo);
  $data = json_input();
  $movId = (int)($data['mov_id'] ?? 0);
  if ($movId <= 0) {
    http_response_code(400);
    echo json_encode(["ok"=>false, "error"=>"mov_id inválido"], JSON_UNESCAPED_UNICODE);
    exit;
  }

  try {
    // carrega mov + cartão
    $st = $pdo->prepare("\n      SELECT m.*, c.codigo, c.status AS c_status, c.aberto_em, c.fechado_em\n      FROM cartao_movimentos m\n      JOIN cartoes c ON c.id = m.cartao_id\n      WHERE m.id=?\n      LIMIT 1\n    ");
    $st->execute([$movId]);
    $m = $st->fetch();
    if (!$m) {
      http_response_code(404);
      echo json_encode(["ok"=>false, "error"=>"Movimento não encontrado"], JSON_UNESCAPED_UNICODE);
      exit;
    }

    if (($m['c_status'] ?? '') === 'BLOQUEADO') {
      http_response_code(403);
      echo json_encode(["ok"=>false, "error"=>"Cartão bloqueado"], JSON_UNESCAPED_UNICODE);
      exit;
    }

    // ✅ não estorna se cartão está fechado
    if (!empty($m['fechado_em']) || ($m['c_status'] ?? '') === 'LIVRE') {
      http_response_code(403);
      echo json_encode(["ok"=>false, "error"=>"Cartão fechado. Estorno não permitido."], JSON_UNESCAPED_UNICODE);
      exit;
    }

    // só permite estorno dentro da sessão atual
    $aberto = $m['aberto_em'] ?? null;
    if ($aberto && !empty($m['criado_em']) && $m['criado_em'] < $aberto) {
      http_response_code(403);
      echo json_encode(["ok"=>false, "error"=>"Movimento não pertence à sessão atual do cartão."], JSON_UNESCAPED_UNICODE);
      exit;
    }

    // evita estornar estorno
    if (($m['motivo'] ?? '') === 'ESTORNO') {
      http_response_code(400);
      echo json_encode(["ok"=>false, "error"=>"Não é possível estornar um estorno."], JSON_UNESCAPED_UNICODE);
      exit;
    }

    // já estornado?
    $chk = $pdo->prepare("SELECT COUNT(*) c FROM cartao_movimentos WHERE ref_mov_id=?");
    $chk->execute([$movId]);
    if ((int)($chk->fetch()['c'] ?? 0) > 0) {
      http_response_code(400);
      echo json_encode(["ok"=>false, "error"=>"Este movimento já foi estornado."], JSON_UNESCAPED_UNICODE);
      exit;
    }

    $op = (string)($m['operacao'] ?? '');
    $valor = (float)($m['valor'] ?? 0);

    // operação inversa
    $inv = ($op === 'C') ? 'D' : 'C';

    // Só afeta caixa quando for estorno de RECARGA feita no balcão (dinheiro saindo)
    $isEstornoRecargaBalcao = (($m['origem'] ?? '') === 'BALCAO' && ($m['motivo'] ?? '') === 'RECARGA' && $op === 'C');

    if ($isEstornoRecargaBalcao) {
      $caixaId = caixa_get_open_id($pdo);
      if (!$caixaId) {
        http_response_code(409);
        echo json_encode(["ok"=>false, "error"=>"Caixa está fechado. Abra o caixa antes de estornar recarga."], JSON_UNESCAPED_UNICODE);
        exit;
      }
    }

    $pdo->beginTransaction();

    // se for debito, conferir saldo antes de estornar? (estorno vira crédito, não precisa)
    // se for crédito, estorno vira débito -> precisa checar saldo
    if ($inv === 'D') {
      $saldo = cartao_saldo_sessao($pdo, (int)$m['cartao_id'], $aberto);
      if ($valor > ($saldo + 0.001)) {
        $pdo->rollBack();
        http_response_code(400);
        echo json_encode(["ok"=>false, "error"=>"Saldo insuficiente para estornar uma recarga (saldo: {$saldo})."], JSON_UNESCAPED_UNICODE);
        exit;
      }
    }

    $obs = 'Estorno do mov #' . $movId;
    $ins = $pdo->prepare("\n      INSERT INTO cartao_movimentos (cartao_id, ref_mov_id, operacao, motivo, valor, origem, observacao, criado_em)\n      VALUES (?, ?, ?, 'ESTORNO', ?, 'BALCAO', ?, NOW())\n    ");
    $ins->execute([(int)$m['cartao_id'], $movId, $inv, money_float($valor), $obs]);
    $estMovId = (int)$pdo->lastInsertId();

    $pdo->prepare("UPDATE cartoes SET atualizado_em=CURRENT_TIMESTAMP WHERE id=?")->execute([(int)$m['cartao_id']]);

    if ($isEstornoRecargaBalcao) {
      // ✅ saída de dinheiro do caixa (devolução de recarga)
      caixa_add_lancamento(
        $pdo,
        $caixaId,
        $__usuarioId,
        'SAIDA',
        'ESTORNO_RECARGA',
        $valor,
        "Estorno recarga cartão {$m['codigo']} (mov #{$movId})",
        'cartao_movimentos',
        $estMovId
      );
    }

    $pdo->commit();

    $saldo2 = cartao_saldo_sessao($pdo, (int)$m['cartao_id'], $aberto);
    echo json_encode(["ok"=>true, "saldo"=>$saldo2], JSON_UNESCAPED_UNICODE);
  } catch (Throwable $e) {
    if ($pdo->inTransaction()) $pdo->rollBack();
    http_response_code(500);
    echo json_encode(["ok"=>false, "error"=>"Erro interno: " . $e->getMessage()], JSON_UNESCAPED_UNICODE);
  }
  exit;
}

http_response_code(404);
echo json_encode(["ok" => false, "error" => "Ação não encontrada"]);
