<?php
/**
 * 1MZ For Artists — Origin Stamping para Conversões USD→MT
 *
 * Problema:
 *   A função canónica rmbz_secure_transfer_usd_to_mt() cria entradas em:
 *     - rmbz_distro_ledger        (type='withdrawal_to_wallet', currency='USD')
 *     - rmbz_wallet_transactions  (type='distro_payout')
 *   ligadas via reference_id, mas SEM rastro de origem (channel/IP/device).
 *
 *   Resultado: o Guarda-Costas, ao auditar um pedido de levantamento
 *   subsequente, vê a conversão na janela esperada mas não consegue
 *   confirmar que veio do mesmo canal/device do pedido. Se uma conta
 *   for comprometida e o atacante fizer conversão+levantamento via
 *   app, não há forma de cruzar canais.
 *
 * Solução:
 *   1. Estender o schema das 2 tabelas com 5 colunas de origem
 *      (origin_channel, origin_ip, origin_user_agent, origin_device_id,
 *      origin_app_version). Migração idempotente.
 *
 *   2. Helper rmbz_stamp_conversion_origin() chamado pela camada REST
 *      DEPOIS de rmbz_secure_transfer_usd_to_mt() ter sucesso. Identifica
 *      as 2 linhas pelo reference_id (CONV_*) que a função canónica cria,
 *      e faz UPDATE com a origem capturada.
 *
 *   3. NÃO modifica a função canónica nem o site — comportamento antigo
 *      continua igual; quando origem é gravada, o Guarda-Costas pode usar.
 *
 * @package 1MZ_For_Artists
 * @since 1.6.24
 */

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

if ( ! defined( 'RMBZ_CONVERSION_ORIGIN_SCHEMA_VERSION' ) ) {
    define( 'RMBZ_CONVERSION_ORIGIN_SCHEMA_VERSION', '1.0.0' );
}


/* ============================================================================
 * SCHEMA — adiciona colunas de origem às 2 tabelas afectadas
 * ============================================================================ */

add_action( 'init', 'rmbz_conversion_origin_maybe_install_schema', 6 );

function rmbz_conversion_origin_maybe_install_schema() {
    $current = get_option( 'rmbz_conversion_origin_schema_v', '0' );
    if ( $current === RMBZ_CONVERSION_ORIGIN_SCHEMA_VERSION ) return;

    global $wpdb;

    $tables = [
        $wpdb->prefix . 'rmbz_distro_ledger',
        $wpdb->prefix . 'rmbz_wallet_transactions',
    ];

    $cols_to_add = [
        'origin_channel'     => "varchar(20) DEFAULT NULL",
        'origin_ip'          => "varchar(45) DEFAULT NULL",
        'origin_user_agent'  => "varchar(255) DEFAULT NULL",
        'origin_device_id'   => "varchar(64) DEFAULT NULL",
        'origin_app_version' => "varchar(20) DEFAULT NULL",
    ];

    foreach ( $tables as $table ) {
        // Verifica se a tabela existe (defensivo — pode ainda não ter corrido
        // o schema-patch principal em instalações novas)
        if ( $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) !== $table ) {
            continue;
        }

        $existing_cols = $wpdb->get_col( "SHOW COLUMNS FROM $table", 0 );
        foreach ( $cols_to_add as $col_name => $col_def ) {
            if ( ! in_array( $col_name, $existing_cols, true ) ) {
                $wpdb->query( "ALTER TABLE $table ADD COLUMN $col_name $col_def" );
            }
        }

        // Index para queries rápidas no Guarda-Costas
        $indexes = $wpdb->get_results( "SHOW INDEX FROM $table" );
        $idx_names = array_map( function( $i ) { return $i->Key_name; }, (array) $indexes );
        if ( ! in_array( 'idx_origin_device', $idx_names, true ) ) {
            $wpdb->query( "ALTER TABLE $table ADD INDEX idx_origin_device (origin_device_id)" );
        }
    }

    update_option( 'rmbz_conversion_origin_schema_v', RMBZ_CONVERSION_ORIGIN_SCHEMA_VERSION );
}


/* ============================================================================
 * STAMP — após uma conversão bem-sucedida, marca origem nas 2 linhas
 * ============================================================================ */

/**
 * Carimba a origem nas 2 linhas que a função rmbz_secure_transfer_usd_to_mt()
 * criou. Identifica pelo padrão de reference_id (CONV_YmdHis_xxx) que a
 * função canónica gera no momento da inserção.
 *
 * Como a função canónica não devolve o reference_id criado, identificamos
 * pela linha mais recente do artista no ledger USD com type='withdrawal_to_wallet'
 * dentro de uma janela curta (10 segundos). Esta é a forma mais segura e
 * que NÃO requer modificar a função canónica.
 *
 * Idempotente: se já tiver origem gravada, não sobrescreve (defensivo).
 *
 * @param string     $telemovel    Telemóvel do artista
 * @param array|null $origin       Resultado de rmbz_origin_capture(), ou null
 *                                 para capturar agora dos $_SERVER
 * @param int        $window_secs  Janela de busca em segundos (default: 10)
 * @return array ['ledger_id' => int|null, 'wallet_trans_id' => int|null,
 *                'updated' => bool, 'reason' => string]
 */
function rmbz_stamp_conversion_origin( $telemovel, $origin = null, $window_secs = 10 ) {
    global $wpdb;

    if ( empty( $telemovel ) ) {
        return [ 'ledger_id' => null, 'wallet_trans_id' => null,
                 'updated' => false, 'reason' => 'no_telemovel' ];
    }

    if ( $origin === null ) {
        if ( ! function_exists( 'rmbz_origin_capture' ) ) {
            return [ 'ledger_id' => null, 'wallet_trans_id' => null,
                     'updated' => false, 'reason' => 'origin_helper_missing' ];
        }
        $origin = rmbz_origin_capture( 'web' );
    }

    $ledger_table = $wpdb->prefix . 'rmbz_distro_ledger';
    $wallet_trans_table = $wpdb->prefix . 'rmbz_wallet_transactions';

    // Verificar que as colunas existem (idempotência)
    $ledger_cols = $wpdb->get_col( "SHOW COLUMNS FROM $ledger_table", 0 );
    if ( ! in_array( 'origin_channel', $ledger_cols, true ) ) {
        // Schema ainda não correu — tentar agora
        rmbz_conversion_origin_maybe_install_schema();
        $ledger_cols = $wpdb->get_col( "SHOW COLUMNS FROM $ledger_table", 0 );
        if ( ! in_array( 'origin_channel', $ledger_cols, true ) ) {
            return [ 'ledger_id' => null, 'wallet_trans_id' => null,
                     'updated' => false, 'reason' => 'schema_not_ready' ];
        }
    }

    $cutoff = date( 'Y-m-d H:i:s', time() - max( 1, (int) $window_secs ) );

    // Encontrar a entrada do ledger USD mais recente para este artista,
    // do tipo conversão, dentro da janela. Só actualiza se ainda não tiver
    // origem gravada (idempotente).
    $ledger_row = $wpdb->get_row( $wpdb->prepare(
        "SELECT id, reference_id, origin_channel
         FROM $ledger_table
         WHERE artist_telemovel = %s
           AND type = 'withdrawal_to_wallet'
           AND created_at >= %s
         ORDER BY id DESC
         LIMIT 1",
        $telemovel, $cutoff
    ) );

    if ( ! $ledger_row ) {
        return [ 'ledger_id' => null, 'wallet_trans_id' => null,
                 'updated' => false, 'reason' => 'no_recent_conversion' ];
    }

    $ledger_id = (int) $ledger_row->id;
    $already_stamped = ! empty( $ledger_row->origin_channel );

    $update_data = [
        'origin_channel'     => $origin['channel']     ?? null,
        'origin_ip'          => $origin['ip']          ?? null,
        'origin_user_agent'  => $origin['user_agent']  ?? null,
        'origin_device_id'   => $origin['device_id']   ?? null,
        'origin_app_version' => $origin['app_version'] ?? null,
    ];
    $update_fmt = [ '%s', '%s', '%s', '%s', '%s' ];

    if ( ! $already_stamped ) {
        $wpdb->update(
            $ledger_table,
            $update_data,
            [ 'id' => $ledger_id ],
            $update_fmt,
            [ '%d' ]
        );
    }

    // Encontrar a linha de wallet_transactions ligada (pela reference_id que
    // aponta ao ledger_id). Esta é a forma fiável — não dependemos de timing.
    $wt_row = $wpdb->get_row( $wpdb->prepare(
        "SELECT id, origin_channel
         FROM $wallet_trans_table
         WHERE user_telemovel = %s
           AND type = 'distro_payout'
           AND reference_id = %d
         ORDER BY id DESC
         LIMIT 1",
        $telemovel, $ledger_id
    ) );

    $wt_id = $wt_row ? (int) $wt_row->id : null;

    if ( $wt_row && empty( $wt_row->origin_channel ) ) {
        $wpdb->update(
            $wallet_trans_table,
            $update_data,
            [ 'id' => $wt_id ],
            $update_fmt,
            [ '%d' ]
        );
    }

    return [
        'ledger_id'       => $ledger_id,
        'wallet_trans_id' => $wt_id,
        'updated'         => ! $already_stamped,
        'reason'          => $already_stamped ? 'already_stamped' : 'ok',
    ];
}


/* ============================================================================
 * GUARDA-COSTAS — mostrar origem da conversão no relatório forense
 * ============================================================================ */

/**
 * Filtro que enriquece o relatório do Guarda-Costas com a origem da
 * conversão USD→MT que precedeu o pedido de levantamento. Detecta
 * mismatch entre canal/device da conversão vs do pedido.
 *
 * Hook: rmbz_wg_forensic_report (filtro definido em admin-withdrawal-guard.php)
 */
add_filter( 'rmbz_wg_forensic_report', 'rmbz_conversion_origin_inject_into_guard', 20, 2 );

function rmbz_conversion_origin_inject_into_guard( $report, $withdrawal ) {
    // Só nos interessa pedidos de royalties (que vêm de conversões)
    $source = isset( $withdrawal->source ) ? $withdrawal->source : 'wallet';
    if ( $source !== 'royalties' ) return $report;

    // Já temos a conversão capturada pelo guard original?
    $conversion = $report['source']['evidence']['conversion_ledger'] ?? null;
    if ( ! $conversion ) return $report;

    // Garantir que temos a estrutura
    if ( ! isset( $report['conversion_origin'] ) ) {
        $report['conversion_origin'] = [];
    }

    $conv_channel  = $conversion->origin_channel    ?? null;
    $conv_ip       = $conversion->origin_ip         ?? null;
    $conv_device   = $conversion->origin_device_id  ?? null;
    $conv_appver   = $conversion->origin_app_version ?? null;
    $conv_ua       = $conversion->origin_user_agent ?? null;

    $report['conversion_origin'] = [
        'channel'     => $conv_channel,
        'ip'          => $conv_ip,
        'device_id'   => $conv_device,
        'app_version' => $conv_appver,
        'user_agent'  => $conv_ua,
    ];

    // Comparar com origem do pedido para detectar mismatches
    $wd_channel = $withdrawal->origin_channel    ?? null;
    $wd_ip      = $withdrawal->origin_ip         ?? null;
    $wd_device  = $withdrawal->origin_device_id  ?? null;

    $flags = [];

    // Se a conversão não tem origem gravada, é uma conversão antiga
    // (antes da feature) — flag baixo, só info
    if ( empty( $conv_channel ) ) {
        $flags[] = [
            'severity' => 'info',
            'code'     => 'conversion_origin_missing',
            'message'  => 'Conversão USD→MT não tem rastro de origem '
                        . '(provavelmente feita antes do sistema de stamping ' .
                        'na app v1.6.24).',
        ];
    } else {
        // Comparar canais
        if ( $wd_channel && $conv_channel && $wd_channel !== $conv_channel ) {
            $flags[] = [
                'severity' => 'high',
                'code'     => 'conversion_channel_mismatch',
                'message'  => sprintf(
                    'CANAL DIFERENTE: conversão veio de "%s" mas pedido '
                    . 'de levantamento veio de "%s". Investigar — pode '
                    . 'indicar tomada de conta.',
                    $conv_channel, $wd_channel
                ),
            ];
        }

        // Comparar devices (só se ambos forem da app)
        if ( $wd_device && $conv_device && $wd_device !== $conv_device ) {
            $flags[] = [
                'severity' => 'high',
                'code'     => 'conversion_device_mismatch',
                'message'  => sprintf(
                    'DEVICE DIFERENTE: conversão device_id "%s..." vs '
                    . 'pedido device_id "%s...". Forte indício de '
                    . 'comprometimento de conta.',
                    substr( $conv_device, 0, 12 ),
                    substr( $wd_device, 0, 12 )
                ),
            ];
        }

        // Comparar IPs (medium — IPs móveis mudam normalmente)
        if ( $wd_ip && $conv_ip && $wd_ip !== $conv_ip ) {
            $flags[] = [
                'severity' => 'medium',
                'code'     => 'conversion_ip_mismatch',
                'message'  => sprintf(
                    'IP diferente entre conversão (%s) e pedido (%s). '
                    . 'Comum em redes móveis, mas verificar geolocalização.',
                    $conv_ip, $wd_ip
                ),
            ];
        }
    }

    // Adicionar flags ao relatório principal
    if ( ! empty( $flags ) ) {
        if ( ! isset( $report['flags'] ) ) $report['flags'] = [];
        $report['flags'] = array_merge( $report['flags'], $flags );
    }

    $report['conversion_origin']['flags'] = $flags;

    return $report;
}


/* ============================================================================
 * UI — render no painel do Guarda-Costas
 * ============================================================================ */

/**
 * Mostra a secção "Origem da Conversão" no Guarda-Costas, abaixo da
 * secção "Origem do Dinheiro" original. Hook: rmbz_wg_render_after_source
 * (definido em admin-withdrawal-guard.php).
 *
 * Se esse hook não existir no plugin do utilizador, esta função é
 * inofensiva — simplesmente nunca corre.
 */
add_action( 'rmbz_wg_render_after_source', 'rmbz_conversion_origin_render_section', 10, 2 );

function rmbz_conversion_origin_render_section( $report, $withdrawal ) {
    $origin = $report['conversion_origin'] ?? null;
    if ( ! $origin ) return;

    $ch  = $origin['channel']     ?? '—';
    $ip  = $origin['ip']          ?? '—';
    $dev = $origin['device_id']   ?? '—';
    $ver = $origin['app_version'] ?? '—';
    $flags = $origin['flags']     ?? [];

    ?>
    <div style="margin-top:20px; background:#1e1e1e; padding:15px;
                border-left:4px solid #ff6b00; border-radius:6px;">
        <h3 style="margin:0 0 10px 0; color:#ff6b00; font-size:15px;">
            🔍 Origem da Conversão USD→MT
        </h3>
        <table style="width:100%; color:#ddd; font-size:13px;">
            <tr><td style="padding:4px 0; opacity:0.7;">Canal:</td>
                <td><strong><?php echo esc_html( $ch ); ?></strong></td></tr>
            <tr><td style="padding:4px 0; opacity:0.7;">IP:</td>
                <td><code><?php echo esc_html( $ip ); ?></code></td></tr>
            <tr><td style="padding:4px 0; opacity:0.7;">Device ID:</td>
                <td><code><?php echo esc_html( substr( $dev, 0, 24 ) . ( strlen( $dev ) > 24 ? '…' : '' ) ); ?></code></td></tr>
            <tr><td style="padding:4px 0; opacity:0.7;">App version:</td>
                <td><?php echo esc_html( $ver ); ?></td></tr>
        </table>

        <?php if ( ! empty( $flags ) ): ?>
            <div style="margin-top:12px;">
                <?php foreach ( $flags as $f ):
                    $color = match( $f['severity'] ) {
                        'high'   => '#e74c3c',
                        'medium' => '#f0b90b',
                        default  => '#3498db',
                    };
                ?>
                    <div style="background:rgba(0,0,0,0.3); border-left:3px solid <?php echo $color; ?>;
                                padding:8px 12px; margin-top:6px; font-size:12px; color:#fff;">
                        <strong style="color:<?php echo $color; ?>;">
                            [<?php echo esc_html( strtoupper( $f['severity'] ) ); ?>]
                        </strong>
                        <?php echo esc_html( $f['message'] ); ?>
                    </div>
                <?php endforeach; ?>
            </div>
        <?php endif; ?>
    </div>
    <?php
}
