<?php
/**
 * Plugin Name: STB Economic Calendar
 * Description: Fetches economic calendar events from Investing.com every 30 minutes
 *              via WP-Cron. Writes wp-content/uploads/stb-ecal.json (back-compat
 *              with existing tools.php client code) AND exposes via
 *              /wp-json/stb/v1/calendar. Pure WordPress — no system cron, no
 *              Python, no external services required. Drop the file into mu-plugins
 *              and it self-configures on activation.
 * Version: 1.0
 *
 * Replaces /opt/stb_ecal_fetch.py + system crontab "every 30 min" entry.
 */

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

// ─── Config ────────────────────────────────────────────────────────────────

// Investing.com country IDs (same set the old Python agent used)
const STB_CAL_COUNTRIES = [ '5', '22', '25', '17', '35', '14', '10', '39' ];
const STB_CAL_IMPORTANCE = [ '3', '2', '1' ];

// Output file path inside uploads/ — preserves the URL existing tools.php fetches
function stb_cal_output_path(): string {
    $upload = wp_get_upload_dir();
    return trailingslashit( $upload['basedir'] ) . 'stb-ecal.json';
}

const STB_CAL_FLAGS = [
    'USD' => '🇺🇸', 'EUR' => '🇪🇺', 'GBP' => '🇬🇧', 'JPY' => '🇯🇵',
    'CAD' => '🇨🇦', 'AUD' => '🇦🇺', 'CHF' => '🇨🇭', 'NZD' => '🇳🇿',
    'CNY' => '🇨🇳', 'SGD' => '🇸🇬', 'NOK' => '🇳🇴', 'SEK' => '🇸🇪',
    'DKK' => '🇩🇰', 'MXN' => '🇲🇽', 'ZAR' => '🇿🇦', 'TRY' => '🇹🇷',
];

// ─── Fetching ──────────────────────────────────────────────────────────────

function stb_cal_fetch_tab( string $tab ) {
    $url    = 'https://www.investing.com/economic-calendar/Service/getCalendarFilteredData';
    $params = [];
    foreach ( STB_CAL_COUNTRIES as $c ) {
        $params[] = [ 'country[]', $c ];
    }
    foreach ( STB_CAL_IMPORTANCE as $i ) {
        $params[] = [ 'importance[]', $i ];
    }
    $params[] = [ 'timeZone', '18' ];
    $params[] = [ 'timeFilter', 'timeRemain' ];
    $params[] = [ 'currentTab', $tab ];
    $params[] = [ 'submitFilters', '1' ];
    $params[] = [ 'limit_from', '0' ];

    // Build x-www-form-urlencoded body that supports repeated keys
    $pairs = [];
    foreach ( $params as $p ) {
        $pairs[] = rawurlencode( $p[0] ) . '=' . rawurlencode( $p[1] );
    }
    $body = implode( '&', $pairs );

    $resp = wp_remote_post( $url, [
        'body'    => $body,
        'headers' => [
            'User-Agent'       => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
            'X-Requested-With' => 'XMLHttpRequest',
            'Referer'          => 'https://www.investing.com/economic-calendar/',
            'Accept'           => 'application/json, text/javascript, */*; q=0.01',
            'Content-Type'     => 'application/x-www-form-urlencoded',
            'Origin'           => 'https://www.investing.com',
        ],
        'timeout' => 25,
    ] );

    if ( is_wp_error( $resp ) ) {
        return [ '', '', '', $resp->get_error_message() ];
    }
    $code = wp_remote_retrieve_response_code( $resp );
    if ( $code !== 200 ) {
        return [ '', '', '', "HTTP $code" ];
    }
    $json = json_decode( wp_remote_retrieve_body( $resp ), true );
    if ( ! is_array( $json ) ) {
        return [ '', '', '', 'invalid JSON' ];
    }
    return [
        (string) ( $json['data']     ?? '' ),
        (string) ( $json['dateFrom'] ?? '' ),
        (string) ( $json['dateTo']   ?? '' ),
        null,
    ];
}

// ─── HTML parsing ──────────────────────────────────────────────────────────

function stb_cal_clean( string $s ): string {
    $s = preg_replace( '/<[^>]+>/', '', $s );
    $s = str_replace(
        [ '&nbsp;', '&amp;', '&lt;', '&gt;', '&#039;' ],
        [ '', '&', '<', '>', "'" ],
        $s
    );
    return trim( preg_replace( '/\s+/', ' ', $s ) );
}

function stb_cal_impact( string $row ): string {
    $n = substr_count( $row, 'grayFullBullishIcon' );
    if ( $n >= 3 || strpos( $row, 'bull3' ) !== false ) return 'high';
    if ( $n === 2 || strpos( $row, 'bull2' ) !== false ) return 'medium';
    return 'low';
}

function stb_cal_direction( string $actual, string $forecast ): string {
    $a = (float) preg_replace( '/[^\d.\-]/', '', $actual );
    $f = (float) preg_replace( '/[^\d.\-]/', '', $forecast );
    if ( ! is_numeric( $a ) || ! is_numeric( $f ) ) return '';
    if ( $actual === '' || $forecast === '' ) return '';
    if ( $a > $f ) return 'up';
    if ( $a < $f ) return 'down';
    return '';
}

function stb_cal_parse( string $html ): array {
    $events = [];
    $day    = '';

    // Split into rows just like the Python re.split(r'(?=<tr[\s>])', html)
    $rows = preg_split( '/(?=<tr[\s>])/', $html );

    foreach ( $rows as $row ) {
        // Day header
        if ( preg_match( '/class="theDay"[^>]*>([^<]+)</', $row, $dm ) ) {
            $day = trim( $dm[1] );
            continue;
        }
        if ( strpos( $row, 'js-event-item' ) === false ) continue;

        if ( ! preg_match( '/id="eventRowId_(\d+)"/', $row, $em ) ) continue;
        $eid = $em[1];

        $time = '';
        if ( preg_match( '/class="[^"]*js-time[^"]*"[^>]*>([^<]*)</', $row, $tm ) ) {
            $time = trim( $tm[1] );
        }

        $ccy = '';
        if ( preg_match( '/class="[^"]*flagCur[^"]*"[^>]*>.*?>\s*([A-Z]{3})\s*</s', $row, $cm ) ) {
            $ccy = trim( $cm[1] );
        }

        $title = '';
        if ( preg_match( '/class="left event"[^>]*>.*?<a[^>]*>\s*(.*?)\s*<\/a>/s', $row, $ttm ) ) {
            $title = stb_cal_clean( $ttm[1] );
        } elseif ( preg_match( '/data-name\s*=\s*"([^"]+)"/', $row, $dn ) ) {
            $title = trim( $dn[1] );
        }
        if ( $title === '' || $ccy === '' ) continue;

        $actual = $forecast = $previous = '';
        if ( preg_match( '/id="eventActual_'   . $eid . '"[^>]*>(.*?)<\/td>/s',          $row, $am ) ) $actual   = stb_cal_clean( $am[1] );
        if ( preg_match( '/id="eventForecast_' . $eid . '"[^>]*>(.*?)<\/(?:td|span)>/s', $row, $fm ) ) $forecast = stb_cal_clean( $fm[1] );
        if ( preg_match( '/id="eventPrevious_' . $eid . '"[^>]*>(.*?)<\/td>/s',          $row, $pm ) ) $previous = stb_cal_clean( $pm[1] );

        $dt = '';
        if ( preg_match( '/data-event-datetime="([^"]+)"/', $row, $dtm ) ) {
            $dt = $dtm[1];
        }

        $events[] = [
            'id'       => $eid,
            'day'      => $day,
            'datetime' => $dt,
            'time'     => $time,
            'ccy'      => $ccy,
            'flag'     => STB_CAL_FLAGS[ $ccy ] ?? '🌐',
            'event'    => $title,
            'impact'   => stb_cal_impact( $row ),
            'previous' => $previous,
            'forecast' => $forecast,
            'actual'   => $actual,
            'dir'      => stb_cal_direction( $actual, $forecast ),
        ];
    }
    return $events;
}

// ─── Refresh & write ───────────────────────────────────────────────────────

function stb_cal_refresh(): void {
    $started   = microtime( true );
    $all       = [];
    $date_from = '';
    $date_to   = '';
    $errors    = [];

    foreach ( [ 'thisWeek', 'nextWeek' ] as $tab ) {
        list( $html, $df, $dt, $err ) = stb_cal_fetch_tab( $tab );
        if ( $err ) {
            $errors[] = "$tab: $err";
            continue;
        }
        $evs = stb_cal_parse( $html );
        if ( ! empty( $evs ) ) {
            $all = array_merge( $all, $evs );
            if ( $date_from === '' ) $date_from = $df;
            $date_to = $dt;
        }
    }

    if ( empty( $all ) ) {
        update_option( 'stb_cal_last_error', implode( ' | ', $errors ) ?: 'no events parsed', false );
        return; // keep last good cache
    }

    // Dedup by id
    $seen   = [];
    $unique = [];
    foreach ( $all as $e ) {
        if ( ! isset( $seen[ $e['id'] ] ) ) {
            $seen[ $e['id'] ] = true;
            $unique[] = $e;
        }
    }

    $out = [
        'fetched_at' => gmdate( 'c' ),
        'date_from'  => $date_from,
        'date_to'    => $date_to,
        'total'      => count( $unique ),
        'events'     => $unique,
    ];

    // 1. Persist in WP transient + option (REST + durable fallback)
    set_transient( 'stb_calendar_v1', $out, 7200 ); // 2h TTL
    update_option( 'stb_cal_last_feed', $out, false );
    update_option( 'stb_cal_last_ok',   time(), false );

    // 2. ALSO write the static file for back-compat with tools.php client code,
    //    which already fetches /wp-content/uploads/stb-ecal.json directly.
    $path = stb_cal_output_path();
    $dir  = dirname( $path );
    if ( ! is_dir( $dir ) ) {
        wp_mkdir_p( $dir );
    }
    if ( is_writable( $dir ) ) {
        $tmp = $path . '.tmp';
        $encoded = wp_json_encode( $out, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT );
        if ( $encoded !== false && file_put_contents( $tmp, $encoded ) !== false ) {
            @rename( $tmp, $path );
        }
    }

    update_option( 'stb_cal_last_count',   count( $unique ), false );
    update_option( 'stb_cal_last_runtime', round( microtime( true ) - $started, 2 ), false );
}

// ─── WP-Cron: every 30 minutes ─────────────────────────────────────────────

add_filter( 'cron_schedules', function ( $s ) {
    if ( ! isset( $s['stb_30min'] ) ) {
        $s['stb_30min'] = [ 'interval' => 1800, 'display' => 'Every 30 minutes' ];
    }
    return $s;
} );

add_action( 'init', function () {
    if ( ! wp_next_scheduled( 'stb_cal_cron' ) ) {
        wp_schedule_event( time() + 30, 'stb_30min', 'stb_cal_cron' );
    }
} );
add_action( 'stb_cal_cron', 'stb_cal_refresh' );

// First-run warm-up: if no cache yet, schedule a one-shot in 10s
add_action( 'init', function () {
    if ( ! get_transient( 'stb_calendar_v1' )
         && ! get_option( 'stb_cal_warm_attempted' ) ) {
        update_option( 'stb_cal_warm_attempted', time(), false );
        wp_schedule_single_event( time() + 10, 'stb_cal_cron' );
    }
} );

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

add_action( 'rest_api_init', function () {
    register_rest_route( 'stb/v1', '/calendar', [
        'methods'             => 'GET',
        'permission_callback' => '__return_true',
        'callback'            => function ( $req ) {
            $data  = get_transient( 'stb_calendar_v1' );
            $stale = false;
            if ( empty( $data ) ) {
                stb_cal_refresh();
                $data = get_transient( 'stb_calendar_v1' );
            }
            if ( empty( $data ) ) {
                $data  = get_option( 'stb_cal_last_feed', [] );
                $stale = ! empty( $data );
            }

            $resp = is_array( $data ) ? $data : [
                'fetched_at' => '', 'date_from' => '', 'date_to' => '',
                'total' => 0, 'events' => [],
            ];
            $resp['stale']      = $stale;
            $resp['source']     = 'Investing.com';
            $resp['updated_at'] = (int) get_option( 'stb_cal_last_ok', 0 );

            // Optional ?impact=high or ?ccy=USD,EUR filters
            $impact_param = $req->get_param( 'impact' );
            $ccy_param    = $req->get_param( 'ccy' );
            if ( $impact_param || $ccy_param ) {
                $want_impacts = $impact_param ? array_map( 'trim', explode( ',', strtolower( $impact_param ) ) ) : null;
                $want_ccy     = $ccy_param ? array_map( 'trim', explode( ',', strtoupper( $ccy_param ) ) ) : null;
                $filtered = [];
                foreach ( $resp['events'] as $e ) {
                    if ( $want_impacts && ! in_array( $e['impact'], $want_impacts, true ) ) continue;
                    if ( $want_ccy && ! in_array( $e['ccy'], $want_ccy, true ) ) continue;
                    $filtered[] = $e;
                }
                $resp['events'] = $filtered;
                $resp['total']  = count( $filtered );
            }

            return new WP_REST_Response( $resp, 200, [
                'Cache-Control'               => 'public, max-age=300',
                'Access-Control-Allow-Origin' => '*',
            ] );
        },
    ] );
} );

// ─── Optional admin status (visible at wp-admin → Tools → STB Calendar) ───
// Lightweight — no settings, just a status read-out for the IT team.

add_action( 'admin_menu', function () {
    add_management_page(
        'STB Economic Calendar',
        'STB Economic Calendar',
        'manage_options',
        'stb-calendar-status',
        function () {
            $last_ok    = (int) get_option( 'stb_cal_last_ok', 0 );
            $last_count = (int) get_option( 'stb_cal_last_count', 0 );
            $last_err   = (string) get_option( 'stb_cal_last_error', '' );
            $runtime    = (float) get_option( 'stb_cal_last_runtime', 0 );
            $next       = wp_next_scheduled( 'stb_cal_cron' );
            $path       = stb_cal_output_path();
            $file_ok    = file_exists( $path );
            $file_age   = $file_ok ? ( time() - filemtime( $path ) ) : null;

            echo '<div class="wrap"><h1>STB Economic Calendar</h1>';
            echo '<table class="widefat striped" style="max-width:640px"><tbody>';
            printf(
                '<tr><th>Last successful fetch</th><td>%s</td></tr>',
                $last_ok ? esc_html( gmdate( 'Y-m-d H:i:s', $last_ok ) ) . ' UTC <em>(' . esc_html( human_time_diff( $last_ok ) ) . ' ago)</em>' : '<em>never</em>'
            );
            printf( '<tr><th>Events in last fetch</th><td>%d</td></tr>', $last_count );
            printf( '<tr><th>Last fetch duration</th><td>%s s</td></tr>', esc_html( $runtime ) );
            printf( '<tr><th>Next scheduled run</th><td>%s</td></tr>',
                $next ? esc_html( gmdate( 'Y-m-d H:i:s', $next ) ) . ' UTC' : '<em>not scheduled</em>'
            );
            printf( '<tr><th>JSON file</th><td>%s%s</td></tr>',
                esc_html( $path ),
                $file_ok ? ' <em>(' . human_time_diff( time() - $file_age ) . ' old)</em>' : ' <strong style="color:#c8102e">missing</strong>'
            );
            printf( '<tr><th>REST endpoint</th><td><code>%s</code></td></tr>', esc_url( rest_url( 'stb/v1/calendar' ) ) );
            if ( $last_err ) {
                printf( '<tr><th>Last error</th><td><code>%s</code></td></tr>', esc_html( $last_err ) );
            }
            echo '</tbody></table>';
            echo '<p style="margin-top:18px"><a class="button button-primary" href="' . esc_url( admin_url( 'tools.php?page=stb-calendar-status&run=1&_wpnonce=' . wp_create_nonce( 'stb_cal_run' ) ) ) . '">Run fetch now</a></p>';
            echo '</div>';

            if ( ! empty( $_GET['run'] ) && check_admin_referer( 'stb_cal_run' ) ) {
                stb_cal_refresh();
                echo '<div class="notice notice-success is-dismissible"><p>Fetch executed.</p></div>';
            }
        }
    );
} );
