<?php
declare(strict_types=1);

require_once __DIR__ . '/auth.php';
require_once __DIR__ . '/conexao.php';

date_default_timezone_set('America/Sao_Paulo');

if ($mysqli instanceof mysqli) {
    $mysqli->set_charset('utf8mb4');
}

if (session_status() !== PHP_SESSION_ACTIVE) {
    session_start();
}

function responder(array $dados, int $status = 200): never
{
    http_response_code($status);
    header('Content-Type: application/json; charset=utf-8');

    echo json_encode(
        $dados,
        JSON_UNESCAPED_UNICODE |
        JSON_UNESCAPED_SLASHES |
        JSON_HEX_TAG |
        JSON_HEX_APOS |
        JSON_HEX_QUOT |
        JSON_HEX_AMP
    );

    exit;
}

function normalizarStatus(string $status): string
{
    $status = trim($status);

    if (function_exists('mb_strtolower')) {
        $status = mb_strtolower($status, 'UTF-8');
    } else {
        $status = strtolower($status);
    }

    return strtr($status, [
        'á' => 'a',
        'à' => 'a',
        'â' => 'a',
        'ã' => 'a',
        'é' => 'e',
        'ê' => 'e',
        'í' => 'i',
        'ó' => 'o',
        'ô' => 'o',
        'õ' => 'o',
        'ú' => 'u',
        'ç' => 'c'
    ]);
}

function nomeSeguro(string $nome): string
{
    $nome = basename(trim($nome));
    $nome = preg_replace('/[\x00-\x1F\x7F]+/u', '', $nome) ?? '';
    $nome = preg_replace('/\s+/u', ' ', $nome) ?? $nome;

    if ($nome === '') {
        return 'termo_assinado';
    }

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

    return substr($nome, 0, 240);
}

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    responder([
        'ok' => false,
        'message' => 'Método não permitido.'
    ], 405);
}

$tokenRecebido = (string) ($_POST['csrf_token'] ?? '');
$tokenSessao = (string) ($_SESSION['csrf_termo_assinado_os'] ?? '');

if (
    $tokenRecebido === '' ||
    $tokenSessao === '' ||
    !hash_equals($tokenSessao, $tokenRecebido)
) {
    responder([
        'ok' => false,
        'message' => 'A sessão expirou. Atualize a página e tente novamente.'
    ], 419);
}

$osId = max(0, (int) ($_POST['os_id'] ?? 0));

if ($osId <= 0) {
    responder([
        'ok' => false,
        'message' => 'Ordem de serviço inválida.'
    ], 422);
}

$stmt = $mysqli->prepare("
    SELECT id, numero_os, status
    FROM ordens_servico
    WHERE id = ?
    LIMIT 1
");

if (!$stmt) {
    responder([
        'ok' => false,
        'message' => 'Não foi possível consultar a ordem de serviço.'
    ], 500);
}

$stmt->bind_param('i', $osId);
$stmt->execute();

$os = $stmt->get_result()->fetch_assoc();
$stmt->close();

if (!$os) {
    responder([
        'ok' => false,
        'message' => 'Ordem de serviço não encontrada.'
    ], 404);
}

if (!str_contains(normalizarStatus((string) $os['status']), 'finaliz')) {
    responder([
        'ok' => false,
        'message' =>
            'O termo assinado só pode ser anexado depois que a retirada for registrada.'
    ], 409);
}

if (
    !isset($_FILES['arquivo']) ||
    !is_array($_FILES['arquivo'])
) {
    responder([
        'ok' => false,
        'message' => 'Selecione o documento assinado.'
    ], 422);
}

$arquivo = $_FILES['arquivo'];
$erroUpload = (int) ($arquivo['error'] ?? UPLOAD_ERR_NO_FILE);

if ($erroUpload !== UPLOAD_ERR_OK) {
    $mensagem = match ($erroUpload) {
        UPLOAD_ERR_INI_SIZE,
        UPLOAD_ERR_FORM_SIZE =>
            'O arquivo ultrapassa o limite permitido pelo servidor.',

        UPLOAD_ERR_PARTIAL =>
            'O envio foi interrompido antes de terminar.',

        UPLOAD_ERR_NO_FILE =>
            'Nenhum arquivo foi selecionado.',

        default =>
            'O servidor não conseguiu receber o arquivo.'
    };

    responder([
        'ok' => false,
        'message' => $mensagem
    ], 422);
}

$temporario = (string) ($arquivo['tmp_name'] ?? '');
$tamanho = (int) ($arquivo['size'] ?? 0);
$nomeOriginal = nomeSeguro((string) ($arquivo['name'] ?? ''));

if (
    $temporario === '' ||
    !is_uploaded_file($temporario)
) {
    responder([
        'ok' => false,
        'message' => 'O arquivo enviado não é válido.'
    ], 422);
}

$limite = 12 * 1024 * 1024;

if ($tamanho <= 0 || $tamanho > $limite) {
    responder([
        'ok' => false,
        'message' => 'O documento deve possuir no máximo 12 MB.'
    ], 422);
}

$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = (string) $finfo->file($temporario);

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

if (!isset($permitidos[$mime])) {
    responder([
        'ok' => false,
        'message' => 'Formato não permitido. Envie PDF, JPG ou PNG.'
    ], 422);
}

if (str_starts_with($mime, 'image/')) {
    $dimensoes = @getimagesize($temporario);

    if ($dimensoes === false) {
        responder([
            'ok' => false,
            'message' => 'A imagem enviada está danificada ou inválida.'
        ], 422);
    }
}

$mysqli->query("
    CREATE TABLE IF NOT EXISTS os_termos_assinados (
        id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
        os_id INT NOT NULL,
        arquivo VARCHAR(500) NOT NULL,
        nome_original VARCHAR(255) NOT NULL,
        mime_type VARCHAR(100) NOT NULL,
        tamanho_bytes BIGINT UNSIGNED NOT NULL DEFAULT 0,
        hash_sha256 CHAR(64) NOT NULL,
        enviado_por VARCHAR(150) NOT NULL,
        enviado_em DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        ativo TINYINT(1) NOT NULL DEFAULT 1,
        substituido_em DATETIME NULL,
        INDEX idx_termo_os_ativo (os_id, ativo),
        INDEX idx_termo_data (enviado_em)
    ) ENGINE=InnoDB
      DEFAULT CHARSET=utf8mb4
      COLLATE=utf8mb4_unicode_ci
");

/*
|--------------------------------------------------------------------------
| Diretório de armazenamento
|--------------------------------------------------------------------------
|
| O documento será salvo diretamente em:
| uploads/termos_assinados/
|
| Não é necessário que a pasta uploads inteira seja gravável. O PHP precisa
| de permissão somente na pasta termos_assinados.
|
*/

$diretorioRelativo = 'uploads/termos_assinados';

$diretorioAbsoluto =
    __DIR__ . DIRECTORY_SEPARATOR .
    'uploads' . DIRECTORY_SEPARATOR .
    'termos_assinados';

if (!is_dir($diretorioAbsoluto)) {
    $criado = @mkdir(
        $diretorioAbsoluto,
        0775,
        true
    );

    if (!$criado && !is_dir($diretorioAbsoluto)) {
        responder([
            'ok' => false,
            'message' =>
                'A pasta termos_assinados não existe e o PHP não conseguiu criá-la. ' .
                'Crie manualmente: ' . $diretorioAbsoluto
        ], 500);
    }
}

if (!is_writable($diretorioAbsoluto)) {
    $permissoes = @fileperms($diretorioAbsoluto);

    $permissoesTexto = is_int($permissoes)
        ? substr(sprintf('%o', $permissoes), -4)
        : 'desconhecidas';

    responder([
        'ok' => false,
        'message' =>
            'A pasta específica dos termos não permite gravação pelo PHP: ' .
            $diretorioAbsoluto .
            '. Permissões atuais: ' .
            $permissoesTexto .
            '. No WinSCP, aplique 0777 somente nessa pasta.'
    ], 500);
}

/*
 * Teste real de gravação. is_writable() pode retornar resultado incorreto
 * em alguns ambientes com ACL ou cache de permissões.
 */
$arquivoTeste = @tempnam(
    $diretorioAbsoluto,
    '.teste_escrita_'
);

if ($arquivoTeste === false) {
    responder([
        'ok' => false,
        'message' =>
            'O PHP consegue localizar a pasta, mas não consegue criar arquivos nela: ' .
            $diretorioAbsoluto .
            '. Aplique 0777 diretamente em termos_assinados pelo WinSCP.'
    ], 500);
}

@unlink($arquivoTeste);

$htaccess =
    $diretorioAbsoluto .
    DIRECTORY_SEPARATOR .
    '.htaccess';

if (!is_file($htaccess)) {
    @file_put_contents(
        $htaccess,
        "Options -Indexes\n" .
        "<IfModule mod_authz_core.c>\n" .
        "    Require all denied\n" .
        "</IfModule>\n" .
        "<IfModule !mod_authz_core.c>\n" .
        "    Order Allow,Deny\n" .
        "    Deny from all\n" .
        "</IfModule>\n"
    );
}

$extensao = $permitidos[$mime];

$nomeInterno = sprintf(
    'os_%d_%s.%s',
    $osId,
    bin2hex(random_bytes(18)),
    $extensao
);

$destinoAbsoluto =
    $diretorioAbsoluto .
    DIRECTORY_SEPARATOR .
    $nomeInterno;

$destinoRelativo =
    $diretorioRelativo . '/' . $nomeInterno;

if (!move_uploaded_file($temporario, $destinoAbsoluto)) {
    responder([
        'ok' => false,
        'message' => 'Não foi possível salvar o documento no servidor.'
    ], 500);
}

@chmod($destinoAbsoluto, 0640);

$hash = hash_file('sha256', $destinoAbsoluto);

if (!is_string($hash) || strlen($hash) !== 64) {
    @unlink($destinoAbsoluto);

    responder([
        'ok' => false,
        'message' => 'Não foi possível validar a integridade do documento.'
    ], 500);
}

$usuario = trim(
    (string) ($_SESSION['usuario'] ?? 'Servidor da TI')
);

if ($usuario === '') {
    $usuario = 'Servidor da TI';
}

try {
    $mysqli->begin_transaction();

    $stmt = $mysqli->prepare("
        UPDATE os_termos_assinados
        SET
            ativo = 0,
            substituido_em = NOW()
        WHERE os_id = ?
          AND ativo = 1
    ");

    if (!$stmt) {
        throw new RuntimeException(
            'Não foi possível atualizar o documento anterior.'
        );
    }

    $stmt->bind_param('i', $osId);
    $stmt->execute();
    $stmt->close();

    $stmt = $mysqli->prepare("
        INSERT INTO os_termos_assinados (
            os_id,
            arquivo,
            nome_original,
            mime_type,
            tamanho_bytes,
            hash_sha256,
            enviado_por,
            ativo
        )
        VALUES (?, ?, ?, ?, ?, ?, ?, 1)
    ");

    if (!$stmt) {
        throw new RuntimeException(
            'Não foi possível registrar o documento.'
        );
    }

    $stmt->bind_param(
        'isssiss',
        $osId,
        $destinoRelativo,
        $nomeOriginal,
        $mime,
        $tamanho,
        $hash,
        $usuario
    );

    $stmt->execute();
    $registroId = (int) $stmt->insert_id;
    $stmt->close();

    $mysqli->commit();
} catch (Throwable $erro) {
    try {
        $mysqli->rollback();
    } catch (Throwable) {
    }

    @unlink($destinoAbsoluto);

    responder([
        'ok' => false,
        'message' => $erro->getMessage()
    ], 500);
}

$dataBanco = date('Y-m-d H:i:s');

responder([
    'ok' => true,
    'message' => 'Termo assinado anexado com sucesso.',
    'registro_id' => $registroId,
    'os_id' => $osId,
    'numero_os' => (string) $os['numero_os'],
    'nome_original' => $nomeOriginal,
    'enviado_por' => $usuario,
    'enviado_em' => date('d/m/Y H:i'),
    'enviado_em_banco' => $dataBanco,
    'view_url' =>
        'visualizar_termo_assinado.php?os_id=' . $osId
]);
