<?php
declare(strict_types=1);

function spE(mixed $valor): string
{
    return htmlspecialchars((string) $valor, ENT_QUOTES, 'UTF-8');
}

function spTexto(mixed $valor, int $limite = 255): string
{
    $texto = trim((string) $valor);
    $texto = preg_replace('/\s+/u', ' ', $texto) ?? $texto;

    if (function_exists('mb_substr')) {
        return mb_substr($texto, 0, $limite, 'UTF-8');
    }

    return substr($texto, 0, $limite);
}

function spNulo(mixed $valor, int $limite = 255): ?string
{
    $texto = spTexto($valor, $limite);
    return $texto === '' ? null : $texto;
}

function spTabelaExiste(mysqli $mysqli, string $tabela): bool
{
    $stmt = $mysqli->prepare("
        SELECT COUNT(*) AS total
        FROM information_schema.tables
        WHERE table_schema = DATABASE()
          AND table_name = ?
    ");

    if (!$stmt) {
        return false;
    }

    $stmt->bind_param('s', $tabela);
    $stmt->execute();
    $linha = $stmt->get_result()->fetch_assoc();
    $stmt->close();

    return (int) ($linha['total'] ?? 0) > 0;
}

function spColunaExiste(
    mysqli $mysqli,
    string $tabela,
    string $coluna
): bool {
    $stmt = $mysqli->prepare("
        SELECT COUNT(*) AS total
        FROM information_schema.columns
        WHERE table_schema = DATABASE()
          AND table_name = ?
          AND column_name = ?
    ");

    if (!$stmt) {
        return false;
    }

    $stmt->bind_param('ss', $tabela, $coluna);
    $stmt->execute();
    $linha = $stmt->get_result()->fetch_assoc();
    $stmt->close();

    return (int) ($linha['total'] ?? 0) > 0;
}

function spToken(): string
{
    return bin2hex(random_bytes(32));
}

function spProtocolo(): string
{
    return sprintf(
        'SOL-%s-%04d',
        date('Ymd-His'),
        random_int(1, 9999)
    );
}

function spNumeroOs(): string
{
    return sprintf(
        'MANU-%s-%s',
        date('Ymd-His'),
        strtoupper(substr(bin2hex(random_bytes(3)), 0, 6))
    );
}

function spBaseUrl(): string
{
    $https = (
        (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ||
        strtolower((string) ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '')) === 'https'
    );

    $scheme = $https ? 'https' : 'http';
    $host = (string) ($_SERVER['HTTP_HOST'] ?? 'localhost');

    if (!preg_match('/^[A-Za-z0-9.:-]+$/', $host)) {
        $host = 'localhost';
    }

    $script = str_replace('\\', '/', (string) ($_SERVER['SCRIPT_NAME'] ?? '/'));
    $dir = rtrim(dirname($script), '/.');

    return $scheme . '://' . $host . ($dir !== '' ? $dir : '');
}

function spUrlAcompanhamento(string $token): string
{
    return spBaseUrl() .
        '/acompanhar_solicitacao.php?token=' .
        rawurlencode($token);
}

function spData(?string $data, string $vazio = 'Não informada'): string
{
    $texto = trim((string) $data);

    if ($texto === '') {
        return $vazio;
    }

    $timestamp = strtotime($texto);

    return $timestamp === false
        ? $vazio
        : date('d/m/Y \à\s H:i', $timestamp);
}

function spStatus(string $status): array
{
    return match (strtoupper(trim($status))) {
        'RECEBIDA' => [
            'rotulo' => 'Equipamento recebido',
            'classe' => 'received',
            'icone' => 'bi-tools',
            'etapa' => 2,
            'descricao' => 'A Gerência de Tecnologia confirmou a chegada do equipamento. O prazo técnico começou a contar.'
        ],
        'CANCELADA' => [
            'rotulo' => 'Solicitação cancelada',
            'classe' => 'cancelled',
            'icone' => 'bi-x-octagon-fill',
            'etapa' => 1,
            'descricao' => 'A solicitação foi encerrada antes do recebimento do equipamento.'
        ],
        default => [
            'rotulo' => 'Aguardando entrega na TI',
            'classe' => 'waiting',
            'icone' => 'bi-box-arrow-in-down',
            'etapa' => 1,
            'descricao' => 'A solicitação foi registrada. O prazo da OS ainda não começou e só será iniciado quando a equipe confirmar a chegada do equipamento.'
        ]
    };
}

function spCsrfPublico(): string
{
    if (session_status() !== PHP_SESSION_ACTIVE) {
        session_start();
    }

    if (empty($_SESSION['csrf_solicitacao_publica'])) {
        $_SESSION['csrf_solicitacao_publica'] = bin2hex(random_bytes(24));
    }

    return (string) $_SESSION['csrf_solicitacao_publica'];
}

function spValidarCsrfPublico(): void
{
    $recebido = (string) ($_POST['csrf_token'] ?? '');
    $esperado = (string) ($_SESSION['csrf_solicitacao_publica'] ?? '');

    if (
        $recebido === '' ||
        $esperado === '' ||
        !hash_equals($esperado, $recebido)
    ) {
        throw new RuntimeException(
            'A sessão do formulário expirou. Atualize a página e tente novamente.'
        );
    }
}

function spCsrfTecnico(): string
{
    if (session_status() !== PHP_SESSION_ACTIVE) {
        session_start();
    }

    if (empty($_SESSION['csrf_solicitacao_tecnico'])) {
        $_SESSION['csrf_solicitacao_tecnico'] = bin2hex(random_bytes(24));
    }

    return (string) $_SESSION['csrf_solicitacao_tecnico'];
}

function spValidarCsrfTecnico(): void
{
    $recebido = (string) ($_POST['csrf_token'] ?? '');
    $esperado = (string) ($_SESSION['csrf_solicitacao_tecnico'] ?? '');

    if (
        $recebido === '' ||
        $esperado === '' ||
        !hash_equals($esperado, $recebido)
    ) {
        throw new RuntimeException(
            'A sessão expirou. Atualize a página e tente novamente.'
        );
    }
}

function spSalvarAnexo(array $arquivo): array
{
    if (
        empty($arquivo) ||
        (int) ($arquivo['error'] ?? UPLOAD_ERR_NO_FILE) === UPLOAD_ERR_NO_FILE
    ) {
        return [
            'caminho' => null,
            'nome_original' => null,
            'mime' => null,
            'tamanho' => 0,
            'hash' => null
        ];
    }

    if ((int) ($arquivo['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
        throw new RuntimeException('Não foi possível receber o anexo.');
    }

    $tamanho = (int) ($arquivo['size'] ?? 0);

    if ($tamanho <= 0 || $tamanho > 8 * 1024 * 1024) {
        throw new RuntimeException('O anexo deve possuir no máximo 8 MB.');
    }

    $temporario = (string) ($arquivo['tmp_name'] ?? '');
    $finfo = new finfo(FILEINFO_MIME_TYPE);
    $mime = (string) ($finfo->file($temporario) ?: '');

    $permitidos = [
        'image/jpeg' => 'jpg',
        'image/png' => 'png',
        'application/pdf' => 'pdf'
    ];

    if (!isset($permitidos[$mime])) {
        throw new RuntimeException(
            'Envie somente arquivos PDF, JPG ou PNG.'
        );
    }

    $pastaRelativa = 'uploads/solicitacoes_publicas/' . date('Y/m');
    $pastaAbsoluta = __DIR__ . '/' . $pastaRelativa;

    if (
        !is_dir($pastaAbsoluta) &&
        !mkdir($pastaAbsoluta, 0775, true) &&
        !is_dir($pastaAbsoluta)
    ) {
        throw new RuntimeException(
            'Não foi possível preparar a pasta de anexos.'
        );
    }

    $nomeArquivo = bin2hex(random_bytes(18)) . '.' . $permitidos[$mime];
    $destino = $pastaAbsoluta . '/' . $nomeArquivo;

    if (!move_uploaded_file($temporario, $destino)) {
        throw new RuntimeException('Não foi possível salvar o anexo.');
    }

    return [
        'caminho' => $pastaRelativa . '/' . $nomeArquivo,
        'nome_original' => spTexto($arquivo['name'] ?? 'anexo', 255),
        'mime' => $mime,
        'tamanho' => $tamanho,
        'hash' => hash_file('sha256', $destino)
    ];
}

function spFlash(string $tipo, string $mensagem): void
{
    $_SESSION['solicitacao_publica_flash'] = [
        'tipo' => $tipo,
        'mensagem' => $mensagem
    ];
}

function spConsumirFlash(): ?array
{
    $flash = $_SESSION['solicitacao_publica_flash'] ?? null;
    unset($_SESSION['solicitacao_publica_flash']);

    return is_array($flash) ? $flash : null;
}

function spBuscarPorToken(mysqli $mysqli, string $token): ?array
{
    $stmt = $mysqli->prepare("
        SELECT
            s.*,
            os.numero_os,
            os.status AS os_status,
            os.token_acompanhamento AS os_token
        FROM solicitacoes_os_publicas AS s
        LEFT JOIN ordens_servico AS os
            ON os.id = s.os_id
        WHERE s.token_publico = ?
        LIMIT 1
    ");

    if (!$stmt) {
        return null;
    }

    $stmt->bind_param('s', $token);
    $stmt->execute();
    $linha = $stmt->get_result()->fetch_assoc();
    $stmt->close();

    return $linha ?: null;
}

function spBuscarPorId(
    mysqli $mysqli,
    int $id,
    bool $forUpdate = false
): ?array {
    $sql = "
        SELECT *
        FROM solicitacoes_os_publicas
        WHERE id = ?
        LIMIT 1
    ";

    if ($forUpdate) {
        $sql .= ' FOR UPDATE';
    }

    $stmt = $mysqli->prepare($sql);

    if (!$stmt) {
        return null;
    }

    $stmt->bind_param('i', $id);
    $stmt->execute();
    $linha = $stmt->get_result()->fetch_assoc();
    $stmt->close();

    return $linha ?: null;
}

function spPrioridadeSugerida(string $defeito): array
{
    $texto = function_exists('mb_strtolower')
        ? mb_strtolower($defeito, 'UTF-8')
        : strtolower($defeito);

    foreach ([
        'pegou fogo', 'incêndio', 'incendio', 'choque',
        'fumaça', 'fumaca', 'servidor parado', 'setor inteiro parado'
    ] as $termo) {
        if (str_contains($texto, $termo)) {
            return ['prioridade' => 'Urgente', 'prazo' => 1];
        }
    }

    foreach ([
        'não liga', 'nao liga', 'tela azul', 'sem internet',
        'não inicia', 'nao inicia', 'impressora parada'
    ] as $termo) {
        if (str_contains($texto, $termo)) {
            return ['prioridade' => 'Alta', 'prazo' => 2];
        }
    }

    return ['prioridade' => 'Média', 'prazo' => 5];
}
