<?php
/**
 * Public AJAX API for VMPlayer.
 *
 * Endpoints called by the frontend player to fetch live metadata
 * for the currently playing station. Endpoints are open to both
 * logged-in and logged-out visitors (using wp_ajax and wp_ajax_nopriv)
 * with a nonce plus lightweight IP rate limiting for protection.
 *
 * @package VMPlayer
 */

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

/**
 * Class VMPlayer_API
 */
class VMPlayer_API {

    /**
     * Nonce action name for public endpoints.
     * This is separate from the admin nonce so the two contexts
     * cannot use each other's tokens.
     */
    const NONCE_ACTION = 'vmplayer_public_nonce';

    /**
     * Maximum requests per minute per IP for the metadata endpoint.
     * Well above what a normal listener would generate but well below
     * what a scraper or DoS attempt would send.
     */
    const RATE_LIMIT_PER_MINUTE = 120;

    /**
     * Plugin version. Used for cache-key namespacing when the
     * response shape changes across releases.
     *
     * @var string
     */
    private $version;

    /**
     * Constructor.
     *
     * @param string $version Plugin version.
     */
    public function __construct( $version ) {
        $this->version = $version;
    }

    /* ------------------------------------------------------------------
     * Nonce for frontend
     * --------------------------------------------------------------- */

    /**
     * Generate a nonce string for the public endpoints.
     * The shortcode and popout templates will call this to embed
     * the nonce in the localized JS config.
     *
     * @return string
     */
    public static function create_nonce() {
        return wp_create_nonce( self::NONCE_ACTION );
    }

    /* ------------------------------------------------------------------
     * Metadata endpoint
     * --------------------------------------------------------------- */

    /**
     * AJAX: fetch current metadata for a station.
     * Payload:
     *   nonce         (required) Public nonce.
     *   station_index (required) Zero-based station index.
     *   refresh       (optional) 1 to bypass cache.
     *
     * @return void
     */
    public function ajax_get_metadata() {
        $this->verify_public_request();
        $this->enforce_rate_limit( 'get_metadata' );

        $index = isset( $_POST['station_index'] )
            ? absint( wp_unslash( $_POST['station_index'] ) )
            : -1;

        if ( $index < 0 ) {
            wp_send_json_error(
                array( 'message' => esc_html__( 'Invalid station index.', 'vmplayer' ) ),
                400
            );
        }

        $station = VMPlayer_Settings::get_station( $index );
        if ( null === $station ) {
            wp_send_json_error(
                array( 'message' => esc_html__( 'Station not found.', 'vmplayer' ) ),
                404
            );
        }

        $refresh = ! empty( $_POST['refresh'] );

        $metadata = $refresh
            ? VMPlayer_Metadata::refresh_metadata( $station )
            : VMPlayer_Metadata::get_metadata( $station );

        wp_send_json_success( $metadata );
    }

    /* ------------------------------------------------------------------
     * Artwork endpoint
     * --------------------------------------------------------------- */

    /**
     * AJAX: standalone artwork lookup.
     * Useful when the caller already knows the artist and title
     * (for example after parsing metadata in the browser) and just
     * wants the artwork URL.
     *
     * @return void
     */
    public function ajax_get_artwork() {
        $this->verify_public_request();
        $this->enforce_rate_limit( 'get_artwork' );

        $artist = isset( $_POST['artist'] )
            ? sanitize_text_field( wp_unslash( $_POST['artist'] ) )
            : '';
        $title = isset( $_POST['title'] )
            ? sanitize_text_field( wp_unslash( $_POST['title'] ) )
            : '';

        if ( '' === $artist || '' === $title ) {
            wp_send_json_error(
                array( 'message' => esc_html__( 'Artist and title are both required.', 'vmplayer' ) ),
                400
            );
        }

        $settings = VMPlayer_Settings::get_settings();
        if ( empty( $settings['musicbrainz_enabled'] ) ) {
            wp_send_json_success(
                array(
                    'artwork_url' => '',
                    'source'      => 'disabled',
                )
            );
        }

        $artwork = VMPlayer_MusicBrainz::lookup_artwork( $artist, $title );

        wp_send_json_success(
            array(
                'artwork_url' => $artwork,
                'source'      => '' !== $artwork ? 'musicbrainz' : 'none',
            )
        );
    }

    /* ------------------------------------------------------------------
     * Security helpers
     * --------------------------------------------------------------- */

    /**
     * Verify a public AJAX request. Nonce required. No capability
     * check because these endpoints are meant for visitors.
     *
     * @return void
     */
    private function verify_public_request() {
        if ( ! check_ajax_referer( self::NONCE_ACTION, 'nonce', false ) ) {
            wp_send_json_error(
                array( 'message' => esc_html__( 'Security check failed. Please reload the page.', 'vmplayer' ) ),
                403
            );
        }
    }

    /**
     * Simple sliding-minute rate limit keyed by IP + action.
     * Uses a WordPress transient with a 60 second lifetime. When
     * the count exceeds the limit, the request is rejected with 429.
     *
     * @param string $action Action label for the counter key.
     * @return void
     */
    private function enforce_rate_limit( $action ) {
        $ip = self::get_client_ip();
        if ( '' === $ip ) {
            return;
        }

        $key   = 'vmplayer_rl_' . md5( $action . '|' . $ip );
        $count = (int) get_transient( $key );
        if ( $count >= self::RATE_LIMIT_PER_MINUTE ) {
            wp_send_json_error(
                array( 'message' => esc_html__( 'Too many requests. Please slow down.', 'vmplayer' ) ),
                429
            );
        }

        set_transient( $key, $count + 1, MINUTE_IN_SECONDS );
    }

    /**
     * Best-effort client IP detection. Falls back to REMOTE_ADDR
     * when proxy headers are absent.
     *
     * @return string
     */
    private static function get_client_ip() {
        // Trust REMOTE_ADDR by default. If the site sits behind a
        // reverse proxy, filter this at the site level to consult
        // X-Forwarded-For instead.
        $ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';

        /**
         * Filter the client IP used for rate limiting.
         *
         * @param string $ip Detected IP.
         */
        return (string) apply_filters( 'vmplayer_client_ip', $ip );
    }
}
