<?php
/**
 * includes/notifications-distro-bridge.php
 *
 * ============================================================================
 * BRIDGE entre eventos de distribuição/subscrições e o Centro de Notificações
 * ============================================================================
 *
 * [1.5.145] Liga os templates editáveis no Centro de Notificações (1.5.138)
 * aos triggers reais do código:
 *
 *   - release_returned  → quando admin marca release como returned
 *   - release_live      → quando release passa para live/distribuído
 *   - royalties_credited → após import CSV (só para users que receberam)
 *   - vip_expired       → cron diário, dia em que subscrição expira
 *
 * Cada listener é IDEMPOTENTE — só dispara uma vez por evento real
 * (idempotência garantida por flags em extra_data ou pela natureza
 * one-shot do hook).
 *
 * Falha silenciosa: se Centro de Notificações não está carregado
 * (rmbz_notify ausente), os listeners não fazem nada — código permanece
 * resiliente.
 */

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


// ============================================================================
// release_returned — quando admin marca release como returned
// ============================================================================

/**
 * Listener: dispara quando hook rmbz_distro_release_returned (definido em
 * distribution-return-workflow.php:191) é accionado.
 *
 * @param int   $release_id
 * @param array $problems_clean  Lista de [code, label, note]
 * @param string $note            Nota geral do admin
 */
function rmbz_notif_on_release_returned( $release_id, $problems_clean, $note ) {
    if ( ! function_exists('rmbz_notify') ) return;

    global $wpdb;
    // [1.5.154] Bug fix: coluna correcta é `artist_telemovel`, não `user_telemovel`.
    // Bug detectado por smoke tests da 1.5.153. Antes deste fix, esta notificação
    // NUNCA disparava (SELECT silenciosamente falhava).
    $rel = $wpdb->get_row( $wpdb->prepare(
        "SELECT id, title, artist_telemovel FROM {$wpdb->prefix}rmbz_distro_releases WHERE id = %d",
        (int) $release_id
    ) );
    if ( ! $rel ) return;
    if ( empty($rel->artist_telemovel) ) return;

    // Construir motivo legível: lista de problemas + nota do admin
    $motivo_lines = [];
    foreach ( (array) $problems_clean as $p ) {
        $line = '• ' . ( $p['label'] ?? $p['code'] ?? '?' );
        if ( ! empty($p['note']) ) $line .= ': ' . $p['note'];
        $motivo_lines[] = $line;
    }
    if ( ! empty($note) ) {
        $motivo_lines[] = '';
        $motivo_lines[] = 'Nota: ' . $note;
    }
    $motivo = implode( "\n", $motivo_lines );

    rmbz_notify( $rel->artist_telemovel, 'release_returned', [
        'titulo_release' => $rel->title ?? ('Release #' . (int) $rel->id),
        'motivo'         => $motivo ?: 'Sem detalhes adicionais.',
    ]);
}
add_action( 'rmbz_distro_release_returned', 'rmbz_notif_on_release_returned', 10, 3 );


// ============================================================================
// release_live — quando release passa para live/distribuído
// ============================================================================

/**
 * Listener: dispara quando rmbz_distro_status_changed (definido em
 * distribution-return-workflow.php:190 e em outros sítios do workflow)
 * indica transição para 'live' ou 'distribuido'.
 *
 * Idempotência: o hook só é disparado quando há transição real, e
 * adicionalmente verificamos que o NOVO status é live mas o ANTIGO não era.
 *
 * @param int    $release_id
 * @param string $old_status
 * @param string $new_status
 */
function rmbz_notif_on_release_live( $release_id, $old_status, $new_status ) {
    if ( ! function_exists('rmbz_notify') ) return;

    // Só interessa quando passa PARA live/distribuido vindo de outro estado
    $live_states = ['live', 'distribuido'];
    if ( ! in_array( $new_status, $live_states, true ) ) return;
    if ( in_array( $old_status, $live_states, true ) ) return;

    global $wpdb;
    // [1.5.154] Bug fix: artist_telemovel (não user_telemovel). Ver fix da
    // função `rmbz_notif_on_release_returned` acima — mesma causa raiz.
    $rel = $wpdb->get_row( $wpdb->prepare(
        "SELECT id, title, artist_telemovel FROM {$wpdb->prefix}rmbz_distro_releases WHERE id = %d",
        (int) $release_id
    ) );
    if ( ! $rel ) return;
    if ( empty($rel->artist_telemovel) ) return;

    rmbz_notify( $rel->artist_telemovel, 'release_live', [
        'titulo_release' => $rel->title ?? ('Release #' . (int) $rel->id),
    ]);
}
add_action( 'rmbz_distro_status_changed', 'rmbz_notif_on_release_live', 10, 3 );


// ============================================================================
// royalties_credited — após import CSV de relatórios
// ============================================================================

/**
 * Listener: chamado pela função de import CSV depois dos créditos serem
 * aplicados. O caller passa um array de utilizadores que receberam,
 * com [tel => total_usd_creditado].
 *
 * Não usa um hook do WordPress padrão (porque o import não dispara um),
 * mas sim uma função pública que o módulo de import deve chamar.
 *
 * IMPORTANTE: este listener tem que ser chamado EXPLICITAMENTE pelo
 * módulo de import — não há auto-magic possível porque os créditos são
 * inseridos em massa via SQL e não disparam hooks individuais.
 *
 * @param array $credits_by_user  ['+258...' => 12.34, '+258...' => 0.05, ...]
 * @param int|string $import_hash  Hash do import (para logs)
 */
function rmbz_notif_dispatch_royalties_credited( $credits_by_user, $import_hash = '', $reporting_period = '' ) {
    if ( empty($credits_by_user) ) return;

    // [FEAT 1.6.14] Construir label PT-PT do período (ex: "Janeiro 2026")
    // sem expor distribuidoras internas (Amadea/Zebralution/etc) — os artistas
    // só conhecem a 1MZ como a sua distribuidora.
    $period_label = '';
    if ( ! empty( $reporting_period ) && preg_match('/^(\d{4})-(\d{2})$/', $reporting_period, $pm) ) {
        $yr = (int) $pm[1];
        $mo = (int) $pm[2];
        if ( function_exists('rmbz_month_name_pt') ) {
            $mname = rmbz_month_name_pt( $mo );
            if ( $mname ) {
                $period_label = ucfirst( $mname ) . ' ' . $yr;
            }
        }
        if ( $period_label === '' ) {
            // Fallback se o helper não estiver disponível
            $names = ['', 'Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho',
                      'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'];
            $period_label = ( $names[$mo] ?? '' ) . ' ' . $yr;
        }
    }

    foreach ( $credits_by_user as $tel => $total_usd ) {
        $usd = (float) $total_usd;
        // Filtro: ignorar créditos < $0.01 (ruído de match review microcêntimos)
        if ( $usd < 0.01 ) continue;

        if ( empty($tel) ) continue;

        $valor_fmt = number_format( $usd, 2, '.', '' );

        // ────────────────────────────────────────────────────────────────────
        // [FIX] Sistema NOVO de notificações (rmbz_notifications) — é DAQUI
        // que a app e o centro de notificações leem. O `rmbz_notify` antigo
        // (em baixo) só grava em rmbz_email_logs (email + sino legado), por
        // isso a notificação de royalties NUNCA chegava à app.
        // Espelha o padrão de rmbz_notif_event_withdrawal_approved().
        // ────────────────────────────────────────────────────────────────────
        if ( function_exists('rmbz_notify_create') ) {
            $valor_disp = number_format( $usd, 2, ',', '.' );

            // [FEAT 1.6.14] Body humano com período. Nunca expor distribuidora
            // interna — só "1MZ" + período + valor.
            if ( $period_label ) {
                $body = '<strong>' . $period_label . '</strong> — <strong>' . $valor_disp . ' USD</strong> já estão na tua wallet 🎉';
            } else {
                // Fallback sem período (não devia acontecer mas seguro)
                $body = '<strong>' . $valor_disp . ' USD</strong> já estão na tua wallet 🎉';
            }

            rmbz_notify_create([
                'type'             => 'royalties_credited',
                'severity'         => 'success',
                'title'            => '💵 Royalties creditados',
                'body'             => $body,
                'target_telemovel' => $tel,
                'dedup_key'        => 'royalties_credited:' . $import_hash . ':' . preg_replace('/[^0-9+]/', '', (string) $tel),
                'expires_at'       => gmdate( 'Y-m-d H:i:s', time() + (30 * 86400) ),
                'created_by'       => 'system',
                'metadata'         => [
                    'amount_usd'        => $valor_fmt,
                    'reporting_period'  => $reporting_period,
                    'period_label'      => $period_label,
                ],
            ]);
        }

        // Sistema ANTIGO (email + sino do site) — mantido para não perder o
        // email. NÃO substituir: a regra do projecto é só adicionar.
        if ( function_exists('rmbz_notify') ) {
            rmbz_notify( $tel, 'royalties_credited', [
                'valor'        => $valor_fmt,
                'moeda'        => 'USD',
                'periodo'      => $period_label, // [FEAT 1.6.14] disponível para templates
            ]);
        }

        // [1.5.290] Primeiro relatório → desbloqueia o Assistente de IA.
        // Corre uma única vez por artista (flag em extra_data).
        rmbz_notif_ai_unlock_first_report( $tel );
    }
}

/**
 * [1.5.290] Quando um artista recebe o seu PRIMEIRO relatório de royalties,
 * celebra e avisa (em todos os canais) que desbloqueou o Assistente de IA —
 * e que para o usar precisa de uma subscrição de distribuição activa.
 *
 * Idempotente: marca uma flag em rmbz_wallets.extra_data ('ai_unlock_notified')
 * para nunca repetir.
 *
 * @param string $tel Telemóvel do artista.
 */
function rmbz_notif_ai_unlock_first_report( $tel ) {
    if ( empty( $tel ) ) return;

    global $wpdb;
    $t_wallets = $wpdb->prefix . 'rmbz_wallets';

    $row = $wpdb->get_row( $wpdb->prepare(
        "SELECT user_nome, extra_data FROM $t_wallets WHERE user_telemovel = %s",
        $tel
    ) );
    if ( ! $row ) return;

    $extra = ! empty( $row->extra_data ) ? json_decode( $row->extra_data, true ) : [];
    if ( ! is_array( $extra ) ) $extra = [];

    // Já foi notificado — não repetir.
    if ( ! empty( $extra['ai_unlock_notified'] ) ) return;

    // Marcar ANTES de enviar (evita duplicados em caso de re-processamento).
    $extra['ai_unlock_notified'] = 1;
    $wpdb->update(
        $t_wallets,
        [ 'extra_data' => wp_json_encode( $extra ) ],
        [ 'user_telemovel' => $tel ],
        [ '%s' ],
        [ '%s' ]
    );

    $nome  = trim( (string) ( $row->user_nome ?: '' ) );
    $primeiro = $nome !== '' ? explode( ' ', $nome )[0] : '';
    $ola   = $primeiro !== '' ? ", $primeiro" : '';

    $title = '🤖 Assistente de IA desbloqueado!';
    $body_app = "Parabéns$ola! O teu primeiro relatório chegou e desbloqueaste o "
        . "Assistente de IA da 1MZ para alavancares a tua carreira. Para o usares, "
        . "adiciona o add-on do Assistente de IA quando ativares ou renovares o teu plano.";

    // 1) Centro de notificações / app (rmbz_notifications)
    if ( function_exists( 'rmbz_notify_create' ) ) {
        rmbz_notify_create([
            'type'             => 'ai_unlocked',
            'severity'         => 'success',
            'title'            => $title,
            'body'             => $body_app,
            'target_telemovel' => $tel,
            'dedup_key'        => 'ai_unlocked:' . preg_replace( '/[^0-9+]/', '', (string) $tel ),
            'expires_at'       => gmdate( 'Y-m-d H:i:s', time() + ( 60 * 86400 ) ),
            'created_by'       => 'system',
            'metadata'         => [ 'route' => 'insights' ],
        ]);
    }

    // 2) Push (FCM)
    if ( function_exists( 'rmbz_fcm_send_to_user' ) ) {
        rmbz_fcm_send_to_user(
            $tel,
            $title,
            "Desbloqueaste a IA da 1MZ! Ativa o add-on no teu plano e usa-a para alavancar a tua música.",
            [ 'type' => 'ai_unlocked', 'route' => 'insights' ]
        );
    }

    // 3) Email + sino do site (sistema antigo, via template 'ai_unlocked')
    if ( function_exists( 'rmbz_notify' ) ) {
        rmbz_notify( $tel, 'ai_unlocked', [] );
    }
}


// ============================================================================
// vip_expired — cron diário, no dia em que a subscrição expira
// ============================================================================

/**
 * Cron job: passa por todas as subscrições activas que expiraram nas
 * últimas 24h e ainda não foram notificadas. Marca uma flag em extra_data
 * para não duplicar.
 *
 * Hooked em rmbz_unlock_funds_hook (cron diário existente).
 */
function rmbz_notif_cron_vip_expired() {
    if ( ! function_exists('rmbz_notify') ) return;
    global $wpdb;

    $table_subs = $wpdb->prefix . 'rmbz_subscriptions';
    if ( $wpdb->get_var("SHOW TABLES LIKE '$table_subs'") !== $table_subs ) return;

    // Subscrições activas que expiraram nas últimas 24h (janela suficiente
    // para apanhar mesmo que o cron falhe num dia)
    $rows = $wpdb->get_results(
        "SELECT s.user_telemovel, s.end_date, w.extra_data
         FROM $table_subs s
         JOIN {$wpdb->prefix}rmbz_wallets w ON w.user_telemovel = s.user_telemovel
         WHERE s.status = 'active'
           AND s.end_date <= NOW()
           AND s.end_date >= DATE_SUB(NOW(), INTERVAL 24 HOUR)"
    );

    if ( empty($rows) ) return;

    foreach ( $rows as $r ) {
        $extra = ! empty($r->extra_data) ? json_decode($r->extra_data, true) : [];
        if ( ! is_array($extra) ) $extra = [];

        // Já notificado para esta subscrição? (idempotência por end_date)
        if ( isset($extra['vip_expired_notified']) && $extra['vip_expired_notified'] === $r->end_date ) {
            continue;
        }

        $sent = rmbz_notify( $r->user_telemovel, 'vip_expired', [] );

        if ( $sent ) {
            $extra['vip_expired_notified'] = $r->end_date;
            $wpdb->update(
                $wpdb->prefix . 'rmbz_wallets',
                [ 'extra_data' => wp_json_encode($extra) ],
                [ 'user_telemovel' => $r->user_telemovel ],
                ['%s'], ['%s']
            );
        }
    }
}
add_action( 'rmbz_unlock_funds_hook', 'rmbz_notif_cron_vip_expired' );

// ============================================================================
// [1.6.118.75] filming_ending_soon — cron diário, quando falta 1 dia para
// terminar a gravação. Notifica por todos os canais: FCM push + email + sino.
// ============================================================================

/**
 * Cron job: procura projectos cuja data de FIM de gravação é AMANHÃ, que
 * estão confirmados pelo artista e em estado aprovado, e ainda não foram
 * notificados. Marca flag em extra_data para não duplicar.
 *
 * Hooked em rmbz_unlock_funds_hook (cron diário existente).
 */
function rmbz_notif_cron_filming_ending() {
    if ( ! function_exists( 'rmbz_notify' ) ) return;
    global $wpdb;

    $table = $wpdb->prefix . 'rmbz_artist_projects';
    if ( $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) !== $table ) return;

    // Amanhã no formato Y-m-d (timezone do site)
    $tomorrow = date( 'Y-m-d', current_time( 'timestamp' ) + DAY_IN_SECONDS );

    // Estados onde faz sentido (filming/editing/etc — projecto aprovado).
    // Procuramos no extra_data (JSON) — fazemos LIKE grosseiro e validamos
    // em PHP para evitar regex complexo no SQL.
    $rows = $wpdb->get_results( $wpdb->prepare(
        "SELECT id, project_title, artist_telemovel, production_status, extra_data
         FROM $table
         WHERE extra_data LIKE %s
           AND production_status NOT IN ('pending_review','rejected','correction')",
        '%' . $wpdb->esc_like( $tomorrow ) . '%'
    ) );

    if ( empty( $rows ) ) return;

    foreach ( $rows as $r ) {
        $extra = ! empty( $r->extra_data ) ? json_decode( $r->extra_data, true ) : [];
        if ( ! is_array( $extra ) ) continue;

        $f_end  = isset( $extra['filming_end'] ) ? (string) $extra['filming_end'] : '';
        $f_conf = ! empty( $extra['filming_confirmed_by_artist'] );

        // Só notifica se: fim é mesmo amanhã + artista confirmou as datas
        if ( $f_end !== $tomorrow || ! $f_conf ) continue;

        // Idempotência: já notificámos para esta data de fim?
        if ( isset( $extra['filming_ending_notified'] ) && $extra['filming_ending_notified'] === $f_end ) {
            continue;
        }

        $tel    = (string) $r->artist_telemovel;
        $musica = (string) $r->project_title;
        $de     = date_i18n( 'd/m/Y', strtotime( $f_end ) );

        if ( $tel === '' ) continue;

        // 1) FCM push
        if ( function_exists( 'rmbz_fcm_send_to_user' ) ) {
            rmbz_fcm_send_to_user(
                $tel,
                '⏳ Filmagens terminam amanhã',
                sprintf( 'A gravação de "%s" termina amanhã (%s). Prepara o último dia!', $musica, $de ),
                [
                    'type'          => 'filming_ending_soon',
                    'project_id'    => (string) $r->id,
                    'project_title' => $musica,
                    'filming_end'   => $f_end,
                    'deeplink'      => 'onemz://projects/' . $r->id,
                ]
            );
        }

        // 2) + 3) Email + sino
        $sent = rmbz_notify( $tel, 'filming_ending_soon', [
            'musica'   => $musica,
            'data_fim' => $de,
        ] );

        // Marca como notificado (mesmo que só o push tenha ido — evita spam)
        $extra['filming_ending_notified'] = $f_end;
        $wpdb->update(
            $table,
            [ 'extra_data' => wp_json_encode( $extra ) ],
            [ 'id' => (int) $r->id ],
            ['%s'], ['%d']
        );

        error_log( sprintf(
            '[1MZ cron filming_ending] proj=%d artist=%s fim=%s notif=%s',
            $r->id, $tel, $f_end, $sent ? 'OK' : 'push_only/fail'
        ) );
    }
}
add_action( 'rmbz_unlock_funds_hook', 'rmbz_notif_cron_filming_ending' );
