<?php
/**
 * 1MZ Music Player — Schema
 *
 * Contas do LEITOR (separadas das contas de artista). O app é um leitor
 * padrão: funciona sem login, mas para ACTIVAR funções (add-ons) o user
 * cria/entra numa conta por e-mail, gerida aqui pelo plugin.
 *
 * @package 1MZ_Music_Player
 */

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

define( 'MP_DB_VERSION', '1.5.0' );

/**
 * Cria/actualiza a tabela de contas do leitor.
 *
 * Cada conta tem UM dispositivo activo (single-session: o último login
 * invalida o anterior), mas os seus dados personalizados ficam na nuvem
 * (sync_data) e podem ser lidos em qualquer dispositivo Android/iOS.
 */
function mp_install_schema() {
    if ( get_option( 'mp_db_version' ) === MP_DB_VERSION ) {
        return;
    }
    global $wpdb;
    $table   = $wpdb->prefix . 'mp_accounts';
    $charset = $wpdb->get_charset_collate();

    $sql = "CREATE TABLE {$table} (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        email VARCHAR(190) NOT NULL,
        pass_hash VARCHAR(255) NOT NULL,
        name VARCHAR(120) NULL,
        token_hash VARCHAR(64) NULL,
        status VARCHAR(20) NOT NULL DEFAULT 'active',
        verified TINYINT(1) NOT NULL DEFAULT 0,
        verify_token VARCHAR(64) NULL,
        entitlements LONGTEXT NULL,
        is_vip TINYINT(1) NOT NULL DEFAULT 0,
        vip_until DATETIME NULL,
        vip_source VARCHAR(20) NULL,
        vip_full_until DATETIME NULL,
        platform VARCHAR(20) NULL,
        device_model VARCHAR(120) NULL,
        device_id VARCHAR(64) NULL,
        sync_data LONGTEXT NULL,
        sync_updated DATETIME NULL,
        created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        last_login DATETIME NULL,
        PRIMARY KEY (id),
        UNIQUE KEY email (email),
        KEY token_hash (token_hash)
    ) {$charset};";

    $orders = $wpdb->prefix . 'mp_vip_orders';
    $sql_orders = "CREATE TABLE {$orders} (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        account_id BIGINT UNSIGNED NOT NULL,
        plan VARCHAR(20) NOT NULL DEFAULT 'monthly',
        amount VARCHAR(20) NULL,
        currency VARCHAR(10) NULL,
        period_days INT NOT NULL DEFAULT 30,
        reference VARCHAR(40) NOT NULL,
        status VARCHAR(20) NOT NULL DEFAULT 'pending',
        created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        activated_at DATETIME NULL,
        expires_at DATETIME NULL,
        PRIMARY KEY (id),
        UNIQUE KEY reference (reference),
        KEY account_id (account_id),
        KEY status (status)
    ) {$charset};";

    $sessions = $wpdb->prefix . 'mp_account_sessions';
    $sql_sessions = "CREATE TABLE {$sessions} (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        account_id BIGINT UNSIGNED NOT NULL,
        device_id VARCHAR(64) NOT NULL,
        token_hash VARCHAR(64) NOT NULL,
        platform VARCHAR(20) NULL,
        device_model VARCHAR(120) NULL,
        created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        last_seen DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
        PRIMARY KEY (id),
        UNIQUE KEY account_device (account_id, device_id),
        UNIQUE KEY token_hash (token_hash),
        KEY account_seen (account_id, last_seen)
    ) {$charset};";

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

    // Preserve the currently valid legacy mobile token as the first device
    // session. The account table already contains that device's last identity.
    $wpdb->query( "INSERT INTO {$sessions}
        (account_id, device_id, token_hash, platform, device_model, created_at, last_seen)
        SELECT id,
               COALESCE(NULLIF(device_id, ''), CONCAT('legacy-', id)),
               token_hash,
               platform,
               device_model,
               COALESCE(last_login, created_at, UTC_TIMESTAMP()),
               COALESCE(last_login, created_at, UTC_TIMESTAMP())
        FROM {$table}
        WHERE token_hash IS NOT NULL AND token_hash <> ''
        ON DUPLICATE KEY UPDATE
            token_hash = VALUES(token_hash),
            platform = VALUES(platform),
            device_model = VALUES(device_model),
            last_seen = VALUES(last_seen)" );
    update_option( 'mp_db_version', MP_DB_VERSION );
}
add_action( 'init', 'mp_install_schema' );

/** Origem normalizada do VIP. Contas antigas mantêm acesso completo. */
function mp_vip_source( $row ) {
    $source = $row && isset( $row->vip_source ) ? sanitize_key( (string) $row->vip_source ) : '';
    return in_array( $source, [ 'paid', 'admin', 'rewarded', 'mixed', 'legacy' ], true )
        ? $source
        : 'legacy';
}

/** Estado VIP sem depender da ordem em que os controllers são carregados. */
function mp_vip_is_active( $row ) {
    if ( ! $row || (int) $row->is_vip !== 1 ) {
        return false;
    }
    return empty( $row->vip_until ) || strtotime( $row->vip_until ) > time();
}

/**
 * O VIP premiado sincroniza dados, mas o envio de áudio depende do admin.
 * Em contas mistas, os dias pagos continuam completos até vip_full_until.
 */
function mp_account_cloud_audio_allowed( $row ) {
    if ( ! mp_vip_is_active( $row ) || (bool) get_option( 'mp_ads_rew_cloud_audio', 0 ) ) {
        return true;
    }
    $source = mp_vip_source( $row );
    if ( 'rewarded' === $source ) {
        return false;
    }
    if ( 'mixed' === $source ) {
        return ! empty( $row->vip_full_until ) && strtotime( $row->vip_full_until ) > time();
    }
    return true;
}

/** SHA-256 do token bearer (nunca guardamos o token raw). */
function mp_hash_token( $token ) {
    return hash( 'sha256', $token . '|' . wp_salt( 'auth' ) );
}

/** Paid/admin VIP accounts keep five mobile sessions; all others keep one. */
function mp_mobile_session_limit( $row ) {
    if ( ! mp_vip_is_active( $row ) ) {
        return 1;
    }
    $source = mp_vip_source( $row );
    if ( 'rewarded' === $source ) {
        return 1;
    }
    if ( 'mixed' === $source ) {
        return ! empty( $row->vip_full_until ) && strtotime( $row->vip_full_until ) > time() ? 5 : 1;
    }
    return 5;
}

/** Removes abandoned sessions so they do not authorize or consume a slot forever. */
function mp_prune_stale_mobile_sessions( $account_id ) {
    global $wpdb;
    $wpdb->query( $wpdb->prepare(
        "DELETE FROM {$wpdb->prefix}mp_account_sessions
         WHERE account_id = %d AND last_seen < DATE_SUB(UTC_TIMESTAMP(), INTERVAL 180 DAY)",
        (int) $account_id
    ) );
}

/** Hash of the current Bearer token, or an empty string. */
function mp_request_token_hash( WP_REST_Request $request ) {
    $auth = (string) $request->get_header( 'authorization' );
    if ( stripos( $auth, 'bearer ' ) !== 0 ) {
        return '';
    }
    $raw = trim( substr( $auth, 7 ) );
    return $raw === '' ? '' : mp_hash_token( $raw );
}

/** Safe session rows for device-management screens. Raw tokens are never returned. */
function mp_mobile_sessions_payload( $account_id, $current_hash = '' ) {
    global $wpdb;
    mp_prune_stale_mobile_sessions( $account_id );
    $table = $wpdb->prefix . 'mp_account_sessions';
    $rows = $wpdb->get_results( $wpdb->prepare(
        "SELECT id, device_id, token_hash, platform, device_model, created_at, last_seen
         FROM {$table} WHERE account_id = %d ORDER BY last_seen DESC, id DESC",
        (int) $account_id
    ) );
    return array_map( function ( $session ) use ( $current_hash ) {
        $label = trim( (string) $session->device_model );
        if ( $label === '' ) {
            $label = $session->platform === 'ios' ? 'iPhone ou iPad' : 'Dispositivo Android';
        }
        return [
            'session_id' => (int) $session->id,
            'name'       => $label,
            'platform'   => (string) $session->platform,
            'created_at' => (string) $session->created_at,
            'last_seen'  => (string) $session->last_seen,
            'is_current' => $current_hash !== '' && hash_equals( (string) $session->token_hash, $current_hash ),
        ];
    }, $rows ?: [] );
}

/** Devolve a conta a partir do header Authorization: Bearer <token>, ou null. */
function mp_account_from_request( WP_REST_Request $request ) {
    $auth = $request->get_header( 'authorization' );
    if ( ! $auth || stripos( $auth, 'bearer ' ) !== 0 ) {
        return null;
    }
    $token = trim( substr( $auth, 7 ) );
    if ( $token === '' ) {
        return null;
    }
    global $wpdb;
    $table = $wpdb->prefix . 'mp_accounts';
    $sessions = $wpdb->prefix . 'mp_account_sessions';
    $hash  = mp_hash_token( $token );
    $row = $wpdb->get_row( $wpdb->prepare(
        "SELECT a.* FROM {$table} a
         INNER JOIN {$sessions} s ON s.account_id = a.id
         WHERE s.token_hash = %s AND a.status = 'active'
           AND s.last_seen >= DATE_SUB(UTC_TIMESTAMP(), INTERVAL 180 DAY)
         LIMIT 1",
        $hash
    ) );
    if ( $row ) {
        if ( 1 === mp_mobile_session_limit( $row ) ) {
            $wpdb->query( $wpdb->prepare(
                "DELETE FROM {$sessions} WHERE account_id = %d AND token_hash <> %s",
                (int) $row->id,
                $hash
            ) );
        }
        $wpdb->query( $wpdb->prepare(
            "UPDATE {$sessions} SET last_seen = %s WHERE token_hash = %s
             AND last_seen < DATE_SUB(UTC_TIMESTAMP(), INTERVAL 1 HOUR)",
            current_time( 'mysql', true ),
            $hash
        ) );
    } else {
        // Compatibility with the single-session token used before schema 1.5.
        $row = $wpdb->get_row( $wpdb->prepare(
            "SELECT a.* FROM {$table} a
             WHERE a.token_hash = %s AND a.status = 'active'
               AND NOT EXISTS (
                   SELECT 1 FROM {$sessions} s WHERE s.account_id = a.id
               )
             LIMIT 1",
            $hash
        ) );
    }
    if ( ! $row ) {
        // Sessão WEB (play.1musicmoz.com): token próprio que não mexe na sessão
        // do telemóvel. Guardado como option "mp_webtok_<hash>" = "<id>|<expira>".
        $val = (string) get_option( 'mp_webtok_' . $hash, '' );
        if ( $val !== '' ) {
            $parts = explode( '|', $val );
            if ( count( $parts ) === 2 && (int) $parts[1] > time() ) {
                $row = $wpdb->get_row( $wpdb->prepare(
                    "SELECT * FROM {$table} WHERE id = %d AND status = 'active' LIMIT 1",
                    (int) $parts[0]
                ) );
            }
        }
    }
    return $row ?: null;
}

/**
 * CORS para o leitor web em play.1musicmoz.com (e localhost em desenvolvimento).
 * Só liberta as origens conhecidas — o resto fica como o WordPress manda.
 */
function mp_web_cors_origins() {
    return [
        'https://play.1musicmoz.com',
        'http://localhost:8080',
        'http://127.0.0.1:8080',
    ];
}
add_filter( 'rest_pre_serve_request', function ( $served ) {
    $origin = function_exists( 'get_http_origin' ) ? get_http_origin() : '';
    if ( $origin && in_array( $origin, mp_web_cors_origins(), true ) ) {
        header( 'Access-Control-Allow-Origin: ' . $origin );
        header( 'Access-Control-Allow-Methods: GET, POST, OPTIONS' );
        header( 'Access-Control-Allow-Headers: Authorization, Content-Type' );
        header( 'Access-Control-Max-Age: 86400' );
        header( 'Vary: Origin', false );
    }
    return $served;
}, 20 );
