<?php

declare(strict_types=1);

/**
 * Integração Firebase Cloud Messaging HTTP v1 sem dependências externas.
 * A chave privada deve ficar fora de public/, por exemplo:
 * /etc/smd-delivery/firebase-service-account.json
 */

function fcm_service_account_path(): string
{
    return (string) config('app', 'firebase_service_account', '/etc/smd-delivery/firebase-service-account.json');
}

function fcm_is_configured(): bool
{
    $path = fcm_service_account_path();
    return $path !== '' && is_file($path) && is_readable($path);
}

function fcm_base64url(string $value): string
{
    return rtrim(strtr(base64_encode($value), '+/', '-_'), '=');
}

function fcm_service_account(): array
{
    static $credentials = null;

    if (is_array($credentials)) {
        return $credentials;
    }

    $path = fcm_service_account_path();
    if (!is_file($path) || !is_readable($path)) {
        throw new RuntimeException('Arquivo da conta de serviço Firebase não encontrado em: ' . $path);
    }

    $raw = file_get_contents($path);
    $decoded = $raw !== false ? json_decode($raw, true) : null;

    if (!is_array($decoded)) {
        throw new RuntimeException('Arquivo da conta de serviço Firebase inválido.');
    }

    foreach (['client_email', 'private_key', 'project_id'] as $required) {
        if (empty($decoded[$required])) {
            throw new RuntimeException('Campo obrigatório ausente na conta Firebase: ' . $required);
        }
    }

    $credentials = $decoded;
    return $credentials;
}

function fcm_project_id(): string
{
    $configured = trim((string) config('app', 'firebase_project_id', ''));
    if ($configured !== '') {
        return $configured;
    }

    $credentials = fcm_service_account();
    return (string) $credentials['project_id'];
}

function fcm_curl(string $url, array $options = []): array
{
    if (!function_exists('curl_init')) {
        throw new RuntimeException('A extensão PHP cURL não está instalada.');
    }

    $ch = curl_init($url);
    if ($ch === false) {
        throw new RuntimeException('Não foi possível iniciar o cURL.');
    }

    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CONNECTTIMEOUT => 12,
        CURLOPT_TIMEOUT => 25,
        CURLOPT_FOLLOWLOCATION => false,
    ] + $options);

    $body = curl_exec($ch);
    $error = curl_error($ch);
    $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($body === false) {
        throw new RuntimeException('Falha de comunicação com o Firebase: ' . $error);
    }

    return ['status' => $status, 'body' => (string) $body];
}

function fcm_access_token(): string
{
    $storage = ROOT_PATH . '/storage';
    if (!is_dir($storage) && !mkdir($storage, 0770, true) && !is_dir($storage)) {
        throw new RuntimeException('Não foi possível criar a pasta storage.');
    }

    $cacheFile = $storage . '/fcm-oauth-cache.json';
    if (is_file($cacheFile)) {
        $cached = json_decode((string) file_get_contents($cacheFile), true);
        if (
            is_array($cached)
            && !empty($cached['access_token'])
            && (int) ($cached['expires_at'] ?? 0) > time() + 120
        ) {
            return (string) $cached['access_token'];
        }
    }

    $credentials = fcm_service_account();
    $now = time();
    $header = ['alg' => 'RS256', 'typ' => 'JWT'];
    $claims = [
        'iss' => (string) $credentials['client_email'],
        'scope' => 'https://www.googleapis.com/auth/firebase.messaging',
        'aud' => (string) ($credentials['token_uri'] ?? 'https://oauth2.googleapis.com/token'),
        'iat' => $now,
        'exp' => $now + 3600,
    ];

    $unsigned = fcm_base64url((string) json_encode($header, JSON_UNESCAPED_SLASHES))
        . '.'
        . fcm_base64url((string) json_encode($claims, JSON_UNESCAPED_SLASHES));

    $privateKey = openssl_pkey_get_private((string) $credentials['private_key']);
    if ($privateKey === false) {
        throw new RuntimeException('A chave privada da conta Firebase é inválida.');
    }

    $signature = '';
    $signed = openssl_sign($unsigned, $signature, $privateKey, OPENSSL_ALGO_SHA256);
    if (PHP_VERSION_ID < 80000) {
        openssl_pkey_free($privateKey);
    }
    if (!$signed) {
        throw new RuntimeException('Não foi possível assinar o token OAuth do Firebase.');
    }

    $assertion = $unsigned . '.' . fcm_base64url($signature);
    $tokenUrl = (string) ($credentials['token_uri'] ?? 'https://oauth2.googleapis.com/token');
    $response = fcm_curl($tokenUrl, [
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'],
        CURLOPT_POSTFIELDS => http_build_query([
            'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
            'assertion' => $assertion,
        ]),
    ]);

    $decoded = json_decode($response['body'], true);
    if ($response['status'] < 200 || $response['status'] >= 300 || empty($decoded['access_token'])) {
        throw new RuntimeException('Firebase recusou o token OAuth: ' . $response['body']);
    }

    $token = (string) $decoded['access_token'];
    $expiresIn = max(300, (int) ($decoded['expires_in'] ?? 3600));
    @file_put_contents($cacheFile, json_encode([
        'access_token' => $token,
        'expires_at' => time() + $expiresIn,
    ], JSON_UNESCAPED_SLASHES), LOCK_EX);
    @chmod($cacheFile, 0660);

    return $token;
}

function fcm_send_to_token(string $deviceToken, string $title, string $message, array $data = []): array
{
    $deviceToken = trim($deviceToken);
    if ($deviceToken === '') {
        throw new RuntimeException('Token FCM vazio.');
    }

    $stringData = [];
    foreach ($data as $key => $value) {
        $stringData[(string) $key] = (string) $value;
    }

    $payload = [
        'message' => [
            'token' => $deviceToken,
            'notification' => [
                'title' => $title,
                'body' => $message,
            ],
            'data' => $stringData,
            'android' => [
                'priority' => 'HIGH',
                'notification' => [
                    'channel_id' => 'pedidos_status',
                    'sound' => 'default',
                    'default_vibrate_timings' => true,
                    'notification_priority' => 'PRIORITY_HIGH',
                    'click_action' => 'ABRIR_PEDIDO',
                ],
            ],
        ],
    ];

    $projectId = fcm_project_id();
    $url = 'https://fcm.googleapis.com/v1/projects/' . rawurlencode($projectId) . '/messages:send';
    $response = fcm_curl($url, [
        CURLOPT_POST => true,
        CURLOPT_HTTPHEADER => [
            'Authorization: Bearer ' . fcm_access_token(),
            'Content-Type: application/json; charset=UTF-8',
        ],
        CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
    ]);

    $decoded = json_decode($response['body'], true);
    $success = $response['status'] >= 200 && $response['status'] < 300 && !empty($decoded['name']);

    return [
        'success' => $success,
        'http_status' => $response['status'],
        'response' => $decoded ?: $response['body'],
        'unregistered' => str_contains($response['body'], 'UNREGISTERED')
            || str_contains($response['body'], 'registration-token-not-registered'),
    ];
}

function order_notification_content(string $status, string $orderNumber): array
{
    $number = $orderNumber !== '' ? ' ' . $orderNumber : '';

    return match ($status) {
        'PEDIDO_REALIZADO' => ['Pedido recebido', 'Recebemos o pedido' . $number . '. Você será avisado a cada etapa.'],
        'AGUARDANDO_CONFIRMACAO' => ['Aguardando confirmação', 'O pedido' . $number . ' está aguardando confirmação da loja.'],
        'CONFIRMADO' => ['Pedido confirmado', 'A loja confirmou o pedido' . $number . '.'],
        'EM_PREPARACAO' => ['Pedido em preparação', 'Seu pedido' . $number . ' já está sendo preparado.'],
        'PRONTO' => ['Pedido pronto', 'O pedido' . $number . ' está pronto para sair.'],
        'AGUARDANDO_ENTREGADOR' => ['Buscando entregador', 'Estamos procurando um entregador para o pedido' . $number . '.'],
        'ENTREGADOR_DESIGNADO' => ['Entregador designado', 'Um entregador foi designado para o pedido' . $number . '.'],
        'SAIU_PARA_ENTREGA' => ['Saiu para entrega', 'O pedido' . $number . ' saiu para entrega. Tenha o código em mãos.'],
        'CHEGANDO' => ['Entregador chegando', 'O entregador do pedido' . $number . ' está próximo do endereço.'],
        'ENTREGUE' => ['Pedido entregue', 'O pedido' . $number . ' foi entregue com sucesso.'],
        'CANCELADO' => ['Pedido cancelado', 'O pedido' . $number . ' foi cancelado. Consulte a loja para mais informações.'],
        default => ['Atualização do pedido', 'O pedido' . $number . ' teve uma atualização: ' . str_replace('_', ' ', $status) . '.'],
    };
}

function notify_client_custom(int $clientId, ?int $orderId, string $title, string $message, string $type = 'AVISO'): array
{
    $pdo = db();
    $stmt = $pdo->prepare(
        'INSERT INTO notificacoes
         (cliente_id, pedido_id, titulo, mensagem, tipo, status_envio, tentativas)
         VALUES (:cliente_id, :pedido_id, :titulo, :mensagem, :tipo, "PROCESSANDO", 1)'
    );
    $stmt->execute([
        'cliente_id' => $clientId,
        'pedido_id' => $orderId,
        'titulo' => $title,
        'mensagem' => $message,
        'tipo' => $type,
    ]);
    $notificationId = (int) $pdo->lastInsertId();

    $deviceStmt = $pdo->prepare(
        'SELECT id, fcm_token
         FROM cliente_dispositivos
         WHERE cliente_id = :cliente_id AND ativo = 1
         ORDER BY ultimo_acesso DESC, id DESC'
    );
    $deviceStmt->execute(['cliente_id' => $clientId]);
    $devices = $deviceStmt->fetchAll();

    if (!$devices) {
        $pdo->prepare('UPDATE notificacoes SET status_envio="SEM_DISPOSITIVO", erro="Cliente sem dispositivo ativo" WHERE id=:id')
            ->execute(['id' => $notificationId]);
        return ['success' => false, 'status' => 'SEM_DISPOSITIVO', 'notification_id' => $notificationId];
    }

    if (!fcm_is_configured()) {
        $pdo->prepare('UPDATE notificacoes SET status_envio="NAO_CONFIGURADO", erro=:erro WHERE id=:id')
            ->execute([
                'erro' => 'Conta de serviço Firebase não instalada em ' . fcm_service_account_path(),
                'id' => $notificationId,
            ]);
        return ['success' => false, 'status' => 'NAO_CONFIGURADO', 'notification_id' => $notificationId];
    }

    $successCount = 0;
    $responses = [];
    $lastError = null;

    foreach ($devices as $device) {
        try {
            $result = fcm_send_to_token((string) $device['fcm_token'], $title, $message, [
                'tipo' => $type,
                'titulo' => $title,
                'mensagem' => $message,
                'pedido_id' => $orderId ? (string) $orderId : '',
                'notificacao_id' => (string) $notificationId,
            ]);
            $responses[] = ['device_id' => (int) $device['id'], 'result' => $result];
            if ($result['success']) {
                $successCount++;
            } elseif ($result['unregistered']) {
                $pdo->prepare('UPDATE cliente_dispositivos SET ativo=0 WHERE id=:id')
                    ->execute(['id' => (int) $device['id']]);
            }
        } catch (Throwable $e) {
            $lastError = $e->getMessage();
            $responses[] = ['device_id' => (int) $device['id'], 'error' => $lastError];
        }
    }

    $status = $successCount > 0 ? 'ENVIADA' : 'FALHOU';
    $update = $pdo->prepare(
        'UPDATE notificacoes
         SET status_envio=:status, enviada_em=:enviada_em, resposta_fcm=:resposta, erro=:erro
         WHERE id=:id'
    );
    $update->execute([
        'status' => $status,
        'enviada_em' => $successCount > 0 ? date('Y-m-d H:i:s') : null,
        'resposta' => json_encode($responses, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
        'erro' => $lastError,
        'id' => $notificationId,
    ]);

    return [
        'success' => $successCount > 0,
        'status' => $status,
        'sent' => $successCount,
        'devices' => count($devices),
        'notification_id' => $notificationId,
    ];
}

function notify_order_status(int $orderId, string $status): array
{
    try {
        $stmt = db()->prepare('SELECT id, cliente_id, numero_pedido FROM pedidos WHERE id=:id LIMIT 1');
        $stmt->execute(['id' => $orderId]);
        $order = $stmt->fetch();

        if (!$order) {
            return ['success' => false, 'status' => 'PEDIDO_NAO_ENCONTRADO'];
        }

        [$title, $message] = order_notification_content($status, (string) $order['numero_pedido']);
        return notify_client_custom(
            (int) $order['cliente_id'],
            (int) $order['id'],
            $title,
            $message,
            'PEDIDO_STATUS:' . $status
        );
    } catch (Throwable $e) {
        error_log('Falha ao notificar status do pedido ' . $orderId . ': ' . $e->getMessage());
        return ['success' => false, 'status' => 'ERRO', 'error' => $e->getMessage()];
    }
}
