<?php
// includes/distribution/distribution-schema-patch.php
//
// VERSÃO CORRIGIDA — 2026-04-22
// Alterações face ao original:
//   [BUG #5] Já não regista em 'init' (frontend). Só em 'admin_init' + no
//            hook de activação. Adicionado lock via transient para evitar
//            race condition entre admins concorrentes.
//   [BUG #8] Todas as tabelas agora declaram ENGINE=InnoDB explicitamente
//            — necessário para as transacções em distribution-security.php
//            funcionarem. Adicionado ALTER ENGINE para tabelas existentes.

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

/**
 * Cria/actualiza tabelas. Idempotente (dbDelta faz check).
 */
function rmbz_distro_install_missing_tables() {
    global $wpdb;
    $charset_collate = $wpdb->get_charset_collate();
    $sql = [];

    // ------------------------------------------------------------------------
    // 1. Ledger de royalties
    // ------------------------------------------------------------------------
    $sql[] = "CREATE TABLE {$wpdb->prefix}rmbz_distro_ledger (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        artist_telemovel varchar(20) NOT NULL,
        amount decimal(12,4) NOT NULL,
        currency varchar(3) DEFAULT 'USD' NOT NULL,
        original_amount decimal(12,4) DEFAULT NULL,
        original_currency varchar(3) DEFAULT NULL,
        exchange_rate decimal(10,6) DEFAULT NULL,
        source_name varchar(100) DEFAULT NULL,
        reporting_period varchar(20) DEFAULT NULL,
        type varchar(50) NOT NULL,
        description text,
        balance_after decimal(12,4) DEFAULT NULL,
        reference_id varchar(80) DEFAULT NULL,
        created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
        PRIMARY KEY  (id),
        KEY idx_artist (artist_telemovel),
        KEY idx_period (reporting_period),
        KEY idx_type (type)
    ) ENGINE=InnoDB $charset_collate;";

    // ------------------------------------------------------------------------
    // 2. Daily stats
    // ------------------------------------------------------------------------
    $sql[] = "CREATE TABLE {$wpdb->prefix}rmbz_distro_daily_stats (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        artist_telemovel varchar(20) NOT NULL,
        release_id bigint(20) DEFAULT NULL,
        track_id bigint(20) DEFAULT NULL,
        report_date date NOT NULL,
        streams bigint(20) DEFAULT 0 NOT NULL,
        sales decimal(12,4) DEFAULT 0 NOT NULL,
        country_code varchar(5) DEFAULT 'UNK',
        store varchar(60) DEFAULT 'Unknown',
        import_hash varchar(64) DEFAULT NULL,
        pending_approval tinyint(1) DEFAULT 0 NOT NULL,
        PRIMARY KEY  (id),
        UNIQUE KEY uniq_daily (artist_telemovel, release_id, track_id, report_date, country_code, store, import_hash),
        KEY idx_artist_date (artist_telemovel, report_date),
        KEY idx_release (release_id),
        KEY idx_pending (pending_approval),
        KEY idx_import_hash (import_hash),
        KEY idx_track (track_id)
    ) ENGINE=InnoDB $charset_collate;";

    // ------------------------------------------------------------------------
    // 3. Splits de royalties
    // ------------------------------------------------------------------------
    $sql[] = "CREATE TABLE {$wpdb->prefix}rmbz_track_splits (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        track_id bigint(20) NOT NULL,
        payee_telemovel varchar(20) DEFAULT NULL,
        payee_name varchar(120) NOT NULL,
        role varchar(40) DEFAULT 'feat' NOT NULL,
        percentage decimal(5,2) NOT NULL,
        status varchar(20) DEFAULT 'pending' NOT NULL,
        created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
        PRIMARY KEY  (id),
        KEY idx_track (track_id),
        KEY idx_payee (payee_telemovel)
    ) ENGINE=InnoDB $charset_collate;";

    // ------------------------------------------------------------------------
    // 4. Histórico de estados
    // ------------------------------------------------------------------------
    $sql[] = "CREATE TABLE {$wpdb->prefix}rmbz_distro_status_history (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        release_id bigint(20) NOT NULL,
        from_status varchar(20) DEFAULT NULL,
        to_status varchar(20) NOT NULL,
        reason_code varchar(50) DEFAULT NULL,
        reason_text text,
        changed_by varchar(60) DEFAULT 'system',
        created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
        PRIMARY KEY  (id),
        KEY idx_release (release_id)
    ) ENGINE=InnoDB $charset_collate;";

    // ------------------------------------------------------------------------
    // 5. Stats (agregadas por artista) — com UNIQUE
    // ------------------------------------------------------------------------
    $sql[] = "CREATE TABLE {$wpdb->prefix}rmbz_distro_stats (
        id bigint(20) NOT NULL AUTO_INCREMENT,
        artist_telemovel varchar(20) NOT NULL,
        total_streams bigint(20) DEFAULT 0 NOT NULL,
        total_earnings decimal(12,4) DEFAULT 0.0000 NOT NULL,
        withdrawn_amount decimal(12,4) DEFAULT 0.0000 NOT NULL,
        current_balance decimal(12,4) DEFAULT 0.0000 NOT NULL,
        last_updated datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
        updated_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
        PRIMARY KEY  (id),
        UNIQUE KEY uniq_artist (artist_telemovel)
    ) ENGINE=InnoDB $charset_collate;";

    require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
    dbDelta( $sql );

    // ------------------------------------------------------------------------
    // De-dup + UNIQUE KEY em rmbz_distro_stats (para tabelas pré-existentes)
    // ------------------------------------------------------------------------
    $table_stats = $wpdb->prefix . 'rmbz_distro_stats';

    if ( defined('DB_NAME') ) {
        $has_unique = $wpdb->get_var( $wpdb->prepare(
            "SELECT COUNT(*) FROM information_schema.STATISTICS
             WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s AND INDEX_NAME = 'uniq_artist'",
            DB_NAME, $table_stats
        ) );
    } else {
        // Fallback: tenta criar e ignora erro se já existir
        $has_unique = 0;
    }

    if ( ! $has_unique ) {
        $duplicates = $wpdb->get_results(
            "SELECT artist_telemovel, MIN(id) as keep_id, COUNT(*) as cnt
             FROM $table_stats
             GROUP BY artist_telemovel
             HAVING cnt > 1"
        );

        foreach ( (array) $duplicates as $dup ) {
            $totals = $wpdb->get_row( $wpdb->prepare(
                "SELECT SUM(total_streams) s, SUM(total_earnings) e,
                        SUM(withdrawn_amount) w, SUM(current_balance) b
                 FROM $table_stats WHERE artist_telemovel = %s",
                $dup->artist_telemovel
            ) );

            $wpdb->update(
                $table_stats,
                [
                    'total_streams'    => (int)    $totals->s,
                    'total_earnings'   => (float)  $totals->e,
                    'withdrawn_amount' => (float)  $totals->w,
                    'current_balance'  => (float)  $totals->b,
                    'last_updated'     => current_time('mysql'),
                ],
                [ 'id' => $dup->keep_id ]
            );

            $wpdb->query( $wpdb->prepare(
                "DELETE FROM $table_stats WHERE artist_telemovel = %s AND id != %d",
                $dup->artist_telemovel, $dup->keep_id
            ) );
        }

        // Pode falhar silenciosamente se já existir; isso é OK
        $wpdb->query( "ALTER TABLE $table_stats ADD UNIQUE KEY uniq_artist (artist_telemovel)" );
    }

    // ------------------------------------------------------------------------
    // Colunas em rmbz_distro_unclaimed
    // ------------------------------------------------------------------------
    $table_unclaimed = $wpdb->prefix . 'rmbz_distro_unclaimed';
    if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_unclaimed'" ) === $table_unclaimed ) {
        $columns = $wpdb->get_col( "SHOW COLUMNS FROM $table_unclaimed" );

        if ( ! in_array('platform', $columns, true) ) {
            $wpdb->query( "ALTER TABLE $table_unclaimed ADD COLUMN platform varchar(60) DEFAULT NULL AFTER amount" );
        }
        if ( ! in_array('country', $columns, true) ) {
            $wpdb->query( "ALTER TABLE $table_unclaimed ADD COLUMN country varchar(5) DEFAULT NULL AFTER platform" );
        }
        if ( ! in_array('resolved_by', $columns, true) ) {
            $wpdb->query( "ALTER TABLE $table_unclaimed ADD COLUMN resolved_by varchar(20) DEFAULT NULL" );
            $wpdb->query( "ALTER TABLE $table_unclaimed ADD COLUMN resolved_at datetime DEFAULT NULL" );
        }
    }

    // ------------------------------------------------------------------------
    // Colunas em rmbz_distro_releases
    // ------------------------------------------------------------------------
    $table_releases = $wpdb->prefix . 'rmbz_distro_releases';
    if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_releases'" ) === $table_releases ) {
        $cols_rel = $wpdb->get_col( "SHOW COLUMNS FROM $table_releases" );

        if ( ! in_array('rejection_reason', $cols_rel, true) ) {
            $wpdb->query( "ALTER TABLE $table_releases
                           ADD COLUMN rejection_reason varchar(50) DEFAULT NULL,
                           ADD COLUMN rejection_note text,
                           ADD COLUMN takedown_requested tinyint(1) DEFAULT 0 NOT NULL,
                           ADD COLUMN takedown_reason text,
                           ADD COLUMN live_at datetime DEFAULT NULL,
                           ADD COLUMN submitted_at datetime DEFAULT NULL,
                           ADD KEY idx_status (status),
                           ADD KEY idx_artist_status (artist_telemovel, status)" );
        }

        // [BUG-SCHEMA1] Adicionar extra_data se não existir.
        // Esta coluna é usada por todo o módulo de distribuição
        // (release_type_manual, classificações, returns, etc.) mas nunca
        // existiu no CREATE TABLE original — todas as escritas falhavam
        // silenciosamente em instalações antigas.
        if ( ! in_array('extra_data', $cols_rel, true) ) {
            $wpdb->query( "ALTER TABLE $table_releases ADD COLUMN extra_data longtext DEFAULT NULL" );
        }

        // [1.5.138] Suporte a CATÁLOGO LEGADO (lançamentos antigos).
        //   - is_legacy: marca releases que já foram publicados anteriormente
        //     (não são novos lançamentos). Quando 1, a validação aceita
        //     release_date no passado e mostra UI específica.
        //   - original_release_date: data ORIGINAL de publicação nas plataformas
        //     (pode ser muito antiga). Usada como metadata para DSPs
        //     (Spotify "Original Release Date", Apple Music "Original Release Year").
        //
        // Re-ler colunas após eventual ALTER acima.
        $cols_rel = $wpdb->get_col( "SHOW COLUMNS FROM $table_releases" );

        if ( ! in_array('is_legacy', $cols_rel, true) ) {
            $wpdb->query( "ALTER TABLE $table_releases ADD COLUMN is_legacy tinyint(1) DEFAULT 0 NOT NULL" );
        }
        if ( ! in_array('original_release_date', $cols_rel, true) ) {
            $wpdb->query( "ALTER TABLE $table_releases ADD COLUMN original_release_date date DEFAULT NULL" );
        }
    }

    // ------------------------------------------------------------------------
    // [BUG #8] Garantir que as tabelas críticas são InnoDB.
    //     As transacções em distribution-security.php dependem disto.
    //     Se forem MyISAM, ALTER TABLE converte; caso contrário é no-op.
    // ------------------------------------------------------------------------
    $critical_tables_to_innodb = [
        $wpdb->prefix . 'rmbz_distro_releases',
        $wpdb->prefix . 'rmbz_distro_tracks',
        $wpdb->prefix . 'rmbz_distro_status_history',
        $wpdb->prefix . 'rmbz_distro_ledger',
        $wpdb->prefix . 'rmbz_distro_stats',
    ];

    foreach ( $critical_tables_to_innodb as $tbl ) {
        if ( $wpdb->get_var( "SHOW TABLES LIKE '$tbl'" ) !== $tbl ) continue;

        $engine = $wpdb->get_var( $wpdb->prepare(
            "SELECT ENGINE FROM information_schema.TABLES
             WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s",
            DB_NAME, $tbl
        ) );

        if ( $engine && strtoupper($engine) !== 'INNODB' ) {
            // Silencioso — se falhar (ex: sem permissão), o admin continua a ver o site
            $wpdb->query( "ALTER TABLE $tbl ENGINE=InnoDB" );
        }
    }

    // ------------------------------------------------------------------------
    // [1.5.164] BUG FIX CRÍTICO — UNIQUE KEY de daily_stats.
    //
    // PROBLEMA:
    // A unique key original era (artist, release, track, date, country, store).
    // Quando vinha um novo import com a mesma combinação (mesmo artista, mesma
    // música, mesmo mês, mesmo país, mesmo store) o ON DUPLICATE KEY UPDATE
    // somava `streams + X` e `sales + Y` na linha existente. Se essa linha já
    // estava aprovada (pending_approval=0), o valor pendente do novo import
    // entrava sem aprovação — ficava visível ao artista imediatamente.
    //
    // CORREÇÃO:
    // Adicionar `import_hash` à unique key. Cada import passa a ter as suas
    // próprias linhas separadas. Aprovação afecta só linhas desse hash. Não
    // há mais sobreposição entre imports.
    //
    // Esta migração:
    //   1. Adiciona coluna `import_hash` se em falta (DEFAULT NULL).
    //   2. Adiciona coluna `pending_approval` se em falta.
    //   3. Faz DROP da unique key antiga.
    //   4. Cria a nova unique key incluindo import_hash.
    //
    // É idempotente — se já estiver aplicado, todos os passos são no-op.
    // ------------------------------------------------------------------------
    $t_daily = $wpdb->prefix . 'rmbz_distro_daily_stats';
    if ( $wpdb->get_var( "SHOW TABLES LIKE '$t_daily'" ) === $t_daily ) {
        $cols_d = $wpdb->get_col( "SHOW COLUMNS FROM $t_daily" );

        // 1. Adicionar import_hash se em falta
        if ( ! in_array('import_hash', $cols_d, true) ) {
            $wpdb->query( "ALTER TABLE $t_daily ADD COLUMN import_hash varchar(64) DEFAULT NULL" );
            $wpdb->query( "ALTER TABLE $t_daily ADD INDEX idx_import_hash (import_hash)" );
        }

        // 2. Adicionar pending_approval se em falta (defensivo)
        if ( ! in_array('pending_approval', $cols_d, true) ) {
            $wpdb->query( "ALTER TABLE $t_daily ADD COLUMN pending_approval tinyint(1) DEFAULT 0 NOT NULL" );
            $wpdb->query( "ALTER TABLE $t_daily ADD INDEX idx_pending (pending_approval)" );
        }

        // 3. Verificar se a unique key antiga existe e tem só 6 colunas
        // (a nova tem 7 — incluindo import_hash). Se for a antiga, dropar e
        // criar a nova.
        $idx_info = $wpdb->get_results( "SHOW INDEX FROM $t_daily WHERE Key_name = 'uniq_daily'" );
        if ( ! empty( $idx_info ) ) {
            $col_count = count( $idx_info );
            // A unique antiga tem 6 colunas, a nova tem 7
            if ( $col_count < 7 ) {
                // Dropar a unique antiga e criar a nova
                $wpdb->query( "ALTER TABLE $t_daily DROP INDEX uniq_daily" );
                // Criar a nova com import_hash incluído
                // NOTA: import_hash pode ser NULL, e MySQL/InnoDB trata NULL como
                // valores distintos em UNIQUE KEYs (cada linha com NULL é única).
                // Isto é OK aqui porque queremos exactamente esse comportamento:
                // linhas legacy sem import_hash (NULL) mantêm-se separadas das
                // linhas novas que têm hash.
                $wpdb->query(
                    "ALTER TABLE $t_daily
                     ADD UNIQUE KEY uniq_daily
                     (artist_telemovel, release_id, track_id, report_date, country_code, store, import_hash)"
                );
            }
        } else {
            // Não há unique key — criar a nova
            $wpdb->query(
                "ALTER TABLE $t_daily
                 ADD UNIQUE KEY uniq_daily
                 (artist_telemovel, release_id, track_id, report_date, country_code, store, import_hash)"
            );
        }
    }

    // ------------------------------------------------------------------------
    // [1.5.190] FAIXAS SILENCIOSAS — coluna last_seen_in_csv em tracks
    //
    // PROBLEMA RESOLVIDO:
    // Faixas que estão "live" mas deixam de receber relatórios passam
    // despercebidas. Admin não tem forma fácil de saber que a faixa X já
    // não recebe royalties há 3+ meses (problema do distribuidor) ou que
    // faixa nova foi distribuída há 4 meses mas nunca apareceu nos CSVs.
    //
    // CORREÇÃO:
    // Coluna last_seen_in_csv DATE — actualizada a cada import quando a
    // faixa aparece no CSV. NULL = nunca recebeu relatório.
    //
    // Backfill inicial: SET last_seen_in_csv = (
    //   SELECT MAX(report_date) FROM rmbz_distro_daily_stats
    //   WHERE track_id = tracks.id
    // ) — capta o histórico já existente.
    //
    // Migration idempotente.
    // ------------------------------------------------------------------------
    $t_tracks = $wpdb->prefix . 'rmbz_distro_tracks';
    if ( $wpdb->get_var( "SHOW TABLES LIKE '$t_tracks'" ) === $t_tracks ) {
        $cols_t = $wpdb->get_col( "SHOW COLUMNS FROM $t_tracks" );

        if ( ! in_array('last_seen_in_csv', $cols_t, true) ) {
            $wpdb->query( "ALTER TABLE $t_tracks ADD COLUMN last_seen_in_csv DATE DEFAULT NULL" );
            $wpdb->query( "ALTER TABLE $t_tracks ADD INDEX idx_last_seen_csv (last_seen_in_csv)" );
        }

        // [ESCALA 50k] Índices para a pesquisa do catálogo e Serviços
        // Exclusivos: JOIN por release_id e LIKE por isrc_code. Sem estes,
        // a pesquisa por ISRC fazia full scan da tabela de faixas.
        $idx_names_t = $wpdb->get_col( "SHOW INDEX FROM $t_tracks", 2 ); // coluna Key_name
        if ( ! in_array('idx_release', (array) $idx_names_t, true) ) {
            $wpdb->query( "ALTER TABLE $t_tracks ADD INDEX idx_release (release_id)" );
        }
        if ( ! in_array('idx_isrc', (array) $idx_names_t, true) ) {
            $wpdb->query( "ALTER TABLE $t_tracks ADD INDEX idx_isrc (isrc_code)" );
        }

        // [1.5.210] BACKFILL FORÇADO de last_seen_in_csv
        //
        // PROBLEMA: Mesmo após 1.5.190 ter adicionado a coluna + 1.5.203 fazer
        // batch UPDATE em cada import, encontrámos releases com dados em
        // daily_stats mas last_seen_in_csv = NULL. Possíveis causas:
        //   1. Migração 1.5.190 falhou silenciosamente em algumas tracks
        //   2. Imports entre 1.5.190 e 1.5.203 podem não ter actualizado todas
        //      as tracks (p.ex. quando track_id era NULL no INSERT)
        //   3. Schema 1.10 só corria backfill se a coluna não existia ainda
        //
        // FIX: Corre o backfill em TODA actualização do schema (1.11+), mas
        // SÓ para tracks com last_seen_in_csv NULL (não regride dados existentes).
        // GREATEST() defensivo se houver algum valor antigo a preservar.
        $t_daily = $wpdb->prefix . 'rmbz_distro_daily_stats';
        if ( $wpdb->get_var( "SHOW TABLES LIKE '$t_daily'" ) === $t_daily ) {
            $d_cols = $wpdb->get_col( "SHOW COLUMNS FROM $t_daily" );
            // Só tracks aprovadas (não pendentes) — pending_approval=0 ou NULL para legacy
            $pa_clause = in_array('pending_approval', $d_cols, true)
                ? ' AND ( pending_approval IS NULL OR pending_approval = 0 )'
                : '';
            $wpdb->query(
                "UPDATE $t_tracks t
                 INNER JOIN (
                     SELECT track_id, MAX(report_date) AS max_date
                     FROM $t_daily
                     WHERE track_id IS NOT NULL AND track_id > 0
                     $pa_clause
                     GROUP BY track_id
                 ) d ON d.track_id = t.id
                 SET t.last_seen_in_csv = GREATEST(
                     IFNULL(t.last_seen_in_csv, '1900-01-01'),
                     d.max_date
                 )
                 WHERE d.max_date > IFNULL(t.last_seen_in_csv, '1900-01-01')"
            );
        }
    }
}

/**
 * [BUG #5 CORRIGIDO]
 * Corre o schema patch com lock de concorrência.
 * Só em admin_init (o comportamento original de também correr em 'init'
 * punha dbDelta no caminho crítico de visitantes anónimos do frontend).
 */
function rmbz_distro_maybe_install_schema() {
    // [BUG-SCHEMA2] Antes usava 'rmbz_db_version' — a mesma chave que
    // functions-setup.php gere com escala diferente (2.1.x). Como
    // version_compare("2.1.0", "1.7", '>=') é true, este patch nunca corria
    // depois do functions-setup ter posto 2.1.0. Resultado: várias colunas
    // (rejection_reason, takedown_*, live_at, submitted_at, extra_data) não
    // eram criadas. Agora usamos a nossa própria chave isolada.
    //
    // [1.5.145] Bumped 1.7 → 1.8 para forçar re-execução em servidores onde
    // o schema antigo já tinha sido aplicado. Adiciona is_legacy +
    // original_release_date a rmbz_distro_releases. Os ALTER TABLE são
    // idempotentes (verificam SHOW COLUMNS antes), por isso é seguro correr
    // novamente em servidores que já tenham as colunas anteriores.
    //
    // [1.5.164] Bumped 1.8 → 1.9 para forçar migração da UNIQUE KEY de
    // daily_stats. A nova unique key inclui import_hash, separando linhas
    // por import e impedindo que valores pendentes contaminem linhas já
    // aprovadas (bug grave: dinheiro pendente aparecia ao artista sem o
    // admin aprovar).
    //
    // [1.5.190] Bumped 1.9 → 1.10 para adicionar coluna last_seen_in_csv
    // a rmbz_distro_tracks (detector de Faixas Silenciosas). Inclui backfill
    // automático com base em rmbz_distro_daily_stats existente.
    //
    // [1.5.210] Bumped 1.10 → 1.11 para forçar RE-EXECUÇÃO do backfill em
    // servidores onde a coluna last_seen_in_csv já existia mas tinha NULLs
    // espalhados (tracks com daily_stats mas last_seen_in_csv não populada).
    // O backfill agora corre sempre que schema é actualizado, e preserva
    // valores existentes via GREATEST().
    $stored = get_option('rmbz_distro_schema_version', '0');
    if ( version_compare( $stored, '1.11', '>=' ) ) return;

    // Lock para evitar 2 admins a correr em paralelo
    if ( get_transient('rmbz_schema_installing') ) return;
    set_transient('rmbz_schema_installing', 1, 300);

    try {
        rmbz_distro_install_missing_tables();
        update_option( 'rmbz_distro_schema_version', '1.11' );
    } catch ( Exception $e ) {
        error_log( '[1MZ] Schema patch falhou: ' . $e->getMessage() );
    }

    delete_transient('rmbz_schema_installing');
}

// Só no admin — nunca no frontend
add_action( 'admin_init', 'rmbz_distro_maybe_install_schema', 5 );

// Também no hook de activação do plugin para instalação limpa
register_activation_hook(
    dirname( dirname( __DIR__ ) ) . '/1mz-for-artists.php',
    'rmbz_distro_install_missing_tables'
);
