<?php
/**
 * includes/admin/import-track-matcher.php
 *
 * Detector multi-camada para identificar a faixa correcta no catálogo da
 * 1MZ a partir de uma linha de royalty.
 *
 * O QUE RESOLVE:
 *
 *   Antes deste módulo, o importador fazia lookup APENAS por ISRC. Se o
 *   distribuidor enviasse um CSV com:
 *     - ISRC errado/em falta (ex: TuneCore que muitas vezes preenche
 *       "Optional ISRC" com o ID interno deles, não o ISRC real)
 *     - ISRC ausente em distribuidores antigos
 *     - Faixa do mesmo álbum mas re-codificada com novo ISRC
 *   ... a linha caía em "Black Box" (não reclamado), e o artista não
 *   recebia o royalty mesmo tendo a faixa registada com UPC válido.
 *
 * COMO FUNCIONA:
 *
 *   Lookup em 3 camadas, em ordem decrescente de confiança:
 *
 *   1. ISRC exacto         → confiança 100%, AUTO-APROVADO
 *      Match único e global. ISRC é standard ISO 3901.
 *
 *   2. UPC + título exacto → confiança alta, REQUER REVISÃO ADMIN
 *      O UPC identifica univocamente o release; o título exacto
 *      identifica a faixa dentro desse release.
 *
 *   3. Artista + título (fuzzy Levenshtein ≤ 2) → confiança média,
 *      REQUER REVISÃO ADMIN. Tolera pequenas variações de grafia
 *      (espaços, acentos, parênteses tipo "(Remix)").
 *
 * FILA DE REVISÃO:
 *
 *   Matches por UPC ou fuzzy NÃO são aplicados imediatamente — vão para
 *   a tabela `rmbz_distro_match_review` para o admin confirmar. Isto evita
 *   atribuir royalties à pessoa errada por causa de homónimos ou typos.
 *
 *   O admin vê contexto completo (linha do CSV, candidato sugerido,
 *   método de match, confiança) e decide aprovar/rejeitar/manual.
 */

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


/* ============================================================================
 * SCHEMA — tabela de matches a rever
 * ============================================================================ */

function rmbz_match_review_maybe_install() {
    $stored = get_option('rmbz_match_review_schema_v', '0');

    if ( version_compare( $stored, '1.0', '<' ) ) {
        global $wpdb;
        $charset_collate = $wpdb->get_charset_collate();
        // NOTA: dbDelta é MUITO exigente quanto ao formato do CREATE TABLE.
        // Comentários inline (`-- texto`) fazem o parser interpretar o
        // comentário como se fosse nome de coluna, gerando SQL inválido
        // tipo `ALTER TABLE ... ADD COLUMN -- Match SUGERIDO`. Também não
        // gosta de linhas em branco no meio das colunas. Manter tudo
        // denso, sem blank lines nem comentários inline.
        $sql = "CREATE TABLE {$wpdb->prefix}rmbz_distro_match_review (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            import_hash varchar(64) DEFAULT NULL,
            report_date date DEFAULT NULL,
            csv_isrc varchar(20) DEFAULT NULL,
            csv_upc varchar(40) DEFAULT NULL,
            csv_track_title varchar(255) DEFAULT NULL,
            csv_artist varchar(255) DEFAULT NULL,
            csv_album varchar(255) DEFAULT NULL,
            csv_label varchar(255) DEFAULT NULL,
            csv_store varchar(60) DEFAULT NULL,
            csv_country varchar(8) DEFAULT NULL,
            csv_streams int(11) DEFAULT 0,
            csv_amount_usd decimal(15,6) DEFAULT 0.000000,
            suggested_track_id bigint(20) DEFAULT NULL,
            suggested_release_id bigint(20) DEFAULT NULL,
            suggested_artist_telemovel varchar(20) DEFAULT NULL,
            match_method varchar(40) NOT NULL,
            match_confidence tinyint(3) DEFAULT 0,
            match_distance tinyint(3) DEFAULT NULL,
            status varchar(20) NOT NULL DEFAULT 'pending',
            reviewed_by varchar(60) DEFAULT NULL,
            reviewed_at datetime DEFAULT NULL,
            review_notes text,
            created_at datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
            PRIMARY KEY  (id),
            KEY idx_status (status),
            KEY idx_import (import_hash),
            KEY idx_suggested_track (suggested_track_id)
        ) $charset_collate;";
        require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
        dbDelta( $sql );
        update_option('rmbz_match_review_schema_v', '1.0');
    }

    // v1.1 — adicionar flag artist_conflict (caso UPC duplicado entre artistas)
    if ( version_compare( $stored, '1.1', '<' ) ) {
        global $wpdb;
        $t = $wpdb->prefix . 'rmbz_distro_match_review';
        if ( $wpdb->get_var("SHOW TABLES LIKE '$t'") === $t ) {
            $cols = $wpdb->get_col("SHOW COLUMNS FROM $t");
            if ( ! in_array('artist_conflict', (array) $cols, true) ) {
                $wpdb->query("ALTER TABLE $t ADD COLUMN artist_conflict tinyint(1) DEFAULT 0 NOT NULL AFTER match_distance");
            }
        }
        update_option('rmbz_match_review_schema_v', '1.1');
    }
}
add_action('admin_init', 'rmbz_match_review_maybe_install', 8);
add_action('init',       'rmbz_match_review_maybe_install', 8);


/* ============================================================================
 * NORMALIZAÇÃO DE STRINGS — para fuzzy matching consistente
 * ============================================================================ */

/**
 * Normaliza uma string para comparação:
 *   - lowercase
 *   - remove acentos
 *   - remove parênteses e seu conteúdo: "Saudade (Remix)" → "saudade"
 *   - remove pontuação
 *   - colapsa espaços
 *
 * Útil porque os distribuidores escrevem títulos de forma inconsistente:
 *   "Saudade" / "Saudade (Original Mix)" / "saudade" / "Saudade." → todos iguais
 */
function rmbz_match_normalize( $s ) {
    if ( ! is_string($s) ) return '';
    $s = trim($s);
    if ( $s === '' ) return '';
    // Remover parênteses e seu conteúdo (versões, remixes, features)
    $s = preg_replace('/\s*\([^)]*\)\s*/', ' ', $s);
    $s = preg_replace('/\s*\[[^\]]*\]\s*/', ' ', $s);
    // Remover acentos
    if ( function_exists('iconv') ) {
        $s = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s);
    }
    $s = strtolower( (string) $s );
    // Remover pontuação
    $s = preg_replace('/[^\w\s]/u', ' ', $s);
    // Colapsar whitespace
    $s = preg_replace('/\s+/', ' ', $s);
    return trim($s);
}


/* ============================================================================
 * DETECTOR PRINCIPAL — multi-camada
 * ============================================================================ */

/**
 * Tenta encontrar a faixa correta no catálogo a partir de uma linha de
 * royalty. Devolve array com:
 *   - track:       objecto com track_id, release_id, artist_telemovel (ou null)
 *   - method:      'isrc' | 'upc_title' | 'fuzzy_artist_title' | 'none'
 *   - confidence:  0-100
 *   - distance:    para fuzzy, distância Levenshtein
 *   - needs_review: bool (true para tudo excepto ISRC)
 *
 * @param array $row {
 *     isrc:        string (pode ser vazio)
 *     upc:         string (pode ser vazio)
 *     track_title: string
 *     artist:      string
 *     album:       string (opcional)
 * }
 */
function rmbz_match_track( array $row ) {
    global $wpdb;
    $t_tracks   = $wpdb->prefix . 'rmbz_distro_tracks';
    $t_releases = $wpdb->prefix . 'rmbz_distro_releases';
    $t_aliases  = $wpdb->prefix . 'rmbz_distro_isrc_overrides';

    $isrc        = strtoupper( trim( (string) ($row['isrc'] ?? '') ) );
    $upc         = trim( (string) ($row['upc'] ?? '') );
    $track_title = (string) ($row['track_title'] ?? '');
    $artist      = (string) ($row['artist'] ?? '');

    /* ------------------------------------------------------------------------
     * CAMADA 0 — ALIAS APRENDIDO (confiança 100%)
     *
     * Quando o admin aprova um match no "Matches a Rever", o sistema grava
     * automaticamente um alias na tabela rmbz_distro_isrc_overrides. Nos
     * imports seguintes, antes de tentar match por ISRC/UPC/fuzzy, vemos
     * primeiro se este valor do CSV já foi mapeado manualmente.
     *
     * Isto resolve o caso de loops infinitos: faixa com ISRC X no catálogo
     * mas loja envia ISRC Y — admin aprova → fica memorizado → próximo
     * import com ISRC Y vai directamente ao track_id correcto sem revisão.
     * ------------------------------------------------------------------------ */
    $has_aliases = $wpdb->get_var("SHOW TABLES LIKE '$t_aliases'") === $t_aliases;
    if ( $has_aliases ) {
        // Tentar com ISRC primeiro, depois com UPC. O `deleted_at IS NULL`
        // permite ao admin desactivar aliases incorrectos sem perder o
        // histórico (soft-delete).
        $alias_candidates = [];
        if ( $isrc !== '' ) $alias_candidates[] = [ $isrc, 'isrc' ];
        if ( $upc !== '' )  $alias_candidates[] = [ $upc,  'upc'  ];

        foreach ( $alias_candidates as [ $value, $type ] ) {
            $hit = $wpdb->get_row( $wpdb->prepare(
                "SELECT a.track_id, t.release_id, r.artist_telemovel
                 FROM $t_aliases a
                 JOIN $t_tracks t ON a.track_id = t.id
                 JOIN $t_releases r ON t.release_id = r.id
                 WHERE a.csv_value = %s
                   AND a.alias_type = %s
                   AND a.deleted_at IS NULL
                 LIMIT 1",
                $value, $type
            ) );
            if ( $hit ) {
                return [
                    'track'        => (object) [
                        'track_id'         => (int) $hit->track_id,
                        'release_id'       => (int) $hit->release_id,
                        'artist_telemovel' => $hit->artist_telemovel,
                    ],
                    'method'       => 'alias_' . $type,
                    'confidence'   => 100,
                    'distance'     => 0,
                    'needs_review' => false,
                ];
            }
        }
    }

    /* ------------------------------------------------------------------------
     * CAMADA 1 — ISRC exacto (confiança 100%)
     * ------------------------------------------------------------------------ */
    if ( $isrc !== '' && rmbz_match_looks_like_isrc($isrc) ) {
        $hit = $wpdb->get_row( $wpdb->prepare(
            "SELECT r.artist_telemovel, t.id AS track_id, t.release_id
             FROM $t_tracks t
             JOIN $t_releases r ON t.release_id = r.id
             WHERE t.isrc_code = %s
             LIMIT 1",
            $isrc
        ) );
        if ( $hit ) {
            return [
                'track'        => $hit,
                'method'       => 'isrc',
                'confidence'   => 100,
                'distance'     => 0,
                'needs_review' => false,
            ];
        }
    }

    /* ------------------------------------------------------------------------
     * CAMADA 2 — UPC + título exacto/normalizado (confiança 90%)
     * ------------------------------------------------------------------------ */
    if ( $upc !== '' && $track_title !== '' ) {
        // Encontrar release(s) com este UPC
        $releases = $wpdb->get_results( $wpdb->prepare(
            "SELECT id, artist_telemovel FROM $t_releases WHERE upc_code = %s LIMIT 5",
            $upc
        ) );
        if ( $releases ) {
            $title_norm = rmbz_match_normalize( $track_title );
            foreach ( $releases as $rel ) {
                // Procurar faixa com título coincidente neste release
                $tracks_in_release = $wpdb->get_results( $wpdb->prepare(
                    "SELECT id, track_title FROM $t_tracks WHERE release_id = %d",
                    $rel->id
                ) );
                foreach ( (array) $tracks_in_release as $tk ) {
                    if ( rmbz_match_normalize( $tk->track_title ) === $title_norm ) {
                        return [
                            'track' => (object)[
                                'track_id'         => (int) $tk->id,
                                'release_id'       => (int) $rel->id,
                                'artist_telemovel' => $rel->artist_telemovel,
                            ],
                            'method'       => 'upc_title',
                            'confidence'   => 90,
                            'distance'     => 0,
                            'needs_review' => true,
                        ];
                    }
                }
            }
        }
    }

    /* ------------------------------------------------------------------------
     * CAMADA 3 — Artista + título fuzzy (Levenshtein ≤ 2)
     * Confiança 70% (com distância 0) até 50% (distância 2).
     * ------------------------------------------------------------------------ */
    if ( $track_title !== '' && $artist !== '' ) {
        $title_norm  = rmbz_match_normalize( $track_title );
        $artist_norm = rmbz_match_normalize( $artist );
        if ( $title_norm !== '' && $artist_norm !== '' ) {
            // Limitar candidatos para não escanear catálogo todo se for grande
            $candidates = $wpdb->get_results( $wpdb->prepare(
                "SELECT t.id AS track_id, t.track_title, r.id AS release_id,
                        r.artist_telemovel, w.user_nome AS artist_db_name
                 FROM $t_tracks t
                 JOIN $t_releases r ON t.release_id = r.id
                 LEFT JOIN {$wpdb->prefix}rmbz_wallets w ON r.artist_telemovel = w.user_telemovel
                 WHERE t.track_title LIKE %s
                 LIMIT 50",
                '%' . $wpdb->esc_like( substr($track_title, 0, 8) ) . '%'
            ) );

            $best = null;
            $best_score = PHP_INT_MAX;
            foreach ( (array) $candidates as $cand ) {
                $cand_title_norm  = rmbz_match_normalize( $cand->track_title );
                $cand_artist_norm = rmbz_match_normalize( $cand->artist_db_name ?? '' );
                if ( $cand_title_norm === '' ) continue;

                $title_dist  = levenshtein( $title_norm, $cand_title_norm );
                $artist_dist = $cand_artist_norm !== '' ? levenshtein( $artist_norm, $cand_artist_norm ) : 99;
                $score       = $title_dist + ( $artist_dist > 3 ? 99 : $artist_dist );

                if ( $title_dist <= 2 && $artist_dist <= 3 && $score < $best_score ) {
                    $best_score = $score;
                    $best = [
                        'cand' => $cand,
                        'title_dist' => $title_dist,
                        'artist_dist' => $artist_dist,
                    ];
                }
            }

            if ( $best ) {
                $confidence = max( 50, 70 - ( $best['title_dist'] * 10 ) );
                return [
                    'track' => (object)[
                        'track_id'         => (int) $best['cand']->track_id,
                        'release_id'       => (int) $best['cand']->release_id,
                        'artist_telemovel' => $best['cand']->artist_telemovel,
                    ],
                    'method'       => 'fuzzy_artist_title',
                    'confidence'   => $confidence,
                    'distance'     => $best['title_dist'],
                    'needs_review' => true,
                ];
            }
        }
    }

    /* ------------------------------------------------------------------------
     * CAMADA 4 — UPC só (sem coincidência de título). Confiança baixa (30%).
     *
     * Caso típico: o CSV tem o UPC certo da BD mas o título está diferente
     * porque o release foi distribuído por outra plataforma com metadata
     * diferente, ou houve duplicação de UPC. Vai sempre para revisão admin.
     *
     * Flag conflict_warning quando o artista do CSV é claramente diferente
     * do artista do release na BD — sinal de UPC duplicado entre artistas
     * diferentes (caso real do Moondai vs Jaime que vimos).
     * ------------------------------------------------------------------------ */
    if ( $upc !== '' ) {
        // Procurar releases com este UPC. Se há múltiplos, escolher o mais
        // recente (provavelmente o mais relevante).
        $rel_match = $wpdb->get_row( $wpdb->prepare(
            "SELECT r.id AS release_id, r.artist_telemovel, w.user_nome AS artist_db_name,
                    (SELECT id FROM $t_tracks WHERE release_id = r.id ORDER BY track_order ASC, id ASC LIMIT 1) AS track_id
             FROM $t_releases r
             LEFT JOIN {$wpdb->prefix}rmbz_wallets w ON r.artist_telemovel = w.user_telemovel
             WHERE r.upc_code = %s
             ORDER BY r.id DESC
             LIMIT 1",
            $upc
        ) );

        if ( $rel_match && $rel_match->track_id ) {
            // Detectar conflito de artista: se o nome do artista do CSV é
            // claramente diferente do registado na BD, sinalizar.
            $artist_norm    = rmbz_match_normalize( $artist );
            $db_artist_norm = rmbz_match_normalize( $rel_match->artist_db_name ?? '' );
            $artist_conflict = false;
            if ( $artist_norm !== '' && $db_artist_norm !== '' ) {
                $dist = levenshtein( $artist_norm, $db_artist_norm );
                if ( $dist > 3 ) $artist_conflict = true;
            }

            return [
                'track' => (object)[
                    'track_id'         => (int) $rel_match->track_id,
                    'release_id'       => (int) $rel_match->release_id,
                    'artist_telemovel' => $rel_match->artist_telemovel,
                ],
                'method'           => 'upc_only',
                'confidence'       => $artist_conflict ? 15 : 30,
                'distance'         => null,
                'needs_review'     => true,
                'artist_conflict'  => $artist_conflict,
            ];
        }
    }

    /* ------------------------------------------------------------------------
     * Sem match — vai para Black Box (unclaimed)
     * ------------------------------------------------------------------------ */
    return [
        'track'        => null,
        'method'       => 'none',
        'confidence'   => 0,
        'distance'     => null,
        'needs_review' => false,
    ];
}


/**
 * Heurística para detectar se string é um ISRC válido.
 * ISRC standard: CCXXX-YY-NNNNN (12 chars sem hífens, 15 com).
 * O "TC Song ID" do TuneCore é tipo "TCAHB1234567" (12 chars começando TC).
 * Para evitar falsos positivos, exigimos formato CC + 3 alphanum + 7 chars.
 */
function rmbz_match_looks_like_isrc( $s ) {
    if ( ! $s ) return false;
    $clean = strtoupper( preg_replace('/[^A-Z0-9]/', '', $s) );
    if ( strlen($clean) !== 12 ) return false;
    // TuneCore Song IDs começam com "TC" mas podem coincidir com ISRC válido
    // (TC = Turks & Caicos, raríssimo). Se começa por TC e não conhecemos
    // o registrant, confiamos que NÃO é ISRC verdadeiro.
    if ( substr($clean, 0, 2) === 'TC' && substr($clean, 2, 3) === 'AHB' ) {
        return false; // padrão TuneCore claro
    }
    // País tem que ser 2 letras válidas
    if ( ! ctype_alpha( substr($clean, 0, 2) ) ) return false;
    return true;
}


/* ============================================================================
 * GUARDAR MATCH PARA REVISÃO
 * ============================================================================ */

function rmbz_match_save_for_review( array $csv_row, array $match_result, $import_hash, $report_date ) {
    if ( empty($match_result['track']) ) return false;
    if ( ! $match_result['needs_review'] ) return false;

    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_distro_match_review';

    $data = [
        'import_hash'                => $import_hash,
        'report_date'                => $report_date,
        'csv_isrc'                   => substr( (string)($csv_row['isrc'] ?? ''), 0, 20 ),
        'csv_upc'                    => substr( (string)($csv_row['upc'] ?? ''), 0, 40 ),
        'csv_track_title'            => substr( (string)($csv_row['track_title'] ?? ''), 0, 255 ),
        'csv_artist'                 => substr( (string)($csv_row['artist'] ?? ''), 0, 255 ),
        'csv_album'                  => substr( (string)($csv_row['album'] ?? ''), 0, 255 ),
        'csv_label'                  => substr( (string)($csv_row['label'] ?? ''), 0, 255 ),
        'csv_store'                  => substr( (string)($csv_row['store'] ?? ''), 0, 60 ),
        'csv_country'                => substr( (string)($csv_row['country'] ?? ''), 0, 8 ),
        'csv_streams'                => (int)($csv_row['streams'] ?? 0),
        'csv_amount_usd'             => (float)($csv_row['amount_usd'] ?? 0),
        'suggested_track_id'         => (int) $match_result['track']->track_id,
        'suggested_release_id'       => (int) $match_result['track']->release_id,
        'suggested_artist_telemovel' => $match_result['track']->artist_telemovel,
        'match_method'               => $match_result['method'],
        'match_confidence'           => (int) $match_result['confidence'],
        'match_distance'             => $match_result['distance'],
        'artist_conflict'            => ! empty($match_result['artist_conflict']) ? 1 : 0,
        'status'                     => 'pending',
        'created_at'                 => current_time('mysql'),
    ];

    $formats = [
        '%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%d','%f',
        '%d','%d','%s','%s','%d','%d','%d','%s','%s'
    ];

    return $wpdb->insert( $t, $data, $formats );
}


/**
 * Conta matches a aguardar revisão (para badge no admin).
 */
function rmbz_match_review_pending_count() {
    global $wpdb;
    $t = $wpdb->prefix . 'rmbz_distro_match_review';
    if ( $wpdb->get_var("SHOW TABLES LIKE '$t'") !== $t ) return 0;
    return (int) $wpdb->get_var( "SELECT COUNT(*) FROM $t WHERE status = 'pending'" );
}
