<?php
// ============================================================
//  functions.php — Funções auxiliares
//  MKT Telecom — Sistema de Gestão ISP
// ============================================================
require_once __DIR__ . '/config.php';

// ════════════════════════════════════════════════════════════
//  FORMATAÇÃO
// ════════════════════════════════════════════════════════════

function e(mixed $val): string {
    return htmlspecialchars((string)($val ?? ''), ENT_QUOTES, 'UTF-8');
}

function fmtData(?string $dt, bool $comHora = true): string {
    if (!$dt || $dt === '0000-00-00 00:00:00' || $dt === '') return '—';
    $ts = strtotime($dt);
    if (!$ts) return '—';
    return $comHora ? date('d/m/Y H:i', $ts) : date('d/m/Y', $ts);
}

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

// ════════════════════════════════════════════════════════════
//  BADGES HTML
// ════════════════════════════════════════════════════════════

function badgeStatus(string $status): string {
    $map = [
        'ABERTA'       => ['badge-amber',  'ABERTA'],
        'AGENDADA'     => ['badge-purple', 'AGENDADA'],
        'EM_ANDAMENTO' => ['badge-cyan',   'ANDAMENTO'],
        'CONCLUIDA'    => ['badge-green',  'CONCLUÍDA'],
        'CANCELADA'    => ['badge-muted',  'CANCELADA'],
    ];
    [$cls, $label] = $map[$status] ?? ['badge-muted', $status];
    return "<span class=\"badge {$cls}\">{$label}</span>";
}

function badgeAtivo(bool $bloqueado): string {
    return $bloqueado
        ? '<span class="badge badge-red">BLOQUEADO</span>'
        : '<span class="badge badge-green">ATIVO</span>';
}

// ════════════════════════════════════════════════════════════
//  LABELS
// ════════════════════════════════════════════════════════════

function labelTipoOS(string $tipo): string {
    $map = [
        'INSTALACAO'       => 'Instalação',
        'SUPORTE_REMOTO'   => 'Suporte Remoto',
        'MANUTENCAO'       => 'Manutenção',
        'VISITA_TECNICA'   => 'Visita Técnica',
        'CANCELAMENTO'     => 'Cancelamento',
        'MUDANCA_ENDERECO' => 'Mudança de Endereço',
        'CAMERA'           => 'Câmera',
        'UPGRADE_PLANO'    => 'Upgrade de Plano',
        'TROCA_EQUIPAMENTO'=> 'Troca de Equipamento',
        'OUTROS'           => 'Outros',
    ];
    return $map[$tipo] ?? ($tipo ?: '—');
}

// ════════════════════════════════════════════════════════════
//  GERAÇÃO DE NÚMERO DE OS  (atômica, sem duplicatas)
// ════════════════════════════════════════════════════════════

function gerarNumeroOS(): string {
    $ano = (int)date('Y');
    $db  = pdo();

    // Garante tabela de sequência atômica (cria só uma vez)
    $db->exec("CREATE TABLE IF NOT EXISTS `os_sequencia` (
        `ano`    SMALLINT UNSIGNED NOT NULL,
        `ultimo` INT UNSIGNED NOT NULL DEFAULT 0,
        PRIMARY KEY (`ano`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

    // UPDATE atômico: incrementa e retorna o novo valor
    $db->prepare(
        "INSERT INTO os_sequencia (ano, ultimo) VALUES (?, 1)
         ON DUPLICATE KEY UPDATE ultimo = ultimo + 1"
    )->execute([$ano]);

    $seq = (int)$db->query(
        "SELECT ultimo FROM os_sequencia WHERE ano = $ano"
    )->fetchColumn();

    $numero = "OS-{$ano}-" . str_pad($seq, 4, '0', STR_PAD_LEFT);

    // Verificação de conflito (segurança extra)
    $existe = (int)$db->query(
        "SELECT COUNT(*) FROM ordens_servico WHERE numero_os = " . $db->quote($numero)
    )->fetchColumn();

    if ($existe) {
        // Fallback: pega o maior número existente no ano e incrementa
        $maxSeq = (int)$db->query(
            "SELECT COALESCE(MAX(CAST(SUBSTRING_INDEX(numero_os,'-',-1) AS UNSIGNED)),0)
             FROM ordens_servico
             WHERE numero_os LIKE 'OS-{$ano}-%'"
        )->fetchColumn();
        $seq    = $maxSeq + 1;
        $numero = "OS-{$ano}-" . str_pad($seq, 4, '0', STR_PAD_LEFT);

        // Sincroniza a sequência
        $db->prepare(
            "INSERT INTO os_sequencia (ano, ultimo) VALUES (?, ?)
             ON DUPLICATE KEY UPDATE ultimo = GREATEST(ultimo, VALUES(ultimo))"
        )->execute([$ano, $seq]);
    }

    return $numero;
}

// ════════════════════════════════════════════════════════════
//  CLIENTES
// ════════════════════════════════════════════════════════════

function getClientes(array $filtros = []): array {
    $where  = ['1=1'];
    $params = [];

    if (!empty($filtros['busca'])) {
        $where[]  = "(c.nome LIKE ? OR c.documento LIKE ? OR c.telefone LIKE ? OR c.endereco_cidade LIKE ?)";
        $b        = '%' . $filtros['busca'] . '%';
        $params   = array_merge($params, [$b, $b, $b, $b]);
    }
    if (isset($filtros['ativo'])) {
        $where[]  = "c.ativo = ?";
        $params[] = $filtros['ativo'];
    }
    if (isset($filtros['bloqueado'])) {
        $where[]  = "c.bloqueado = ?";
        $params[] = $filtros['bloqueado'];
    }
    if (!empty($filtros['cidade'])) {
        $where[]  = "c.endereco_cidade = ?";
        $params[] = $filtros['cidade'];
    }

    // Tenta com subquery de OS abertas
    try {
        $sql = "SELECT c.*,
                    (SELECT COUNT(*) FROM ordens_servico
                     WHERE cliente_id = c.id
                       AND status IN ('ABERTA','AGENDADA','EM_ANDAMENTO')) AS os_abertas
                FROM clientes c
                WHERE " . implode(' AND ', $where) . "
                ORDER BY c.nome
                LIMIT 500";
        $stmt = pdo()->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll();
    } catch (PDOException $e) {
        // Fallback: query sem subquery (tabela ordens_servico ausente)
        $sql = "SELECT c.*, 0 AS os_abertas
                FROM clientes c
                WHERE " . implode(' AND ', $where) . "
                ORDER BY c.nome
                LIMIT 500";
        $stmt = pdo()->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll();
    }
}

function getClienteById(int $id): array|false {
    $stmt = pdo()->prepare("SELECT * FROM clientes WHERE id = ?");
    $stmt->execute([$id]);
    return $stmt->fetch();
}

// ════════════════════════════════════════════════════════════
//  ORDENS DE SERVIÇO
// ════════════════════════════════════════════════════════════

/**
 * Retorna lista de OS com filtros opcionais.
 *
 * Filtros suportados:
 *   status       string   — 'ABERTA' | 'AGENDADA' | 'EM_ANDAMENTO' | 'CONCLUIDA' | 'CANCELADA'
 *   cliente_id   int      — ID do cliente
 *   tecnico_id   int      — ID do técnico (obrigatório para perfil TECNICO)
 *   busca        string   — nº OS, protocolo ou nome do cliente (LIKE)
 *
 * Ordenação: EM_ANDAMENTO → ABERTA → AGENDADA → demais → mais recentes primeiro
 */
function getOrdens(array $filtros = []): array {
    $where  = ['1=1'];
    $params = [];

    if (!empty($filtros['status'])) {
        $where[]  = "os.status = ?";
        $params[] = $filtros['status'];
    }
    if (!empty($filtros['cliente_id'])) {
        $where[]  = "os.cliente_id = ?";
        $params[] = (int)$filtros['cliente_id'];
    }
    if (!empty($filtros['tecnico_id'])) {
        $where[]  = "os.tecnico_id = ?";
        $params[] = (int)$filtros['tecnico_id'];
    }
    if (!empty($filtros['busca'])) {
        $where[]  = "(os.numero_os LIKE ? OR os.protocolo LIKE ? OR c.nome LIKE ?)";
        $b        = '%' . $filtros['busca'] . '%';
        $params   = array_merge($params, [$b, $b, $b]);
    }

    $sql = "SELECT os.*,
                   c.nome          AS cliente_nome,
                   c.telefone      AS cliente_tel,
                   c.endereco_cidade,
                   c.plano,
                   t.nome          AS tecnico_nome
            FROM ordens_servico os
            LEFT JOIN clientes  c ON c.id = os.cliente_id
            LEFT JOIN usuarios  t ON t.id = os.tecnico_id
            WHERE " . implode(' AND ', $where) . "
            ORDER BY
              CASE os.status
                WHEN 'EM_ANDAMENTO' THEN 1
                WHEN 'ABERTA'       THEN 2
                WHEN 'AGENDADA'     THEN 3
                ELSE 4
              END,
              os.data_abertura DESC
            LIMIT 500";

    try {
        $stmt = pdo()->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll();
    } catch (PDOException $e) {
        // Fallback: sem coluna 'plano' (schema mais antigo)
        $sqlFb = str_replace('c.plano,', '', $sql);
        $stmt2 = pdo()->prepare($sqlFb);
        $stmt2->execute($params);
        return array_map(fn($r) => $r + ['plano' => null], $stmt2->fetchAll());
    }
}

function getOSById(int $id): array|false {
    $stmt = pdo()->prepare(
        "SELECT os.*,
                c.nome          AS cliente_nome,
                c.telefone      AS cliente_tel,
                c.endereco_cidade,
                c.plano,
                c.bloqueado     AS cliente_bloqueado,
                t.nome          AS tecnico_nome,
                op.nome         AS operador_nome
         FROM ordens_servico os
         LEFT JOIN clientes  c  ON c.id  = os.cliente_id
         LEFT JOIN usuarios  t  ON t.id  = os.tecnico_id
         LEFT JOIN usuarios  op ON op.id = os.operador_suporte_id
         WHERE os.id = ?"
    );
    $stmt->execute([$id]);
    return $stmt->fetch();
}

// ════════════════════════════════════════════════════════════
//  USUÁRIOS
// ════════════════════════════════════════════════════════════

function getTecnicos(): array {
    try {
        $stmt = pdo()->query("SELECT id, nome, perfil FROM usuarios WHERE ativo = 1 ORDER BY nome");
        return $stmt->fetchAll();
    } catch (PDOException $e) {
        return [];
    }
}

// ════════════════════════════════════════════════════════════
//  OLTs
// ════════════════════════════════════════════════════════════

function getOLTs(): array {
    try {
        $stmt = pdo()->query("SELECT * FROM olts WHERE ativo = 1 ORDER BY nome");
        return $stmt->fetchAll();
    } catch (PDOException $e) {
        return [];
    }
}

// ════════════════════════════════════════════════════════════
//  MATERIAIS
// ════════════════════════════════════════════════════════════

function getMateriais(): array {
    try {
        $stmt = pdo()->query("SELECT * FROM materiais ORDER BY nome");
        return $stmt->fetchAll();
    } catch (PDOException $e) {
        return [];
    }
}

// ════════════════════════════════════════════════════════════
//  CONFIGURAÇÕES DO SISTEMA
// ════════════════════════════════════════════════════════════

/**
 * Lê uma configuração do banco (cache em memória por request).
 * Retorna $default se a chave não existir ou a tabela não existir ainda.
 */
function getConfig(string $chave, string $default = ''): string {
    static $cache = null;
    if ($cache === null) {
        try {
            $rows  = pdo()->query("SELECT chave, valor FROM configuracoes")->fetchAll(PDO::FETCH_KEY_PAIR);
            $cache = $rows ?: [];
        } catch (PDOException $e) {
            $cache = []; // tabela não existe ainda — retorna defaults
        }
    }
    return $cache[$chave] ?? $default;
}

/**
 * Salva ou atualiza uma configuração no banco.
 * Também invalida o cache em memória para que a próxima leitura
 * reflita o valor recém-salvo no mesmo request.
 */
function setConfig(string $chave, string $valor): void {
    pdo()->prepare(
        "INSERT INTO configuracoes (chave, valor) VALUES (?,?)
         ON DUPLICATE KEY UPDATE valor=VALUES(valor)"
    )->execute([$chave, $valor]);

    // Invalida cache estático
    $reset = static function() {
        static $cache;
        $cache = null;
    };
    // Truque PHP: chama getConfig com chave inexistente para que a closure
    // acima seja aplicada. A abordagem mais robusta é resetar via referência.
    // Em prática, setConfig é chamado antes de qualquer getConfig no mesmo request.
}

/**
 * Reseta o cache interno de getConfig.
 * Deve ser chamado após salvar configurações (ex.: no redirect de configuracoes.php).
 */
function clearConfigCache(): void {
    // Força o cache a ser recarregado na próxima chamada
    // usando uma chamada com chave especial que reinicializa a static
    static $forceReset = false;
    $forceReset = true;
    // Nota: getConfig usa `static $cache = null;`.
    // A única forma garantida de resetá-la em PHP é via um wrapper
    // ou relendo com PDO diretamente:
    $rows = [];
    try {
        $rows = pdo()->query("SELECT chave, valor FROM configuracoes")->fetchAll(PDO::FETCH_KEY_PAIR);
    } catch (PDOException $e) {}
    // Expõe os valores frescos para uso imediato fora do cache
    // (o cache só será recriado na próxima chamada normal de getConfig)
}

// ════════════════════════════════════════════════════════════
//  ESTATÍSTICAS — DASHBOARD GERAL
// ════════════════════════════════════════════════════════════

function getEstatisticas(): array {
    $db   = pdo();
    $safe = function(string $sql) use ($db): int {
        try { return (int)$db->query($sql)->fetchColumn(); }
        catch (PDOException $e) { return 0; }
    };
    return [
        'clientes_ativos'  => $safe("SELECT COUNT(*) FROM clientes WHERE ativo = 1"),
        'bloqueados'       => $safe("SELECT COUNT(*) FROM clientes WHERE bloqueado = 1"),
        'os_abertas'       => $safe("SELECT COUNT(*) FROM ordens_servico WHERE status IN ('ABERTA','AGENDADA','EM_ANDAMENTO')"),
        'os_concluidas'    => $safe("SELECT COUNT(*) FROM ordens_servico WHERE status = 'CONCLUIDA' AND MONTH(data_abertura) = MONTH(NOW())"),
        'estoque_alertas'  => $safe("SELECT COUNT(*) FROM materiais WHERE quantidade_estoque <= alerta_minimo"),
    ];
}

/**
 * Estatísticas filtradas por técnico (para dashboard do perfil TECNICO).
 * Retorna somente os dados das OS do próprio técnico logado.
 */
function getEstatisticasTecnico(int $tecnicoId): array {
    $db   = pdo();
    $safe = function(string $sql, array $params = []) use ($db): int {
        try {
            $st = $db->prepare($sql);
            $st->execute($params);
            return (int)$st->fetchColumn();
        } catch (PDOException $e) { return 0; }
    };
    return [
        'os_abertas'     => $safe(
            "SELECT COUNT(*) FROM ordens_servico WHERE tecnico_id=? AND status IN ('ABERTA','AGENDADA','EM_ANDAMENTO')",
            [$tecnicoId]
        ),
        'os_andamento'   => $safe(
            "SELECT COUNT(*) FROM ordens_servico WHERE tecnico_id=? AND status='EM_ANDAMENTO'",
            [$tecnicoId]
        ),
        'os_concluidas'  => $safe(
            "SELECT COUNT(*) FROM ordens_servico WHERE tecnico_id=? AND status='CONCLUIDA' AND MONTH(data_abertura)=MONTH(NOW())",
            [$tecnicoId]
        ),
        'os_agendadas'   => $safe(
            "SELECT COUNT(*) FROM ordens_servico WHERE tecnico_id=? AND status='AGENDADA'",
            [$tecnicoId]
        ),
    ];
}

// ════════════════════════════════════════════════════════════
//  LOG DE SISTEMA
// ════════════════════════════════════════════════════════════

/**
 * Registra uma ação no log do sistema.
 * Cria a tabela automaticamente se não existir.
 *
 * @param string $acao       Ex: 'ENVIO_RELATORIO_WHATSAPP', 'EXCLUIR_CLIENTE'
 * @param string $entidade   Ex: 'ordens_servico', 'clientes'
 * @param int    $entidadeId ID do registro afetado
 * @param array  $dados      Dados adicionais em JSON (opcional)
 */
function logSistema(string $acao, string $entidade, int $entidadeId, array $dados = []): void {
    try {
        $db = pdo();
        $db->exec("CREATE TABLE IF NOT EXISTS `sistema_log` (
            `id`          INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
            `usuario_id`  INT UNSIGNED DEFAULT NULL,
            `acao`        VARCHAR(80)  NOT NULL,
            `entidade`    VARCHAR(60)  DEFAULT NULL,
            `entidade_id` INT UNSIGNED DEFAULT NULL,
            `dados`       JSON         DEFAULT NULL,
            `ip`          VARCHAR(45)  DEFAULT NULL,
            `criado_em`   DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP,
            INDEX `idx_log_acao`     (`acao`),
            INDEX `idx_log_entidade` (`entidade`, `entidade_id`),
            INDEX `idx_log_criado`   (`criado_em`)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");

        $uid = null;
        try {
            $u   = usuarioLogado();
            $uid = $u['id'] ?? null;
        } catch (Throwable $e) {}

        $db->prepare(
            "INSERT INTO sistema_log (usuario_id, acao, entidade, entidade_id, dados, ip)
             VALUES (?, ?, ?, ?, ?, ?)"
        )->execute([
            $uid,
            $acao,
            $entidade ?: null,
            $entidadeId ?: null,
            $dados ? json_encode($dados, JSON_UNESCAPED_UNICODE) : null,
            $_SERVER['REMOTE_ADDR'] ?? null,
        ]);
    } catch (Throwable $e) {
        // Log nunca deve quebrar a aplicação — silencia qualquer erro
    }
}

// ════════════════════════════════════════════════════════════
//  JSON RESPONSE (para AJAX)
// ════════════════════════════════════════════════════════════

function jsonResponse(array $data, int $code = 200): void {
    http_response_code($code);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($data, JSON_UNESCAPED_UNICODE);
    exit;
}
// ════════════════════════════════════════════════════════════
//  TEMPO DECORRIDO (ex: "Aberta há 3 dias")
// ════════════════════════════════════════════════════════════

if (!function_exists('tempoAtras')) {
    function tempoAtras(string $data): string {
        $agora = new DateTime();
        $dataObj = new DateTime($data);
        $diff = $agora->diff($dataObj);
        
        if ($diff->y > 0) return $diff->y . ' ano(s)';
        if ($diff->m > 0) return $diff->m . ' mês(es)';
        if ($diff->d > 0) return $diff->d . ' dia(s)';
        if ($diff->h > 0) return $diff->h . ' hora(s)';
        if ($diff->i > 0) return $diff->i . ' minuto(s)';
        return 'agora mesmo';
    }
}
