<?php
/**
 * 1MZ For Artists — Wallet Controller
 *
 * Endpoints:
 *   GET  /1mz/v1/wallet                  — Saldo total e retirável
 *   GET  /1mz/v1/wallet/transactions     — Histórico paginado
 *   GET  /1mz/v1/wallet/withdrawals      — Pedidos de levantamento
 *   POST /1mz/v1/wallet/withdrawals      — Criar pedido de levantamento
 *   GET  /1mz/v1/wallet/deposits         — Histórico de depósitos
 *   GET  /1mz/v1/wallet/saved_payouts    — Contactos de pagamento guardados
 *
 * REGRAS DO PLUGIN (ver my-account-page.php → rmbz_handle_withdrawal_request):
 *   - PIN obrigatório como segunda barreira (4 dígitos)
 *   - Métodos: M-Pesa, E-Mola apenas (do formulário web)
 *   - M-Pesa só aceita números 84/85 (Vodacom)
 *   - E-Mola só aceita números 86/87 (Movitel)
 *   - Telefone: 9 dígitos (formato local)
 *   - Mínimo: RMBZ_MIN_WITHDRAWAL (50 MT por defeito)
 *   - Só 1 pedido pendente por utilizador
 *   - Lock pessimista (GET_LOCK MySQL) para evitar duplo-clique
 *   - DEBITA o saldo ao criar pedido (não só fica pendente sem afectar saldo)
 *   - Audit log via rmbz_wg_log
 *
 * @package 1MZ_For_Artists
 * @since   1.6.0
 */

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

add_action( 'rest_api_init', 'rmbz_api_register_wallet_routes' );

function rmbz_api_register_wallet_routes() {
    register_rest_route( RMBZ_API_NAMESPACE, '/wallet', [
        'methods'             => 'GET',
        'callback'            => 'rmbz_api_handle_get_wallet',
        'permission_callback' => 'rmbz_api_require_auth',
    ] );

    register_rest_route( RMBZ_API_NAMESPACE, '/wallet/transactions', [
        'methods'             => 'GET',
        'callback'            => 'rmbz_api_handle_get_transactions',
        'permission_callback' => 'rmbz_api_require_auth',
        'args' => [
            'page'     => [ 'type' => 'integer', 'default' => 1, 'minimum' => 1 ],
            'per_page' => [ 'type' => 'integer', 'default' => 20, 'minimum' => 1, 'maximum' => 100 ],
            'type'     => [ 'type' => 'string' ],
        ],
    ] );

    register_rest_route( RMBZ_API_NAMESPACE, '/wallet/withdrawals', [
        [
            'methods'             => 'GET',
            'callback'            => 'rmbz_api_handle_list_withdrawals',
            'permission_callback' => 'rmbz_api_require_auth',
            'args' => [
                'page'     => [ 'type' => 'integer', 'default' => 1 ],
                'per_page' => [ 'type' => 'integer', 'default' => 20, 'maximum' => 100 ],
            ],
        ],
        [
            'methods'             => 'POST',
            'callback'            => 'rmbz_api_handle_create_withdrawal',
            'permission_callback' => 'rmbz_api_require_auth',
            'args' => [
                'amount'          => [ 'required' => true, 'type' => 'number' ],
                'payment_method'  => [ 'required' => true, 'type' => 'string' ],
                'payment_details' => [ 'required' => true, 'type' => 'string' ],
                'pin'             => [ 'required' => true, 'type' => 'string' ],
            ],
        ],
    ] );

    register_rest_route( RMBZ_API_NAMESPACE, '/wallet/deposits', [
        'methods'             => 'GET',
        'callback'            => 'rmbz_api_handle_list_deposits',
        'permission_callback' => 'rmbz_api_require_auth',
        'args' => [
            'page'     => [ 'type' => 'integer', 'default' => 1 ],
            'per_page' => [ 'type' => 'integer', 'default' => 20, 'maximum' => 100 ],
        ],
    ] );

    register_rest_route( RMBZ_API_NAMESPACE, '/wallet/saved_payouts', [
        'methods'             => 'GET',
        'callback'            => 'rmbz_api_handle_saved_payouts',
        'permission_callback' => 'rmbz_api_require_auth',
    ] );
}

/* ----------------------------------------------------------------------- */
/* GET WALLET                                                              */
/* ----------------------------------------------------------------------- */

function rmbz_api_handle_get_wallet( WP_REST_Request $request ) {
    $telemovel = (string) $request->get_param( 'rmbz_telemovel' );
    $wallet = rmbz_api_format_wallet( $telemovel );

    if ( ! $wallet ) {
        return rmbz_api_error( 'Carteira não encontrada.', 404, 'not_found' );
    }

    global $wpdb;
    $locked = (float) $wpdb->get_var( $wpdb->prepare(
        "SELECT SUM(amount) FROM {$wpdb->prefix}rmbz_wallet_transactions
         WHERE user_telemovel = %s AND is_unlocked = 0 AND unlock_date > %s",
        $telemovel,
        current_time( 'mysql', 1 )
    ) );

    $wallet['balance_locked'] = max( 0, $locked );

    // Mínimo de levantamento (lê do plugin se possível)
    $wallet['min_withdrawal'] = defined( 'RMBZ_MIN_WITHDRAWAL' ) ? (float) RMBZ_MIN_WITHDRAWAL : 50.0;

    return rmbz_api_ok( $wallet );
}

/* ----------------------------------------------------------------------- */
/* TRANSACTIONS                                                            */
/* ----------------------------------------------------------------------- */

function rmbz_api_handle_get_transactions( WP_REST_Request $request ) {
    $telemovel = (string) $request->get_param( 'rmbz_telemovel' );
    $page      = max( 1, (int) $request->get_param( 'page' ) );
    $per_page  = min( 100, max( 1, (int) $request->get_param( 'per_page' ) ) );
    $type      = (string) $request->get_param( 'type' );

    $offset = ( $page - 1 ) * $per_page;

    global $wpdb;
    $where = $wpdb->prepare( 'WHERE user_telemovel = %s', $telemovel );

    if ( $type !== '' ) {
        $where .= $wpdb->prepare( ' AND type = %s', $type );
    }

    $total = (int) $wpdb->get_var(
        "SELECT COUNT(*) FROM {$wpdb->prefix}rmbz_wallet_transactions {$where}"
    );

    $rows = $wpdb->get_results( $wpdb->prepare(
        "SELECT transaction_id, amount, type, reference_id, project_id, details,
                is_unlocked, unlock_date, transaction_date
         FROM {$wpdb->prefix}rmbz_wallet_transactions
         {$where}
         ORDER BY transaction_id DESC
         LIMIT %d OFFSET %d",
        $per_page, $offset
    ) );

    $items = array_map( function ( $r ) {
        return [
            'id'           => (int) $r->transaction_id,
            'amount'       => (float) $r->amount,
            'type'         => $r->type,
            'reference_id' => $r->reference_id ? (int) $r->reference_id : null,
            'project_id'   => $r->project_id ? (int) $r->project_id : null,
            'details'      => $r->details,
            'is_unlocked'  => (bool) $r->is_unlocked,
            'unlock_date'  => $r->unlock_date ? mysql_to_rfc3339( $r->unlock_date ) : null,
            'date'         => mysql_to_rfc3339( $r->transaction_date ),
        ];
    }, $rows ?: [] );

    return rmbz_api_ok( [
        'items' => $items,
        'pagination' => [
            'page'        => $page,
            'per_page'    => $per_page,
            'total'       => $total,
            'total_pages' => (int) ceil( $total / $per_page ),
        ],
    ] );
}

/* ----------------------------------------------------------------------- */
/* WITHDRAWALS — LIST                                                      */
/* ----------------------------------------------------------------------- */

function rmbz_api_handle_list_withdrawals( WP_REST_Request $request ) {
    $telemovel = (string) $request->get_param( 'rmbz_telemovel' );
    $page      = max( 1, (int) $request->get_param( 'page' ) );
    $per_page  = min( 100, max( 1, (int) $request->get_param( 'per_page' ) ) );
    $offset    = ( $page - 1 ) * $per_page;

    global $wpdb;
    $total = (int) $wpdb->get_var( $wpdb->prepare(
        "SELECT COUNT(*) FROM {$wpdb->prefix}rmbz_withdrawal_requests WHERE user_telemovel = %s",
        $telemovel
    ) );

    $rows = $wpdb->get_results( $wpdb->prepare(
        "SELECT id, amount, source, exchange_rate, status, payment_method, payment_details,
                request_date, processed_date, admin_notes
         FROM {$wpdb->prefix}rmbz_withdrawal_requests
         WHERE user_telemovel = %s
         ORDER BY id DESC
         LIMIT %d OFFSET %d",
        $telemovel, $per_page, $offset
    ) );

    $items = array_map( function ( $r ) {
        return [
            'id'              => (int) $r->id,
            'amount'          => (float) $r->amount,
            'source'          => $r->source,
            'exchange_rate'   => $r->exchange_rate ? (float) $r->exchange_rate : null,
            'status'          => $r->status,
            'payment_method'  => $r->payment_method,
            'payment_details' => $r->payment_details,
            'requested_at'    => mysql_to_rfc3339( $r->request_date ),
            'processed_at'    => $r->processed_date ? mysql_to_rfc3339( $r->processed_date ) : null,
            'admin_notes'     => $r->admin_notes,
        ];
    }, $rows ?: [] );

    return rmbz_api_ok( [
        'items' => $items,
        'pagination' => [
            'page'        => $page,
            'per_page'    => $per_page,
            'total'       => $total,
            'total_pages' => (int) ceil( $total / $per_page ),
        ],
    ] );
}

/* ----------------------------------------------------------------------- */
/* WITHDRAWALS — CREATE (REGRAS REAIS DO PLUGIN)                           */
/* ----------------------------------------------------------------------- */

function rmbz_api_handle_create_withdrawal( WP_REST_Request $request ) {
    // [1.6.47] Bloqueio global de levantamentos
    if ( function_exists( 'rmbz_is_globally_blocked' ) && rmbz_is_globally_blocked( 'withdrawals' ) ) {
        return rmbz_api_error(
            'Os levantamentos estão temporariamente suspensos pela administração. Tenta de novo mais tarde.',
            403,
            'withdrawals_globally_blocked'
        );
    }

    $telemovel       = (string) $request->get_param( 'rmbz_telemovel' );
    $amount          = (float) $request->get_param( 'amount' );
    $payment_method  = sanitize_text_field( (string) $request->get_param( 'payment_method' ) );
    $payment_details = sanitize_text_field( (string) $request->get_param( 'payment_details' ) );
    $pin             = (string) $request->get_param( 'pin' );

    // [REGRA] PIN exatos 4 dígitos
    if ( ! preg_match( '/^\d{4}$/', $pin ) ) {
        return rmbz_api_error( 'O PIN deve ter exatamente 4 dígitos.', 400, 'bad_pin_format' );
    }

    // [REGRA] PIN como segunda barreira
    if ( ! function_exists( 'rmbz_verify_user_pin' ) || ! rmbz_verify_user_pin( $telemovel, $pin ) ) {
        $rl = rmbz_api_rate_limit( 'withdraw_pin_' . md5( $telemovel ), 5, 900 );
        if ( ! $rl['allowed'] ) {
            return rmbz_api_error( 'Demasiadas tentativas. Aguarda 15 minutos.', 429, 'rate_limited' );
        }
        return rmbz_api_error( 'PIN incorrecto.', 401, 'bad_pin' );
    }

    if ( $amount <= 0 ) {
        return rmbz_api_error( 'Valor inválido.', 400, 'bad_amount' );
    }

    // [REGRA] Métodos válidos no formulário web: M-Pesa e E-Mola
    // Aceitamos também variantes para flexibilidade do cliente.
    $method_normalized = strtolower( str_replace( [ '-', ' ' ], '', $payment_method ) );
    $allowed_normalized = [ 'mpesa', 'emola' ];
    if ( ! in_array( $method_normalized, $allowed_normalized, true ) ) {
        return rmbz_api_error(
            'Método de pagamento inválido. Aceita-se M-Pesa ou E-Mola.',
            400, 'bad_method'
        );
    }

    // Normalizar para o formato canónico que o resto do plugin usa
    $payment_method_canonical = ( $method_normalized === 'mpesa' ) ? 'M-Pesa' : 'E-Mola';

    // [REGRA] Telefone normalizado a 9 dígitos
    if ( ! function_exists( 'rmbz_normalize_phone_mz' ) ) {
        return rmbz_api_error( 'Sistema indisponível.', 500, 'system' );
    }
    $normalized_phone = rmbz_normalize_phone_mz( $payment_details );
    if ( empty( $normalized_phone ) ) {
        return rmbz_api_error(
            'Número de telefone inválido. Deve ter 9 dígitos (ex: 841234567).',
            400, 'bad_phone'
        );
    }

    // [REGRA] Prefixo do telefone deve bater certo com o método
    $detected_op = function_exists( 'rmbz_phone_operator_mz' )
        ? rmbz_phone_operator_mz( $normalized_phone )
        : '';

    if ( $method_normalized === 'mpesa' && $detected_op !== 'mpesa' ) {
        return rmbz_api_error(
            'O número ' . $normalized_phone . ' não é M-Pesa. M-Pesa aceita números começados por 84 ou 85 (Vodacom).',
            400, 'phone_method_mismatch'
        );
    }
    if ( $method_normalized === 'emola' && $detected_op !== 'emola' ) {
        return rmbz_api_error(
            'O número ' . $normalized_phone . ' não é E-Mola. E-Mola aceita números começados por 86 ou 87 (Movitel).',
            400, 'phone_method_mismatch'
        );
    }

    $payment_details_canonical = $normalized_phone;

    // [REGRA] Mínimo
    $min = defined( 'RMBZ_MIN_WITHDRAWAL' ) ? (float) RMBZ_MIN_WITHDRAWAL : 50.0;
    if ( $amount < $min ) {
        return rmbz_api_error(
            sprintf( 'Mínimo de %s MT.', number_format( $min, 2, ',', '.' ) ),
            400, 'below_min'
        );
    }

    global $wpdb;

    // [REGRA] Lock pessimista (igual ao site web)
    $lock_name = 'rmbz_wd_' . md5( $telemovel );
    $got_lock = (int) $wpdb->get_var( $wpdb->prepare( "SELECT GET_LOCK(%s, 5)", $lock_name ) );
    if ( ! $got_lock ) {
        return rmbz_api_error(
            'Outro pedido teu já está a ser processado. Aguarda uns segundos.',
            409, 'locked'
        );
    }

    try {
        // [REGRA] Só 1 pendente
        $has_pending = $wpdb->get_var( $wpdb->prepare(
            "SELECT id FROM {$wpdb->prefix}rmbz_withdrawal_requests
             WHERE user_telemovel = %s AND status = 'pendente'",
            $telemovel
        ) );
        if ( $has_pending ) {
            return rmbz_api_error(
                'Já tens um pedido de levantamento pendente. Aguarda a conclusão antes de fazer um novo.',
                409, 'has_pending'
            );
        }

        // [REGRA] Saldo suficiente
        $available = function_exists( 'rmbz_get_withdrawable_balance' )
            ? rmbz_get_withdrawable_balance( $telemovel )
            : 0;

        if ( $amount > $available ) {
            return rmbz_api_error( 'Saldo insuficiente na carteira.', 400, 'insufficient' );
        }

        // [REGRA] DEBITAR o saldo (não só ficar pendente)
        if ( ! function_exists( 'rmbz_update_wallet_balance' ) ) {
            return rmbz_api_error( 'Sistema indisponível.', 500, 'system' );
        }
        $details = 'Levantamento (' . $payment_method_canonical . ': ' . $payment_details_canonical . ')';
        $debit_result = rmbz_update_wallet_balance(
            $telemovel,
            -$amount,
            'withdrawal_request',
            null,
            $details
        );

        if ( ! $debit_result['success'] ) {
            return rmbz_api_error(
                'Erro ao debitar saldo: ' . ( $debit_result['error'] ?? 'desconhecido' ),
                500, 'debit_failed'
            );
        }

        // Capturar metadados de origem para o registo
        $origin = $request->get_param( 'rmbz_origin' );
        if ( ! is_array( $origin ) ) {
            // Fallback se o tracker ainda não correu
            $origin = function_exists( 'rmbz_origin_capture' )
                ? rmbz_origin_capture( 'api' )
                : [ 'channel' => 'app_android', 'ip' => null, 'user_agent' => null,
                    'device_id' => null, 'app_version' => null ];
        }

        // Inserir o pedido COM metadados de origem
        $inserted = $wpdb->insert(
            $wpdb->prefix . 'rmbz_withdrawal_requests',
            [
                'user_telemovel'     => $telemovel,
                'amount'             => $amount,
                'source'             => 'wallet',
                'status'             => 'pendente',
                'payment_method'     => $payment_method_canonical,
                'payment_details'    => $payment_details_canonical,
                'request_date'       => current_time( 'mysql', 1 ),
                'origin_channel'     => $origin['channel'],
                'origin_ip'          => $origin['ip'],
                'origin_user_agent'  => $origin['user_agent'],
                'origin_device_id'   => $origin['device_id'],
                'origin_app_version' => $origin['app_version'],
            ],
            [ '%s', '%f', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' ]
        );

        if ( ! $inserted ) {
            // Rollback do débito
            rmbz_update_wallet_balance( $telemovel, $amount, 'withdrawal_rollback', null, 'Rollback do débito após falha de inserção.' );
            return rmbz_api_error( 'Falha ao criar o pedido.', 500, 'db_error' );
        }

        $id = (int) $wpdb->insert_id;

        // Guardar contacto
        if ( function_exists( 'rmbz_save_payout_contact' ) ) {
            rmbz_save_payout_contact( $telemovel, $payment_method_canonical, $payment_details_canonical );
        }

        // Audit log com canal
        if ( function_exists( 'rmbz_wg_log' ) ) {
            $channel_label = $origin['channel'] === 'app_android' ? 'app Android'
                           : ( $origin['channel'] === 'app_ios' ? 'app iOS'
                               : ( $origin['channel'] === 'web' ? 'site web' : $origin['channel'] ) );
            rmbz_wg_log(
                $id,
                'created',
                sprintf(
                    'Pedido criado pelo artista via %s. Fonte=Carteira, Valor=%s MT, Para=%s:%s. IP=%s, Dispositivo=%s',
                    $channel_label,
                    number_format( $amount, 2, ',', '.' ),
                    $payment_method_canonical,
                    $payment_details_canonical,
                    $origin['ip'] ?? '?',
                    $origin['device_id'] ? substr( $origin['device_id'], 0, 12 ) . '...' : '(sem device)'
                )
            );
        }

        do_action( 'rmbz_withdrawal_request_created', $id, $telemovel, $amount, 'api' );

        return rmbz_api_ok( [
            'id'      => $id,
            'status'  => 'pendente',
            'message' => 'Pedido enviado com sucesso. Será processado pela equipa.',
        ], 201 );

    } finally {
        // Libertar lock SEMPRE
        $wpdb->query( $wpdb->prepare( "SELECT RELEASE_LOCK(%s)", $lock_name ) );
    }
}

/* ----------------------------------------------------------------------- */
/* DEPOSITS                                                                */
/* ----------------------------------------------------------------------- */

function rmbz_api_handle_list_deposits( WP_REST_Request $request ) {
    $telemovel = (string) $request->get_param( 'rmbz_telemovel' );
    $page      = max( 1, (int) $request->get_param( 'page' ) );
    $per_page  = min( 100, max( 1, (int) $request->get_param( 'per_page' ) ) );
    $offset    = ( $page - 1 ) * $per_page;

    global $wpdb;

    // [1.5.199] Auto-expirar depósitos PENDENTES com mais de 1h.
    // Decisão: APAGAR (não marcar como expirado). User não vê histórico
    // de tentativas falhadas — limpo. Admin já não os vê na fila de
    // aprovação (poupa tempo).
    //
    // Executado em cada GET, defensivo (cron pode falhar). Custo: 1
    // DELETE rápido por sessão (indexado por status + request_date).
    //
    // O cron `rmbz_expire_pending_deposits` (hourly) faz limpeza
    // global; este DELETE serve para o user actual ver a lista limpa
    // sem ter de esperar pelo cron.
    //
    // [1.5.203] **BUG FIX CRÍTICO**: `request_date` é gravado em
    // `current_time('mysql', 1)` (UTC) — ver
    // rmbz_api_handle_create_deposit em subscription-controller.php.
    // Se o MySQL estiver configurado num fuso diferente (e.g. Maputo
    // UTC+2), o `NOW()` retorna 2h À FRENTE do que está em
    // `request_date`. Resultado: depósitos criados agora eram
    // imediatamente considerados "criados há 2h" e apagados.
    // Solução: usar `UTC_TIMESTAMP()` para que ambos os lados da
    // comparação estejam em UTC.
    //
    // [1.6.118.6] **BUG FIX CRÍTICO 2**: este DELETE estava a apagar
    // PRIMEIROS DEPÓSITOS de contas novas (que têm janela de 24h, não
    // 1h). Resultado: user criava conta, esperava aprovação, depósito
    // era apagado em <1h, e ao entrar na app o `firstDepositPending`
    // vinha null → `SubscriptionGate` deixava passar incorrectamente.
    // Excluir `is_first_deposit = 1` aqui — esses são geridos pelo CRON
    // `rmbz_first_deposit_expiry_check` que tem janela 24h + safeguards
    // (soft/hard delete consoante conteúdo gerado).
    $wpdb->query(
        "DELETE FROM {$wpdb->prefix}rmbz_deposit_requests
         WHERE status = 'pendente'
         AND is_first_deposit = 0
         AND request_date < DATE_SUB( UTC_TIMESTAMP(), INTERVAL 1 HOUR )"
    );

    $total = (int) $wpdb->get_var( $wpdb->prepare(
        "SELECT COUNT(*) FROM {$wpdb->prefix}rmbz_deposit_requests WHERE user_telemovel = %s",
        $telemovel
    ) );

    $rows = $wpdb->get_results( $wpdb->prepare(
        "SELECT id, amount, status, payment_method, transaction_id, proof_image, sender_phone,
                request_date, processed_date, admin_notes, source_platform, is_first_deposit
         FROM {$wpdb->prefix}rmbz_deposit_requests
         WHERE user_telemovel = %s
         ORDER BY id DESC
         LIMIT %d OFFSET %d",
        $telemovel, $per_page, $offset
    ) );

    // [1.5.199] Carregar números oficiais 1MZ uma vez para usar abaixo.
    // O user envia para `rmbz_mpesa_number` ou `rmbz_emola_number`
    // dependendo do método. Apenas relevante para depósitos pendentes;
    // depósitos aprovados/rejeitados podem ter o número actualmente
    // diferente do que era na altura.
    $mpesa_number = (string) rmbz_get_setting( 'rmbz_mpesa_number' );
    $emola_number = (string) rmbz_get_setting( 'rmbz_emola_number' );

    $items = array_map( function ( $r ) use ( $mpesa_number, $emola_number ) {
        // [1.5.199] Calcular expires_at = request_date + 1 hora
        // (formato RFC3339). Cliente usa para mostrar contador.
        // Só faz sentido em status='pendente' mas devolvemos sempre
        // para simplicidade — cliente ignora se não-pendente.
        $req_ts = strtotime( $r->request_date . ' UTC' );
        $expires_at = $req_ts ? gmdate( 'c', $req_ts + 3600 ) : null;

        // Número oficial 1MZ para o método usado neste depósito —
        // permite ao user repetir a transferência se algo correr mal.
        $official = '';
        if ( $r->payment_method === 'M-Pesa' ) {
            $official = $mpesa_number;
        } elseif ( $r->payment_method === 'E-Mola' ) {
            $official = $emola_number;
        }

        return [
            'id'              => (int) $r->id,
            'amount'          => (float) $r->amount,
            'status'          => $r->status,
            'payment_method'  => $r->payment_method,
            'transaction_id'  => $r->transaction_id,
            'proof_image'     => $r->proof_image,
            'sender_phone'    => $r->sender_phone,
            'official_number' => $official,    // [1.5.199] novo
            'expires_at'      => $expires_at,  // [1.5.199] novo
            'requested_at'    => mysql_to_rfc3339( $r->request_date ),
            'processed_at'    => $r->processed_date ? mysql_to_rfc3339( $r->processed_date ) : null,
            'admin_notes'     => $r->admin_notes,
            'rejection_reason'=> $r->status === 'rejeitado' ? (string) $r->admin_notes : '',
            'source_platform' => $r->source_platform,
            'is_first_deposit'=> ! empty( $r->is_first_deposit ),
        ];
    }, $rows ?: [] );

    return rmbz_api_ok( [
        'items' => $items,
        'pagination' => [
            'page'        => $page,
            'per_page'    => $per_page,
            'total'       => $total,
            'total_pages' => (int) ceil( $total / $per_page ),
        ],
    ] );
}

/* ----------------------------------------------------------------------- */
/* [1.5.199] CRON — Expirar depósitos pendentes após 1h                    */
/* ----------------------------------------------------------------------- */

/**
 * Cron worker hourly que apaga depósitos pendentes com mais de 1h.
 *
 * Razão: mesmo que o user não faça refresh da lista, queremos limpar
 * a tabela do servidor de tentativas perdidas. Admins não as querem
 * ver na fila de aprovação e o user pode entretanto criar nova
 * tentativa fresca.
 *
 * Agendado em `rmbz_register_deposit_expire_cron()` invocado uma vez
 * no boot do plugin (api-bootstrap).
 */
function rmbz_cron_expire_pending_deposits() {
    global $wpdb;
    // [1.5.203] UTC_TIMESTAMP em vez de NOW() — ver explicação no
    // rmbz_api_handle_list_deposits.
    //
    // [1.6.118.6] **BUG FIX**: excluir `is_first_deposit = 1` — esses
    // têm janela 24h e são geridos pelo CRON
    // `rmbz_first_deposit_expiry_check` em first-deposit-expiry.php.
    // Sem este filtro, primeiros depósitos eram apagados em 1h e a
    // app deixava de mostrar o `FirstDepositGateScreen`.
    $deleted = $wpdb->query(
        "DELETE FROM {$wpdb->prefix}rmbz_deposit_requests
         WHERE status = 'pendente'
         AND is_first_deposit = 0
         AND request_date < DATE_SUB( UTC_TIMESTAMP(), INTERVAL 1 HOUR )"
    );
    if ( $deleted > 0 ) {
        error_log( sprintf( '[rmbz] cron: %d depósitos pendentes (top-ups) expirados (>1h)', $deleted ) );
    }
}
add_action( 'rmbz_cron_expire_deposits', 'rmbz_cron_expire_pending_deposits' );

/* ----------------------------------------------------------------------- */
/* [1.5.205] CRON — Aviso de 5 min antes da expiração do depósito         */
/* ----------------------------------------------------------------------- */

/**
 * Cron worker que corre A CADA 5 MINUTOS e dispara push notification
 * "Faltam 5 min para o teu depósito expirar" aos users com pedidos
 * pendentes prestes a expirar.
 *
 * Lógica:
 *   - Procura depósitos `pendente` com expires_at (request_date + 1h)
 *     entre 4 e 6 minutos no futuro — window de 2min para apanhar
 *     todos sem depender de timing perfeito.
 *   - Filtra os que ainda não foram notificados (via transient
 *     `rmbz_deposit_5min_notified_{id}` com TTL de 30 min).
 *   - Dispara FCM para `user_telemovel`.
 *
 * Por que transient e não nova coluna BD:
 *   - Não precisa de schema migration
 *   - TTL automático limpa após expirar (não acumula lixo)
 *   - Atómico (set/get rápido)
 */
function rmbz_cron_warn_5min_expire() {
    global $wpdb;

    // Window: depósitos cujo expires_at está entre +4 e +6 minutos.
    // request_date + 1h = expires_at, logo expires_at - 5min = request_date + 55min
    // Queremos depósitos onde NOW está em [expires - 6min, expires - 4min]
    // = request_date em [NOW - 56min, NOW - 54min] em UTC.
    // [1.6.118.6] Excluir `is_first_deposit = 1` — esses têm janela
    // 24h, não 1h. Mandar notificação "expira em 5 min" quando ainda
    // faltam 23h seria confuso e errado. Os primeiros depósitos têm
    // o seu próprio aviso countdown na UI (FirstDepositGateScreen no
    // app, banner no site).
    $rows = $wpdb->get_results(
        "SELECT id, user_telemovel, amount, payment_method
         FROM {$wpdb->prefix}rmbz_deposit_requests
         WHERE status = 'pendente'
         AND is_first_deposit = 0
         AND request_date BETWEEN
             DATE_SUB( UTC_TIMESTAMP(), INTERVAL 56 MINUTE )
             AND DATE_SUB( UTC_TIMESTAMP(), INTERVAL 54 MINUTE )",
        ARRAY_A
    );

    if ( empty( $rows ) ) return;
    if ( ! function_exists( 'rmbz_fcm_send_to_user' ) ) return;

    foreach ( $rows as $row ) {
        $id = (int) $row['id'];
        $key = 'rmbz_deposit_5min_notified_' . $id;
        // Já notificado nesta janela? Skip.
        if ( get_transient( $key ) ) continue;

        $telemovel = (string) $row['user_telemovel'];
        $amount    = number_format( (float) $row['amount'], 2, '.', '' );
        $method    = strtoupper( (string) $row['payment_method'] );

        $title = '⏱ Depósito a expirar em 5 min';
        $body  = sprintf(
            'O teu depósito de %s MT por %s expira em 5 minutos. Conclui rapidamente.',
            $amount, $method
        );
        $data = array(
            'type'        => 'deposit_expiring',
            'deposit_id'  => (string) $id,
            'action_url'  => '/wallet?tab=deposits',
        );

        rmbz_fcm_send_to_user( $telemovel, $title, $body, $data );

        // Marca como notificado por 30 min (mais que tempo suficiente —
        // o depósito ou expira ou é aprovado nesse intervalo).
        set_transient( $key, 1, 30 * MINUTE_IN_SECONDS );
    }
}
add_action( 'rmbz_cron_warn_5min_expire', 'rmbz_cron_warn_5min_expire' );

/**
 * Schedule custom de 5 minutos — WordPress por default tem
 * `hourly`, `daily`, `twicedaily`. Precisamos de `every_5_minutes`.
 */
function rmbz_add_cron_schedule_5min( $schedules ) {
    if ( ! isset( $schedules['rmbz_every_5_minutes'] ) ) {
        $schedules['rmbz_every_5_minutes'] = array(
            'interval' => 5 * MINUTE_IN_SECONDS,
            'display'  => 'A cada 5 minutos (1MZ)',
        );
    }
    return $schedules;
}
add_filter( 'cron_schedules', 'rmbz_add_cron_schedule_5min' );

/**
 * Regista o cron de 5min no init. Idempotente.
 */
function rmbz_register_5min_warn_cron() {
    if ( ! wp_next_scheduled( 'rmbz_cron_warn_5min_expire' ) ) {
        wp_schedule_event( time() + 60, 'rmbz_every_5_minutes', 'rmbz_cron_warn_5min_expire' );
    }
}
add_action( 'init', 'rmbz_register_5min_warn_cron' );

/**
 * Regista o cron schedule no activate do plugin. Idempotente — se já
 * existir, `wp_schedule_event` retorna false silenciosamente.
 */
function rmbz_register_deposit_expire_cron() {
    if ( ! wp_next_scheduled( 'rmbz_cron_expire_deposits' ) ) {
        // Primeira execução em 1 minuto, depois a cada hora.
        wp_schedule_event( time() + 60, 'hourly', 'rmbz_cron_expire_deposits' );
    }
}
// Registar no init para garantir que está sempre activo (não só no
// activate). Custo zero se já agendado.
add_action( 'init', 'rmbz_register_deposit_expire_cron' );

/* ----------------------------------------------------------------------- */
/* SAVED PAYOUTS — contactos guardados para sugerir no formulário          */
/* ----------------------------------------------------------------------- */

function rmbz_api_handle_saved_payouts( WP_REST_Request $request ) {
    $telemovel = (string) $request->get_param( 'rmbz_telemovel' );

    if ( ! function_exists( 'rmbz_get_saved_payouts' ) ) {
        return rmbz_api_ok( [ 'payouts' => [] ] );
    }

    $payouts = rmbz_get_saved_payouts( $telemovel );

    return rmbz_api_ok( [
        'payouts' => array_map( function ( $p ) {
            return [
                'method' => $p['method'] ?? '',
                'number' => $p['number'] ?? '',
                'label'  => $p['label']  ?? '',
                'added'  => $p['added']  ?? null,
            ];
        }, $payouts ),
    ] );
}
