<?php
// includes/distribution/distribution-lifecycle.php
//
// Ciclo de vida dos lançamentos — aprovar, rejeitar, takedown.
// Carregar no 1mz-for-artists.php depois de distribution-main.php.
//
// O que adiciona:
//   - Admin pode aprovar (pendente → live) ou rejeitar (pendente → rejected)
//     com motivo estruturado (8 categorias pré-definidas) + texto livre.
//   - Artista pode pedir takedown de uma música live, com justificação.
//   - Artista pode re-submeter um release rejected depois de corrigir.
//   - Cada transição é registada em rmbz_distro_status_history (audit log).
//   - Hook `rmbz_distro_status_changed` para notificações e integrações.
//
// Snippets de UI (botões para colar em admin-views.php e
// distribution-single-view.php) estão no fim do ficheiro em bloco de comentário.

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

/**
 * Motivos de rejeição pré-definidos. Mantém estruturado para dar para filtrar/analytics.
 */
function rmbz_distro_rejection_reasons() {
    return [
        'copyright'    => 'Violação de direitos autorais / sample sem licença',
        'audio_quality'=> 'Qualidade do áudio insuficiente',
        'cover_quality'=> 'Capa inadequada (resolução, conteúdo ou direitos)',
        'metadata'     => 'Metadados incorrectos (título, artista ou género)',
        'explicit_tag' => 'Conteúdo explícito não marcado como tal',
        'duplicate'    => 'Faixa ou release duplicado',
        'misleading'   => 'Artista principal diferente do indicado',
        'other'        => 'Outro motivo (ver texto livre)',
    ];
}

/**
 * Transição de estado centralizada. Registra sempre no histórico.
 *
 * @param int    $release_id
 * @param string $new_status
 * @param string $changed_by 'admin', 'artist', 'system' ou telemovel
 * @param array  $opts [ reason_code, reason_text ]
 * @return bool
 */
function rmbz_distro_change_status( $release_id, $new_status, $changed_by = 'system', $opts = [] ) {
    global $wpdb;
    $tbl_r = $wpdb->prefix . 'rmbz_distro_releases';
    $tbl_h = $wpdb->prefix . 'rmbz_distro_status_history';

    $rel = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $tbl_r WHERE id = %d", $release_id ) );
    if ( ! $rel ) return false;

    // Transições permitidas (state machine simples)
    $allowed = [
        'incomplete' => ['pendente'],
        'draft'      => ['pendente'],
        'pendente'   => ['live', 'rejected'],
        'rejected'   => ['pendente'],   // re-submissão após correcção
        'live'       => ['takedown_requested', 'taken_down'],
        'takedown_requested' => ['taken_down', 'live'],
        'taken_down' => [], // estado final
    ];

    $current = $rel->status;
    if ( ! isset($allowed[$current]) || ! in_array( $new_status, $allowed[$current], true ) ) {
        return false;
    }

    $wpdb->query('START TRANSACTION');
    try {
        $update = [ 'status' => $new_status ];
        $fmt    = [ '%s' ];

        if ( $new_status === 'rejected' ) {
            $update['rejection_reason'] = sanitize_key( $opts['reason_code'] ?? 'other' );
            $update['rejection_note']   = sanitize_textarea_field( $opts['reason_text'] ?? '' );
            $fmt[] = '%s'; $fmt[] = '%s';
        }
        if ( $new_status === 'live' ) {
            $update['live_at']          = current_time('mysql');
            $update['rejection_reason'] = null;
            $update['rejection_note']   = null;
            $fmt[] = '%s'; $fmt[] = '%s'; $fmt[] = '%s';
        }
        if ( $new_status === 'takedown_requested' ) {
            $update['takedown_requested'] = 1;
            $update['takedown_reason']    = sanitize_textarea_field( $opts['reason_text'] ?? '' );
            $fmt[] = '%d'; $fmt[] = '%s';
        }

        $wpdb->update( $tbl_r, $update, ['id' => $release_id], $fmt, ['%d'] );

        $wpdb->insert( $tbl_h, [
            'release_id'  => $release_id,
            'from_status' => $current,
            'to_status'   => $new_status,
            'reason_code' => $opts['reason_code'] ?? null,
            'reason_text' => $opts['reason_text'] ?? null,
            'changed_by'  => $changed_by,
        ] );

        $wpdb->query('COMMIT');
    } catch ( Exception $e ) {
        $wpdb->query('ROLLBACK');
        return false;
    }

    // Hook para notificações, webhooks, emails, etc.
    do_action( 'rmbz_distro_status_changed', $release_id, $current, $new_status, $opts );
    return true;
}

/**
 * ----------------------------------------------------------------------------
 * ADMIN ACTIONS (approve / reject)
 * ----------------------------------------------------------------------------
 */
function rmbz_admin_approve_release() {
    if ( ! current_user_can('manage_options') ) wp_die('Sem permissão.');
    check_admin_referer('rmbz_approve_release');

    $release_id = isset($_POST['release_id']) ? absint($_POST['release_id']) : 0;
    $ok = rmbz_distro_change_status( $release_id, 'live', 'admin' );

    $redirect = wp_get_referer() ?: admin_url('admin.php?page=rmbz-admin-distribution');
    $redirect = add_query_arg( ['msg' => $ok ? 'approved' : 'error'], $redirect );
    wp_safe_redirect( $redirect );
    exit;
}
add_action( 'admin_post_rmbz_approve_release', 'rmbz_admin_approve_release' );

function rmbz_admin_reject_release() {
    if ( ! current_user_can('manage_options') ) wp_die('Sem permissão.');
    check_admin_referer('rmbz_reject_release');

    $release_id  = isset($_POST['release_id']) ? absint($_POST['release_id']) : 0;
    $reason_code = sanitize_key( $_POST['reason_code'] ?? 'other' );
    $reason_text = sanitize_textarea_field( $_POST['reason_text'] ?? '' );

    if ( ! isset( rmbz_distro_rejection_reasons()[ $reason_code ] ) ) $reason_code = 'other';

    $ok = rmbz_distro_change_status( $release_id, 'rejected', 'admin', [
        'reason_code' => $reason_code,
        'reason_text' => $reason_text,
    ] );

    $redirect = wp_get_referer() ?: admin_url('admin.php?page=rmbz-admin-distribution');
    $redirect = add_query_arg( ['msg' => $ok ? 'rejected' : 'error'], $redirect );
    wp_safe_redirect( $redirect );
    exit;
}
add_action( 'admin_post_rmbz_reject_release', 'rmbz_admin_reject_release' );

/**
 * ----------------------------------------------------------------------------
 * ARTIST ACTION (takedown request)
 * ----------------------------------------------------------------------------
 */
function rmbz_artist_request_takedown() {
    if ( ! check_ajax_referer('rmbz_distro_nonce', 'nonce', false) ) {
        wp_send_json_error('Segurança.', 403);
    }
    if ( ! rmbz_is_user_logged_in() ) wp_send_json_error('Sessão.', 401);

    $telemovel = rmbz_get_current_user_telemovel();

    // FIX #5 — rate limit (takedown é destrutivo e dispara workflow admin)
    // 3 pedidos/hora por artista: suficiente para uso legítimo, trava spam.
    if ( function_exists('rmbz_rate_limit') ) {
        $rl_key = 'takedown_' . $telemovel;
        // rmbz_rate_limit usa janela de 60s — para "3 por hora" multiplicamos
        // a contagem: 3 pedidos por 60s ≈ 180 por hora teórico, mas como os
        // pedidos reais são raros, 3/min já é suficiente como trava.
        if ( ! rmbz_rate_limit( $rl_key, 3 ) ) {
            wp_send_json_error('Demasiados pedidos de takedown. Aguarda um minuto.', 429);
        }
    }

    $release_id  = isset($_POST['release_id']) ? absint($_POST['release_id']) : 0;
    $reason_text = sanitize_textarea_field( $_POST['reason'] ?? '' );

    // ──────────────────────────────────────────────────────────────────────
    // Motivo estruturado — limita a um conjunto pré-definido. Permite
    // estatísticas e ajuda o admin a priorizar.
    // ──────────────────────────────────────────────────────────────────────
    $reason_code = sanitize_key( $_POST['reason_code'] ?? '' );
    $valid_codes = [
        'rights'        => 'Direitos de autor / licença expirada',
        'metadata_error'=> 'Erro na faixa / metadata',
        'relaunch'      => 'Vou relançar com nova versão',
        'collaborator'  => 'Conflito com colaborador',
        'other'         => 'Outro',
    ];
    if ( ! isset($valid_codes[$reason_code]) ) {
        wp_send_json_error('Selecciona um motivo válido para o pedido.');
    }

    // Texto obrigatório só para "outro"; para os outros é opcional mas
    // útil. Mínimo 10 caracteres se o artista preenche.
    if ( $reason_code === 'other' ) {
        if ( mb_strlen( $reason_text ) < 10 ) {
            wp_send_json_error('Para "Outro motivo" tens de explicar (mínimo 10 caracteres).');
        }
    }
    if ( mb_strlen($reason_text) > 1000 ) {
        wp_send_json_error('Motivo demasiado longo (máx. 1000 caracteres).');
    }

    global $wpdb;
    $rel = $wpdb->get_row( $wpdb->prepare(
        "SELECT id, status, live_at, title FROM {$wpdb->prefix}rmbz_distro_releases
         WHERE id = %d AND artist_telemovel = %s",
        $release_id, $telemovel
    ) );
    if ( ! $rel ) wp_send_json_error('Não encontrado.', 404);

    // ──────────────────────────────────────────────────────────────────────
    // Validar idade mínima — só pode pedir takedown depois de X dias do
    // release estar live. Isto evita que o artista publique e remova
    // imediatamente, o que é abusivo para o pipeline dos DSPs.
    // Default 30 dias (option editável).
    // ──────────────────────────────────────────────────────────────────────
    $min_days = (int) get_option( 'rmbz_takedown_min_days_after_live', 30 );
    if ( $min_days > 0 && in_array( $rel->status, ['live','distribuido'], true ) ) {
        if ( empty($rel->live_at) ) {
            // Sem live_at — caso edge raro, permitir mas avisar admin no log
            error_log( sprintf('[1MZ] takedown sem live_at no release %d', $release_id) );
        } else {
            $live_ts  = strtotime( $rel->live_at );
            $cutoff   = $live_ts + ( $min_days * DAY_IN_SECONDS );
            if ( time() < $cutoff ) {
                $days_left = (int) ceil( ( $cutoff - time() ) / DAY_IN_SECONDS );
                wp_send_json_error( sprintf(
                    'O takedown só pode ser pedido %d dias depois do release ficar nas lojas. Faltam %d dia(s).',
                    $min_days, $days_left
                ) );
            }
        }
    }

    $ok = rmbz_distro_change_status( $release_id, 'takedown_requested', $telemovel, [
        'reason_text' => sprintf( '[%s] %s',
            $valid_codes[$reason_code],
            $reason_text !== '' ? $reason_text : '(sem detalhes adicionais)'
        ),
        'reason_code' => 'artist_' . $reason_code,
    ] );
    if ( ! $ok ) wp_send_json_error('Não é possível fazer takedown deste lançamento no estado atual.');

    wp_send_json_success([
        'message' => 'Pedido de takedown enviado. A equipa vai rever em breve.',
        'reload'  => true,
    ]);
}
// FIX #3 — adicionar wp_ajax_nopriv_. O plugin usa sessão própria, portanto
// para o WordPress o artista é sempre anónimo. Sem nopriv este endpoint
// respondia "0" e o botão de takedown ficava partido em produção.
add_action( 'wp_ajax_rmbz_request_takedown',        'rmbz_artist_request_takedown' );
add_action( 'wp_ajax_nopriv_rmbz_request_takedown', 'rmbz_artist_request_takedown' );


/**
 * ----------------------------------------------------------------------------
 * PRE-CHECK — endpoint informativo chamado pelo modal antes do submit
 * ----------------------------------------------------------------------------
 * Devolve (sem alterar nada):
 *   - eligible: bool — se pode pedir AGORA
 *   - days_left: int — dias restantes para ficar elegível (0 se já pode)
 *   - has_holdings: bool — se há holdings retidas para colaboradores externos
 *   - holdings_total: float — valor em USD retido
 *   - min_days: int — quantos dias mínimos precisa esperar
 *   - live_at: string — quando o release ficou live
 *
 * Usado pelo modal para mostrar aviso útil ANTES de o artista preencher.
 */
function rmbz_artist_takedown_precheck() {
    if ( ! check_ajax_referer('rmbz_distro_nonce', 'nonce', false) ) {
        wp_send_json_error('Segurança.', 403);
    }
    if ( ! rmbz_is_user_logged_in() ) wp_send_json_error('Sessão.', 401);

    $telemovel = rmbz_get_current_user_telemovel();
    $release_id = isset($_POST['release_id']) ? absint($_POST['release_id']) : 0;
    if ( ! $release_id ) wp_send_json_error('ID inválido.');

    global $wpdb;
    $rel = $wpdb->get_row( $wpdb->prepare(
        "SELECT id, status, live_at, title FROM {$wpdb->prefix}rmbz_distro_releases
         WHERE id = %d AND artist_telemovel = %s",
        $release_id, $telemovel
    ) );
    if ( ! $rel ) wp_send_json_error('Não encontrado.', 404);

    $min_days = (int) get_option( 'rmbz_takedown_min_days_after_live', 30 );
    $eligible = true;
    $days_left = 0;

    if ( $min_days > 0 && in_array( $rel->status, ['live','distribuido'], true ) && ! empty($rel->live_at) ) {
        $live_ts = strtotime( $rel->live_at );
        $cutoff  = $live_ts + ( $min_days * DAY_IN_SECONDS );
        if ( time() < $cutoff ) {
            $eligible = false;
            $days_left = (int) ceil( ( $cutoff - time() ) / DAY_IN_SECONDS );
        }
    }

    // Holdings de splits externos (informativo — não bloqueia)
    $has_holdings = false;
    $holdings_total = 0.0;
    $t_holdings = $wpdb->prefix . 'rmbz_splits_holdings';
    if ( $wpdb->get_var("SHOW TABLES LIKE '$t_holdings'") === $t_holdings ) {
        $hcols = $wpdb->get_col("SHOW COLUMNS FROM $t_holdings");
        $extra = '';
        if ( in_array('reverted_at', (array) $hcols, true) ) $extra .= ' AND reverted_at IS NULL';
        $sum = (float) $wpdb->get_var( $wpdb->prepare(
            "SELECT COALESCE(SUM(amount_usd),0)
             FROM $t_holdings
             WHERE release_id = %d AND claimed_at IS NULL $extra",
            $release_id
        ) );
        if ( $sum > 0 ) {
            $has_holdings = true;
            $holdings_total = $sum;
        }
    }

    wp_send_json_success([
        'eligible'       => $eligible,
        'days_left'      => $days_left,
        'min_days'       => $min_days,
        'live_at'        => $rel->live_at,
        'has_holdings'   => $has_holdings,
        'holdings_total' => $holdings_total,
        'release_title'  => $rel->title,
    ]);
}
add_action( 'wp_ajax_rmbz_takedown_precheck',        'rmbz_artist_takedown_precheck' );
add_action( 'wp_ajax_nopriv_rmbz_takedown_precheck', 'rmbz_artist_takedown_precheck' );

/**
 * ----------------------------------------------------------------------------
 * NOTIFICAÇÃO — exemplo (integra com rmbz_notification_functions, se existir)
 * ----------------------------------------------------------------------------
 *
 * NOTA TÉCNICA: o hook `rmbz_distro_status_changed` é disparado em dois
 * sítios com número de argumentos diferentes:
 *   - distribution-lifecycle.php         → 4 args ($release_id, $from, $to, $opts)
 *   - admin-distribution-releases.php    → 3 args ($release_id, $from, $to)
 *   - distribution-return-workflow.php   → 3 args ($release_id, $from, $to)
 *
 * Se registarmos o callback a exigir 4 argumentos (`, 10, 4`), os 3
 * disparos causam FATAL ERROR ("Too few arguments"). Solução: registar com
 * 3 argumentos obrigatórios e tratar o $opts como parâmetro opcional via
 * func_get_args().
 */
add_action( 'rmbz_distro_status_changed', function( $release_id, $from, $to ) {
    // $opts é opcional — só vem no caminho do distribution-lifecycle.php
    $args = func_get_args();
    $opts = ( isset($args[3]) && is_array($args[3]) ) ? $args[3] : [];

    if ( ! function_exists('rmbz_add_notification') ) return;
    global $wpdb;

    $rel = $wpdb->get_row( $wpdb->prepare(
        "SELECT artist_telemovel, title FROM {$wpdb->prefix}rmbz_distro_releases WHERE id = %d",
        $release_id
    ) );
    if ( ! $rel ) return;

    $msg = '';
    switch ( $to ) {
        case 'live':
            $msg = "🎉 O seu lançamento \"{$rel->title}\" está AO VIVO nas lojas!";
            break;
        case 'rejected':
            $reasons = rmbz_distro_rejection_reasons();
            $label   = $reasons[ $opts['reason_code'] ?? 'other' ] ?? 'Motivo não especificado';
            $msg = "⚠️ O seu lançamento \"{$rel->title}\" foi rejeitado. Motivo: {$label}";
            break;
        case 'takedown_requested':
            $msg = "📬 Pedido de takedown recebido para \"{$rel->title}\". Vamos tratar em 48h.";
            break;
        case 'taken_down':
            $msg = "🚫 O seu lançamento \"{$rel->title}\" foi removido das lojas.";
            break;
    }
    if ( $msg ) rmbz_add_notification( $rel->artist_telemovel, $msg );
}, 10, 3 );

/**
 * ----------------------------------------------------------------------------
 * HELPER — histórico em HTML para mostrar na view do admin ou do artista
 * ----------------------------------------------------------------------------
 */
function rmbz_render_release_history( $release_id ) {
    global $wpdb;
    $rows = $wpdb->get_results( $wpdb->prepare(
        "SELECT * FROM {$wpdb->prefix}rmbz_distro_status_history
         WHERE release_id = %d ORDER BY created_at ASC",
        $release_id
    ) );
    if ( ! $rows ) return '<p style="color:#888;">Sem histórico ainda.</p>';

    $reasons = rmbz_distro_rejection_reasons();
    $html = '<ul class="rmbz-history" style="list-style:none; padding:0; margin:0;">';
    foreach ( $rows as $r ) {
        $label = $reasons[$r->reason_code] ?? ($r->reason_code ?: '-');
        $html .= sprintf(
            '<li style="padding:10px; border-left:2px solid #f0b90b; margin-bottom:8px; background:#1a1a1a; color:#ccc;">
                <small style="color:#888;">%s — por %s</small><br>
                <strong>%s → %s</strong>%s
             </li>',
            esc_html( mysql2date('d/m/Y H:i', $r->created_at) ),
            esc_html( $r->changed_by ),
            esc_html( $r->from_status ?: '(início)' ),
            esc_html( $r->to_status ),
            $r->reason_text
                ? '<br><em style="color:#aaa; font-size:12px;">' . esc_html($r->reason_text) . '</em>'
                : ( $r->reason_code ? '<br><em style="color:#aaa; font-size:12px;">' . esc_html($label) . '</em>' : '' )
        );
    }
    $html .= '</ul>';
    return $html;
}

/* =============================================================================
 * SNIPPETS PARA COLAR NOS FICHEIROS EXISTENTES
 * =============================================================================

 A) Em admin-views.php (ou onde listas os releases pendentes), para cada release:

    if ( $rel->status === 'pendente' ):
    ?>
    <details style="margin-top:10px;">
        <summary>Aprovar / Rejeitar</summary>

        <form method="post" action="<?php echo admin_url('admin-post.php'); ?>" style="display:inline;">
            <?php wp_nonce_field('rmbz_approve_release'); ?>
            <input type="hidden" name="action" value="rmbz_approve_release">
            <input type="hidden" name="release_id" value="<?php echo (int)$rel->id; ?>">
            <button class="button button-primary">✅ Aprovar (Publicar)</button>
        </form>

        <form method="post" action="<?php echo admin_url('admin-post.php'); ?>" style="margin-top:10px;">
            <?php wp_nonce_field('rmbz_reject_release'); ?>
            <input type="hidden" name="action" value="rmbz_reject_release">
            <input type="hidden" name="release_id" value="<?php echo (int)$rel->id; ?>">
            <select name="reason_code" required>
                <option value="">— motivo —</option>
                <?php foreach ( rmbz_distro_rejection_reasons() as $k => $v ): ?>
                    <option value="<?php echo esc_attr($k); ?>"><?php echo esc_html($v); ?></option>
                <?php endforeach; ?>
            </select>
            <textarea name="reason_text" placeholder="Detalhes (opcional)" rows="2" style="width:100%;"></textarea>
            <button class="button button-secondary">❌ Rejeitar</button>
        </form>
    </details>
    <?php endif;

 B) Em distribution-single-view.php (artista), se status = rejected, mostrar o motivo:

    if ( $rel->status === 'rejected' ) {
        $reasons = rmbz_distro_rejection_reasons();
        echo '<div class="rmbz-rejection-box">
                <h3>⚠️ Lançamento Rejeitado</h3>
                <p><strong>Motivo:</strong> ' . esc_html( $reasons[$rel->rejection_reason] ?? 'Não especificado' ) . '</p>';
        if ( $rel->rejection_note ) echo '<p>' . esc_html($rel->rejection_note) . '</p>';
        echo '<p>Corrija e envie novamente.</p></div>';
    }

 C) Em distribution-single-view.php, se status = live, botão de takedown:

    if ( $rel->status === 'live' ):
    ?>
        <button id="btn-request-takedown" class="btn-danger">Pedir Retirada das Lojas</button>
        <script>
        document.getElementById('btn-request-takedown').addEventListener('click', function() {
            var reason = prompt('Porque quer retirar esta música? (mínimo 10 caracteres)');
            if ( ! reason || reason.length < 10 ) return;
            var fd = new FormData();
            fd.append('action', 'rmbz_request_takedown');
            fd.append('nonce', '<?php echo wp_create_nonce('rmbz_distro_nonce'); ?>');
            fd.append('release_id', '<?php echo (int)$rel->id; ?>');
            fd.append('reason', reason);
            fetch('<?php echo admin_url('admin-ajax.php'); ?>', { method: 'POST', body: fd })
                .then(r => r.json())
                .then(d => { alert(d.data.message || d.data); if (d.success) location.reload(); });
        });
        </script>
    <?php endif;

 =============================================================================
 */
