<?php
/**
 * Plugin Name: STB Live Prices (v2 - STB API primary)
 * Description: Fetches live prices from api.stbbrokers.com for forex/metals/crypto,
 *              falls back to TradingView scanner for indices/oil. Caches output and
 *              exposes the merged feed via /wp-json/stb/v1/prices.
 * Version: 2.0
 *
 * Replaces v1 (TradingView-only). Key differences vs v1:
 *  - Primary source = api.stbbrokers.com (our LP)
 *  - TradingView used only for symbols STB doesn't carry (indices, oil)
 *  - Fetch cadence: every 5 min (STB has ~5-min rate limit per IP)
 *  - 24h baseline stored separately to compute change_pct (STB API only gives ask/bid)
 *  - Cache TTL 360s with fallback to last-known if upstream fails
 */

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

// ─── Symbol catalogue ──────────────────────────────────────────────────────
// Each entry: stb_key (key in STB API response) OR tv_ticker (TradingView ticker)
// Display fields shared by both.
define( 'STB_PRICES_CATALOG', [
    // STB API symbols
    'EURUSD' => [ 'source' => 'stb', 'stb_key' => 'EURUSD', 'name' => 'EUR / USD', 'type' => 'FOREX',     'display' => 'EURUSD' ],
    'GBPUSD' => [ 'source' => 'stb', 'stb_key' => 'GBPUSD', 'name' => 'GBP / USD', 'type' => 'FOREX',     'display' => 'GBPUSD' ],
    'USDJPY' => [ 'source' => 'stb', 'stb_key' => 'USDJPY', 'name' => 'USD / JPY', 'type' => 'FOREX',     'display' => 'USDJPY' ],
    'USDCHF' => [ 'source' => 'stb', 'stb_key' => 'USDCHF', 'name' => 'USD / CHF', 'type' => 'FOREX',     'display' => 'USDCHF' ],
    'AUDUSD' => [ 'source' => 'stb', 'stb_key' => 'AUDUSD', 'name' => 'AUD / USD', 'type' => 'FOREX',     'display' => 'AUDUSD' ],
    'USDCAD' => [ 'source' => 'stb', 'stb_key' => 'USDCAD', 'name' => 'USD / CAD', 'type' => 'FOREX',     'display' => 'USDCAD' ],
    'XAUUSD' => [ 'source' => 'stb', 'stb_key' => 'XAUUSD', 'name' => 'XAU / USD', 'type' => 'COMMODITY', 'display' => 'Gold' ],
    'XAGUSD' => [ 'source' => 'stb', 'stb_key' => 'XAGUSD', 'name' => 'XAG / USD', 'type' => 'COMMODITY', 'display' => 'Silver' ],
    'BTCUSD' => [ 'source' => 'stb', 'stb_key' => 'BTCUSD', 'name' => 'BTC / USD', 'type' => 'CRYPTO',    'display' => 'Bitcoin' ],
    'ETHUSD' => [ 'source' => 'stb', 'stb_key' => 'ETHUSD', 'name' => 'ETH / USD', 'type' => 'CRYPTO',    'display' => 'Ethereum' ],
    // TradingView fallback (STB doesn't carry these yet)
    'NAS100' => [ 'source' => 'tv', 'tv_ticker' => 'NASDAQ:NDX',  'name' => 'NAS 100', 'type' => 'INDEX',     'display' => 'NAS100' ],
    'SPX500' => [ 'source' => 'tv', 'tv_ticker' => 'SP:SPX',      'name' => 'S&P 500', 'type' => 'INDEX',     'display' => 'SPX500' ],
    'US30'   => [ 'source' => 'tv', 'tv_ticker' => 'TVC:DJI',     'name' => 'US 30',   'type' => 'INDEX',     'display' => 'US30' ],
    'DAX40'  => [ 'source' => 'tv', 'tv_ticker' => 'TVC:DEU40',   'name' => 'DAX 40',  'type' => 'INDEX',     'display' => 'DAX40' ],
    'USOIL'  => [ 'source' => 'tv', 'tv_ticker' => 'NYMEX:CL1!',  'name' => 'WTI Oil', 'type' => 'COMMODITY', 'display' => 'OIL' ],
] );

// ─── Pricing helpers ───────────────────────────────────────────────────────

function stb_lp_format_price( float $price ): string {
    if ( $price >= 10000 )    return number_format( $price, 0 );
    if ( $price >= 100 )      return number_format( $price, 2 );
    if ( $price >= 1 )        return number_format( $price, 4 );
    return number_format( $price, 5 );
}

function stb_lp_decimals_for( float $price ): int {
    if ( $price >= 10000 ) return 0;
    if ( $price >= 100 )   return 2;
    if ( $price >= 1 )     return 4;
    return 5;
}

// ─── STB API fetch ─────────────────────────────────────────────────────────

function stb_lp_fetch_stb_api(): array {
    $stb_symbols = [];
    foreach ( STB_PRICES_CATALOG as $code => $meta ) {
        if ( $meta['source'] === 'stb' ) $stb_symbols[] = $meta['stb_key'];
    }
    if ( empty( $stb_symbols ) ) return [];

    $url  = 'https://api.stbbrokers.com/live-prices?symbols=' . implode( ',', $stb_symbols );
    $resp = wp_remote_get( $url, [
        'timeout' => 12,
        'headers' => [ 'User-Agent' => 'STB-Site/1.0' ],
    ] );

    if ( is_wp_error( $resp ) ) {
        update_option( 'stb_prices_last_stb_error', $resp->get_error_message() );
        return [];
    }
    $body = wp_remote_retrieve_body( $resp );
    $json = json_decode( $body, true );
    if ( empty( $json['status'] ) || empty( $json['data'] ) ) {
        update_option( 'stb_prices_last_stb_error', substr( (string) $body, 0, 200 ) );
        return [];
    }

    $out = [];
    foreach ( $json['data'] as $key => $row ) {
        $bid = (float) ( $row['bid'] ?? 0 );
        $ask = (float) ( $row['ask'] ?? 0 );
        if ( $bid <= 0 || $ask <= 0 ) continue;
        $mid = ( $bid + $ask ) / 2;
        $out[ $key ] = [
            'bid'    => $bid,
            'ask'    => $ask,
            'mid'    => $mid,
            'price'  => $mid,
            'spread' => $ask - $bid,
        ];
    }
    update_option( 'stb_prices_last_stb_ok', time() );
    return $out;
}

// ─── TradingView fetch (for symbols STB doesn't have) ──────────────────────

function stb_lp_fetch_tv_api(): array {
    $tickers = [];
    foreach ( STB_PRICES_CATALOG as $code => $meta ) {
        if ( $meta['source'] === 'tv' ) $tickers[] = $meta['tv_ticker'];
    }
    if ( empty( $tickers ) ) return [];

    $resp = wp_remote_post( 'https://scanner.tradingview.com/global/scan', [
        'body'    => wp_json_encode( [
            'symbols' => [ 'tickers' => $tickers ],
            'columns' => [ 'close', 'change', 'change_abs' ],
        ] ),
        'headers' => [
            'Content-Type' => 'application/json',
            'User-Agent'   => 'Mozilla/5.0',
            'Origin'       => 'https://www.tradingview.com',
            'Referer'      => 'https://www.tradingview.com/',
        ],
        'timeout' => 10,
    ] );
    if ( is_wp_error( $resp ) ) return [];
    $json = json_decode( wp_remote_retrieve_body( $resp ), true );
    if ( empty( $json['data'] ) ) return [];

    $out = [];
    foreach ( $json['data'] as $row ) {
        $tk    = $row['s'];
        $d     = $row['d'];
        $price = (float) ( $d[0] ?? 0 );
        $pct   = (float) ( $d[1] ?? 0 );
        $abs   = (float) ( $d[2] ?? 0 );
        if ( $price <= 0 ) continue;
        $out[ $tk ] = [
            'price'      => $price,
            'change_pct' => $pct,
            'change_abs' => $abs,
        ];
    }
    update_option( 'stb_prices_last_tv_ok', time() );
    return $out;
}

// ─── Baseline (24h) — used to compute change_pct for STB-sourced rows ──────
//
// STB API only gives ask/bid. We snapshot the mid price every ~24h
// and compute change_pct against that baseline so the UI can show daily delta.

function stb_lp_update_baseline( array $stb_rows ): void {
    $baseline = get_option( 'stb_prices_baseline', [] );
    $now      = time();
    foreach ( $stb_rows as $key => $row ) {
        if ( empty( $baseline[ $key ] ) || $now - (int) $baseline[ $key ]['t'] >= 86400 ) {
            $baseline[ $key ] = [ 'p' => $row['mid'], 't' => $now ];
        }
    }
    update_option( 'stb_prices_baseline', $baseline, false );
}

function stb_lp_change_for( string $key, float $price ): array {
    $baseline = get_option( 'stb_prices_baseline', [] );
    $b        = $baseline[ $key ]['p'] ?? null;
    if ( ! $b || $b <= 0 ) return [ 'pct' => 0.0, 'abs' => 0.0 ];
    $abs = $price - (float) $b;
    $pct = ( $abs / (float) $b ) * 100;
    return [ 'pct' => $pct, 'abs' => $abs ];
}

// ─── Build the merged feed ─────────────────────────────────────────────────

function stb_lp_build_feed(): array {
    $stb_rows = stb_lp_fetch_stb_api();
    $tv_rows  = stb_lp_fetch_tv_api();

    if ( ! empty( $stb_rows ) ) stb_lp_update_baseline( $stb_rows );

    // Per-row fallback: last-known feed (durable). If STB API is rate-limited
    // or TV is down for a single refresh, keep the previous values for those rows
    // instead of dropping them. Stale rows are tagged so UI can dim them.
    $previous = get_option( 'stb_prices_last_feed_v2', [] );
    $prev_by_code = [];
    foreach ( $previous as $row ) {
        if ( ! empty( $row['code'] ) ) $prev_by_code[ $row['code'] ] = $row;
    }

    $feed = [];
    foreach ( STB_PRICES_CATALOG as $code => $meta ) {
        $row    = null;
        $stale  = false;

        if ( $meta['source'] === 'stb' ) {
            $k = $meta['stb_key'];
            if ( isset( $stb_rows[ $k ] ) ) {
                $price = $stb_rows[ $k ]['mid'];
                $chg   = stb_lp_change_for( $k, $price );
                $row   = [
                    'price'      => $price,
                    'bid'        => $stb_rows[ $k ]['bid'],
                    'ask'        => $stb_rows[ $k ]['ask'],
                    'spread'     => $stb_rows[ $k ]['spread'],
                    'change_pct' => $chg['pct'],
                    'change_abs' => $chg['abs'],
                ];
            } elseif ( isset( $prev_by_code[ $code ] ) ) {
                // Re-use previous values for this code
                $p     = $prev_by_code[ $code ];
                $row   = [
                    'price'      => $p['price'],
                    'bid'        => $p['bid'],
                    'ask'        => $p['ask'],
                    'spread'     => $p['spread'],
                    'change_pct' => $p['change_pct'],
                    'change_abs' => $p['change_abs'],
                ];
                $stale = true;
            } else {
                continue; // nothing yet
            }
        } else { // tv
            $tk = $meta['tv_ticker'];
            if ( isset( $tv_rows[ $tk ] ) ) {
                $row = [
                    'price'      => $tv_rows[ $tk ]['price'],
                    'bid'        => null,
                    'ask'        => null,
                    'spread'     => null,
                    'change_pct' => $tv_rows[ $tk ]['change_pct'],
                    'change_abs' => $tv_rows[ $tk ]['change_abs'],
                ];
            } elseif ( isset( $prev_by_code[ $code ] ) ) {
                $p     = $prev_by_code[ $code ];
                $row   = [
                    'price'      => $p['price'],
                    'bid'        => $p['bid'],
                    'ask'        => $p['ask'],
                    'spread'     => $p['spread'],
                    'change_pct' => $p['change_pct'],
                    'change_abs' => $p['change_abs'],
                ];
                $stale = true;
            } else {
                continue;
            }
        }

        $price = (float) $row['price'];
        $pct   = (float) $row['change_pct'];
        $up    = $pct >= 0;

        $feed[] = [
            'code'       => $code,
            'symbol'     => $code,
            'name'       => $meta['name'],
            'type'       => $meta['type'],
            'display'    => $meta['display'],
            'source'     => $meta['source'],
            'stale'      => $stale,
            'price'      => $price,
            'price_fmt'  => stb_lp_format_price( $price ),
            'decimals'   => stb_lp_decimals_for( $price ),
            'bid'        => $row['bid'],
            'ask'        => $row['ask'],
            'spread'     => $row['spread'],
            'change_pct' => round( $pct, 3 ),
            'change_abs' => round( (float) $row['change_abs'], 6 ),
            'direction'  => $up ? 'up' : 'down',
            'change_str' => ( $up ? '+' : '' ) . number_format( $pct, 2 ) . '%',
            'updated_at' => time(),
        ];
    }
    return $feed;
}

// ─── Cache refresh ─────────────────────────────────────────────────────────

function stb_lp_refresh(): void {
    $feed = stb_lp_build_feed();
    if ( ! empty( $feed ) ) {
        set_transient( 'stb_live_prices_v2', $feed, 600 );    // 10 min TTL
        update_option( 'stb_prices_last_ok_v2', time(), false );
        update_option( 'stb_prices_last_feed_v2', $feed, false ); // durable fallback
    }
}

// ─── Cron: every 5 minutes (STB API rate limit ≈ 1 per 5 min) ──────────────

add_filter( 'cron_schedules', function ( $s ) {
    $s['stb_5min'] = [ 'interval' => 300, 'display' => 'Every 5 minutes (STB Live Prices)' ];
    return $s;
} );

add_action( 'init', function () {
    if ( ! wp_next_scheduled( 'stb_lp_cron' ) ) {
        wp_schedule_event( time() + 30, 'stb_5min', 'stb_lp_cron' );
    }
} );
add_action( 'stb_lp_cron', 'stb_lp_refresh' );

// ─── REST endpoint ─────────────────────────────────────────────────────────

add_action( 'rest_api_init', function () {
    register_rest_route( 'stb/v1', '/prices', [
        'methods'             => 'GET',
        'permission_callback' => '__return_true',
        'callback'            => function ( $req ) {
            $feed   = get_transient( 'stb_live_prices_v2' );
            $stale  = false;

            // Cache miss → try to refresh inline (rare; cron should keep it warm)
            if ( empty( $feed ) ) {
                stb_lp_refresh();
                $feed = get_transient( 'stb_live_prices_v2' );
            }

            // Still empty (upstream down) → durable last-known fallback
            if ( empty( $feed ) ) {
                $feed  = get_option( 'stb_prices_last_feed_v2', [] );
                $stale = true;
            }

            // Optional ?symbols=EURUSD,XAUUSD filter
            $param = $req->get_param( 'symbols' );
            if ( $param ) {
                $want = array_filter( array_map( 'trim', explode( ',', strtoupper( $param ) ) ) );
                $feed = array_values( array_filter( $feed, function ( $r ) use ( $want ) {
                    return in_array( $r['code'], $want, true );
                } ) );
            }

            return new WP_REST_Response( [
                'ok'         => ! empty( $feed ),
                'stale'      => $stale,
                'count'      => count( $feed ),
                'updated_at' => (int) get_option( 'stb_prices_last_ok_v2', 0 ),
                'prices'     => $feed,
            ], 200, [
                'Cache-Control'                => 'public, max-age=30',
                'Access-Control-Allow-Origin'  => '*',
            ] );
        },
    ] );
} );

// ─── Admin "warm-up" trigger (run once after activation) ───────────────────

add_action( 'init', function () {
    if ( ! get_transient( 'stb_live_prices_v2' ) && ! get_option( 'stb_lp_warm_attempted' ) ) {
        update_option( 'stb_lp_warm_attempted', time(), false );
        wp_schedule_single_event( time() + 5, 'stb_lp_cron' );
    }
} );
