<?php
/**
 * 1MZ For Artists — FCM Sender (Fase 4b)
 *
 * Envia push notifications através do Firebase Cloud Messaging API v1.
 *
 * Não usa o SDK PHP do Google (que tem várias dependências). Em vez disso,
 * implementa o fluxo OAuth 2.0 service-account → access_token diretamente
 * com openssl_sign() e wp_remote_post().
 *
 * Caminho do ficheiro de credenciais:
 *   define('RMBZ_FCM_CREDENTIALS_PATH', '/home/jmetano/firebase-service-account.json');
 *
 * Esta constante deve ser definida em wp-config.php
 *
 * @package 1MZ_For_Artists
 * @since 1.6.6
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Devolve o caminho do ficheiro de credenciais Firebase.
 */
function rmbz_fcm_credentials_path() {
    if ( defined( 'RMBZ_FCM_CREDENTIALS_PATH' ) ) {
        return RMBZ_FCM_CREDENTIALS_PATH;
    }
    return null;
}

/**
 * Lê o ficheiro de credenciais e devolve array, ou WP_Error.
 */
function rmbz_fcm_load_credentials() {
    $path = rmbz_fcm_credentials_path();
    if ( ! $path ) {
        return new WP_Error( 'no_credentials_path', 'RMBZ_FCM_CREDENTIALS_PATH não está definido em wp-config.php' );
    }
    if ( ! file_exists( $path ) ) {
        return new WP_Error( 'credentials_not_found', 'Ficheiro de credenciais Firebase não encontrado: ' . $path );
    }
    if ( ! is_readable( $path ) ) {
        return new WP_Error( 'credentials_not_readable', 'Ficheiro de credenciais Firebase não é legível.' );
    }

    $raw = @file_get_contents( $path );
    $data = json_decode( $raw, true );
    if ( ! is_array( $data ) ) {
        return new WP_Error( 'credentials_invalid', 'Ficheiro de credenciais Firebase corrompido.' );
    }

    $required = [ 'project_id', 'private_key', 'client_email' ];
    foreach ( $required as $k ) {
        if ( empty( $data[ $k ] ) ) {
            return new WP_Error( 'credentials_missing_field', "Campo obrigatório em falta: $k" );
        }
    }

    return $data;
}

/**
 * Gera/devolve um OAuth access token válido para o FCM.
 * Cacheia em transient (50 min — token válido 60 min).
 */
function rmbz_fcm_get_access_token() {
    $cached = get_transient( 'rmbz_fcm_oauth_token' );
    if ( $cached ) return $cached;

    $creds = rmbz_fcm_load_credentials();
    if ( is_wp_error( $creds ) ) return $creds;

    $now = time();
    $exp = $now + 3600;

    // 1) JWT header
    $header = [ 'alg' => 'RS256', 'typ' => 'JWT' ];

    // 2) JWT claims
    $claims = [
        'iss'   => $creds['client_email'],
        'scope' => 'https://www.googleapis.com/auth/firebase.messaging',
        'aud'   => 'https://oauth2.googleapis.com/token',
        'iat'   => $now,
        'exp'   => $exp,
    ];

    $b64h = rmbz_fcm_b64url( wp_json_encode( $header ) );
    $b64c = rmbz_fcm_b64url( wp_json_encode( $claims ) );
    $signing_input = $b64h . '.' . $b64c;

    $signature = '';
    $private_key = openssl_pkey_get_private( $creds['private_key'] );
    if ( ! $private_key ) {
        return new WP_Error( 'invalid_private_key', 'Chave privada Firebase inválida.' );
    }
    $ok = openssl_sign( $signing_input, $signature, $private_key, OPENSSL_ALGO_SHA256 );
    if ( PHP_VERSION_ID < 80000 ) {
        openssl_pkey_free( $private_key );
    }
    if ( ! $ok ) {
        return new WP_Error( 'signing_failed', 'Falha a assinar JWT.' );
    }

    $jwt = $signing_input . '.' . rmbz_fcm_b64url( $signature );

    // 3) Trocar JWT por access token
    $resp = wp_remote_post( 'https://oauth2.googleapis.com/token', [
        'timeout' => 15,
        'headers' => [ 'Content-Type' => 'application/x-www-form-urlencoded' ],
        'body'    => [
            'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
            'assertion'  => $jwt,
        ],
    ] );

    if ( is_wp_error( $resp ) ) return $resp;
    $code = wp_remote_retrieve_response_code( $resp );
    $body = json_decode( wp_remote_retrieve_body( $resp ), true );

    if ( $code !== 200 || empty( $body['access_token'] ) ) {
        $err = isset( $body['error_description'] ) ? $body['error_description'] : 'Falha a obter access token.';
        return new WP_Error( 'oauth_failed', $err . ' (code=' . $code . ')' );
    }

    $token = $body['access_token'];
    set_transient( 'rmbz_fcm_oauth_token', $token, 50 * MINUTE_IN_SECONDS );

    return $token;
}

/**
 * Base64URL — sem padding, "+" → "-", "/" → "_"
 */
function rmbz_fcm_b64url( $input ) {
    return rtrim( strtr( base64_encode( $input ), '+/', '-_' ), '=' );
}

/**
 * Devolve o project_id do ficheiro de credenciais.
 */
function rmbz_fcm_project_id() {
    $creds = rmbz_fcm_load_credentials();
    if ( is_wp_error( $creds ) ) return null;
    return $creds['project_id'];
}

/* =============================================================================
 * Envio de push para um device específico (FCM v1)
 * ============================================================================= */

/**
 * Envia push para UM device token.
 *
 * @param string $fcm_token Token do device
 * @param string $title Título da notificação
 * @param string $body Corpo
 * @param array  $data Dados extra (todos serão strings; FCM exige strings em data{})
 * @return array|WP_Error
 */
function rmbz_fcm_send_to_token( $fcm_token, $title, $body, $data = [] ) {
    $access_token = rmbz_fcm_get_access_token();
    if ( is_wp_error( $access_token ) ) return $access_token;

    $project_id = rmbz_fcm_project_id();
    if ( ! $project_id ) {
        return new WP_Error( 'no_project_id', 'project_id em falta.' );
    }

    // FCM exige TODOS os campos de "data" como strings
    $data_strings = [];
    foreach ( $data as $k => $v ) {
        if ( $v === null ) continue;
        $data_strings[ (string) $k ] = is_scalar( $v ) ? (string) $v : wp_json_encode( $v );
    }

    // [1.6.118.109] Bloco webpush — só aplica a tokens web (PWA). [1.6.341] NUNCA
    // incluir `fcm_options` vazio: `array_filter([...])` sem link dava `[]`, que a
    // FCM v1 rejeita (espera objeto) → INVALID_ARGUMENT em TODOS os envios sem URL,
    // invalidando tokens bons. Agora só pomos `link` quando existe mesmo.
    $webpush = [
        'notification' => [
            'icon' => RMBZ_PLUGIN_URL . 'assets/pwa/icon-192.png',
        ],
    ];
    $link = isset( $data['url'] ) ? trim( (string) $data['url'] ) : '';
    if ( $link !== '' ) {
        $webpush['fcm_options'] = [ 'link' => $link ];
    }

    $payload = [
        'message' => [
            'token' => $fcm_token,
            'notification' => [
                'title' => $title,
                'body'  => $body,
            ],
            'data' => $data_strings,
            'android' => [
                'priority' => 'HIGH',
                'notification' => [
                    'sound'       => 'default',
                    'channel_id'  => 'general',
                ],
            ],
            'webpush' => $webpush,
        ],
    ];

    $url = "https://fcm.googleapis.com/v1/projects/{$project_id}/messages:send";

    $resp = wp_remote_post( $url, [
        'timeout' => 15,
        'headers' => [
            'Authorization' => 'Bearer ' . $access_token,
            'Content-Type'  => 'application/json; charset=UTF-8',
        ],
        'body' => wp_json_encode( $payload ),
    ] );

    if ( is_wp_error( $resp ) ) return $resp;
    $code = wp_remote_retrieve_response_code( $resp );
    $body_resp = json_decode( wp_remote_retrieve_body( $resp ), true );

    if ( $code === 200 ) {
        return [ 'ok' => true, 'name' => $body_resp['name'] ?? null ];
    }

    // Erros conhecidos:
    //   404 UNREGISTERED — token expirou ou foi desinstalado → marcar inactivo
    //   400 INVALID_ARGUMENT — token mal formado → marcar inactivo
    $error_status = isset( $body_resp['error']['status'] ) ? $body_resp['error']['status'] : 'UNKNOWN';
    $error_msg = isset( $body_resp['error']['message'] ) ? $body_resp['error']['message'] : 'Erro FCM';

    return new WP_Error( 'fcm_failed', $error_msg, [
        'http_code' => $code,
        'status'    => $error_status,
    ] );
}

/**
 * Envia push a todos os devices activos de um utilizador.
 *
 * @param string $telemovel
 * @param string $title
 * @param string $body
 * @param array  $data
 * @return array Estatísticas: ['sent'=>N, 'failed'=>N, 'invalidated'=>N]
 */
function rmbz_fcm_send_to_user( $telemovel, $title, $body, $data = [] ) {
    global $wpdb;
    $stats = [ 'sent' => 0, 'failed' => 0, 'invalidated' => 0 ];

    $tokens = $wpdb->get_results( $wpdb->prepare(
        "SELECT id, fcm_token FROM {$wpdb->prefix}rmbz_app_push_tokens
         WHERE telemovel = %s AND is_active = 1",
        $telemovel
    ) );

    if ( empty( $tokens ) ) return $stats;

    foreach ( $tokens as $row ) {
        $result = rmbz_fcm_send_to_token( $row->fcm_token, $title, $body, $data );

        if ( is_wp_error( $result ) ) {
            $stats['failed']++;
            $err_data = $result->get_error_data();
            $err_status = is_array( $err_data ) && ! empty( $err_data['status'] ) ? $err_data['status'] : '';

            // [1.6.341] Desativar SÓ quando o token está mesmo MORTO:
            //   • UNREGISTERED / NOT_FOUND (404) → app desinstalada ou token expirou.
            //   • INVALID_ARGUMENT *só* se a mensagem disser que o TOKEN é inválido
            //     ("registration token...") — distingue do nosso erro de PAYLOAD,
            //     que dava INVALID_ARGUMENT genérico e antes apagava tokens BONS.
            $msg_low = strtolower( (string) $result->get_error_message() );
            $token_is_dead =
                in_array( $err_status, [ 'UNREGISTERED', 'NOT_FOUND' ], true )
                || ( $err_status === 'INVALID_ARGUMENT' && strpos( $msg_low, 'registration token' ) !== false );
            if ( $token_is_dead ) {
                $wpdb->update(
                    $wpdb->prefix . 'rmbz_app_push_tokens',
                    [ 'is_active' => 0, 'last_push_status' => $err_status ],
                    [ 'id' => $row->id ],
                    [ '%d', '%s' ],
                    [ '%d' ]
                );
                $stats['invalidated']++;
            } else {
                $wpdb->update(
                    $wpdb->prefix . 'rmbz_app_push_tokens',
                    [ 'last_push_status' => substr( $err_status . ': ' . $result->get_error_message(), 0, 32 ) ],
                    [ 'id' => $row->id ],
                    [ '%s' ],
                    [ '%d' ]
                );
            }

            error_log( '[1MZ-FCM] failed for token id=' . $row->id . ': ' . $result->get_error_message() );
        } else {
            $stats['sent']++;
            $wpdb->update(
                $wpdb->prefix . 'rmbz_app_push_tokens',
                [
                    'last_push_sent_at' => current_time( 'mysql' ),
                    'last_push_status'  => 'sent',
                ],
                [ 'id' => $row->id ],
                [ '%s', '%s' ],
                [ '%d' ]
            );
        }
    }

    return $stats;
}
