<?php
// phpcs:disable WordPress.Security.NonceVerification.Missing, WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange
if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class YATOON_Performance {
    const BASELINE_OPTION = 'yatoon_performance_baseline_454';
    const SCHEMA_VERSION = '3';
    private static $started_at = 0;
    private static $action = '';
    private static $query_count_start = 0;
    private static $remote_started = array();
    private static $remote_ms = 0;
    private static $remote_provider = 'none';

    public static function init() {
        self::install_schema();
        add_action( 'init', array( __CLASS__, 'begin_request' ), 1 );
        add_filter( 'http_request_args', array( __CLASS__, 'begin_remote_request' ), 1, 2 );
        add_action( 'http_api_debug', array( __CLASS__, 'finish_remote_request' ), 10, 5 );
        add_action( 'admin_menu', array( __CLASS__, 'add_menu' ), 23 );
        add_action( 'wp_ajax_yatoon_run_performance_baseline', array( __CLASS__, 'run_baseline' ) );
        add_action( 'yatoon_cleanup_performance', array( __CLASS__, 'cleanup' ) );
        if ( ! wp_next_scheduled( 'yatoon_cleanup_performance' ) ) {
            wp_schedule_event( time() + DAY_IN_SECONDS, 'daily', 'yatoon_cleanup_performance' );
        }
    }

    /**
     * Run a private, read-only baseline from the real WordPress environment.
     * No customer rows, credentials, or remote payment calls are included.
     */
    public static function run_baseline() {
        if ( ! current_user_can( 'manage_options' ) ) {
            wp_send_json_error( array( 'message' => __( 'You do not have permission to run this benchmark.', 'yatoon-booking-system' ) ), 403 );
        }
        check_ajax_referer( 'yatoon_performance_baseline', 'nonce' );

        global $wpdb;
        $started = microtime( true );
        $queries_before = function_exists( 'get_num_queries' ) ? get_num_queries() : 0;
        $memory_before = memory_get_usage( true );

        $service_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}yatoon_services WHERE is_active=1" );
        $staff_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}yatoon_staff WHERE is_active=1 AND is_bookable=1" );
        $booking_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}yatoon_bookings WHERE status<>'cancelled'" );

        $asset_paths = array(
            YATOON_PLUGIN_DIR . 'assets/css/yatoon-booking-bundle.min.css',
            YATOON_PLUGIN_DIR . 'assets/js/yatoon-booking-bundle.min.js',
        );
        $asset_bytes = 0;
        foreach ( $asset_paths as $asset_path ) {
            if ( is_readable( $asset_path ) ) {
                $asset_bytes += (int) filesize( $asset_path );
            }
        }

        $snapshot = array(
            'recorded_at'       => current_time( 'mysql' ),
            'runtime_profile'   => class_exists( 'YATOON_Feature_Modules' ) ? YATOON_Feature_Modules::current_profile() : 'custom',
            'server_ms'         => round( ( microtime( true ) - $started ) * 1000, 2 ),
            'query_count'       => max( 0, ( function_exists( 'get_num_queries' ) ? get_num_queries() : 0 ) - $queries_before ),
            'memory_delta_mb'   => round( max( 0, memory_get_usage( true ) - $memory_before ) / 1048576, 2 ),
            'peak_memory_mb'    => round( memory_get_peak_usage( true ) / 1048576, 2 ),
            'frontend_bytes'    => $asset_bytes,
            'active_services'   => $service_count,
            'bookable_staff'    => $staff_count,
            'active_bookings'   => $booking_count,
        );
        update_option( self::BASELINE_OPTION, $snapshot, false );
        wp_send_json_success( $snapshot );
    }

    private static function install_schema() {
        global $wpdb;
        if ( get_option( 'yatoon_performance_schema', '' ) === self::SCHEMA_VERSION ) { return;
        }
        require_once ABSPATH . 'wp-admin/includes/upgrade.php';
        $charset = $wpdb->get_charset_collate();
        dbDelta( "CREATE TABLE {$wpdb->prefix}yatoon_performance (
            id bigint(20) NOT NULL AUTO_INCREMENT,
            action_name varchar(80) NOT NULL,
            duration_ms int(11) NOT NULL,
            memory_mb decimal(8,2) DEFAULT 0,
            response_code int(11) DEFAULT 200,
            query_count int(11) DEFAULT 0,
            remote_ms int(11) DEFAULT 0,
            remote_provider varchar(32) DEFAULT 'none',
            payload_kb decimal(10,2) DEFAULT 0,
            bottleneck varchar(32) DEFAULT 'application',
            diagnostic_version tinyint(3) DEFAULT 0,
            created_at datetime DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (id),
            KEY action_created (action_name,created_at)
        ) {$charset};" );
        if ( YATOON_Schema_Verifier::tables_exist( array( 'yatoon_performance' ) ) ) {
            update_option( 'yatoon_performance_schema', self::SCHEMA_VERSION, false );
        }
    }

    public static function begin_request() {
        $action = isset( $_REQUEST['action'] ) ? sanitize_key( wp_unslash( $_REQUEST['action'] ) ) : '';
        $tracked = array( 'yatoon_create_booking', 'yatoon_upload_design_reference', 'yatoon_get_available_times', 'yatoon_get_available_staff', 'yatoon_get_service_options' );
        if ( ! in_array( $action, $tracked, true ) ) { return;
        }
        self::$started_at = microtime( true );
        self::$action = $action;
        self::$query_count_start = function_exists( 'get_num_queries' ) ? get_num_queries() : 0;
        self::$remote_started = array();
        self::$remote_ms = 0;
        self::$remote_provider = 'none';
        register_shutdown_function( array( __CLASS__, 'finish_request' ) );
    }

    public static function begin_remote_request( $args, $url ) {
        if ( self::$started_at && self::$action ) {
            self::$remote_started[ md5( (string) $url ) ] = microtime( true );
        }
        return $args;
    }

    /**
     * Capture timing and provider only. URLs, bodies, customer data and
     * credentials are deliberately never stored.
     */
    public static function finish_remote_request( $response, $context, $class, $parsed_args, $url ) {
        if ( ! self::$started_at || ! self::$action || 'response' !== $context ) { return;
        }
        $key = md5( (string) $url );
        if ( isset( self::$remote_started[ $key ] ) ) {
            self::$remote_ms += (int) round( ( microtime( true ) - self::$remote_started[ $key ] ) * 1000 );
            unset( self::$remote_started[ $key ] );
        }
        $host = strtolower( (string) wp_parse_url( (string) $url, PHP_URL_HOST ) );
        foreach ( array( 'square' => 'squareup', 'stripe' => 'stripe', 'paypal' => 'paypal', 'google' => 'googleapis', 'twilio' => 'twilio', 'vagaro' => 'vagaro', 'microsoft' => 'microsoft' ) as $provider => $needle ) {
            if ( false !== strpos( $host, $needle ) ) {
                self::$remote_provider = $provider;
                return;
            }
        }
        if ( $host ) { self::$remote_provider = 'other';
        }
    }

    public static function finish_request() {
        if ( ! self::$started_at || ! self::$action ) { return;
        }
        global $wpdb;
        $duration_ms = (int) round( ( microtime( true ) - self::$started_at ) * 1000 );
        $query_count = max( 0, ( function_exists( 'get_num_queries' ) ? get_num_queries() : 0 ) - self::$query_count_start );
        $payload_kb = isset( $_SERVER['CONTENT_LENGTH'] ) ? round( absint( $_SERVER['CONTENT_LENGTH'] ) / 1024, 2 ) : 0;
        $response_code = http_response_code() ?: 200;
        $bottleneck = 'application';
        if ( self::$remote_ms >= 250 && self::$remote_ms >= (int) round( $duration_ms * 0.5 ) ) {
            $bottleneck = 'remote-' . self::$remote_provider;
        } elseif ( $query_count >= 25 ) {
            $bottleneck = 'database';
        } elseif ( 'yatoon_upload_design_reference' === self::$action && $payload_kb >= 512 ) {
            $bottleneck = 'upload';
        } elseif ( $response_code >= 400 ) {
            $bottleneck = 'error-response';
        }
        $wpdb->insert(
            $wpdb->prefix . 'yatoon_performance',
            array(
                'action_name' => self::$action,
                'duration_ms' => $duration_ms,
                'memory_mb' => round( memory_get_peak_usage( true ) / 1048576, 2 ),
                'response_code' => $response_code,
                'query_count' => $query_count,
                'remote_ms' => self::$remote_ms,
                'remote_provider' => self::$remote_provider,
                'payload_kb' => $payload_kb,
                'bottleneck' => $bottleneck,
                'diagnostic_version' => 2,
                'created_at' => current_time( 'mysql', true ),
            ),
            array( '%s', '%d', '%f', '%d', '%d', '%d', '%s', '%f', '%s', '%d', '%s' )
        );
    }

    private static function percentiles( $action_name ) {
        global $wpdb;
        $values = $wpdb->get_col(
            $wpdb->prepare(
                "SELECT duration_ms FROM {$wpdb->prefix}yatoon_performance WHERE action_name=%s AND created_at >= UTC_TIMESTAMP() - INTERVAL 30 DAY ORDER BY duration_ms ASC LIMIT 10000",
                $action_name
            )
        );
        $count = count( $values );
        if ( ! $count ) { return array( 0, 0, 0 );
        }
        $at = static function( $ratio ) use ( $values, $count ) {
            return (int) $values[ min( $count - 1, max( 0, (int) ceil( $count * $ratio ) - 1 ) ) ];
        };
        return array( $at( 0.50 ), $at( 0.95 ), $at( 0.99 ) );
    }

    public static function cleanup() {
        global $wpdb;
        $wpdb->query(
            "DELETE FROM {$wpdb->prefix}yatoon_performance
             WHERE created_at < UTC_TIMESTAMP() - INTERVAL 90 DAY"
        );
    }

    public static function add_menu() {
        add_submenu_page( 'yatoon-booking-system', 'Performance', 'Performance', 'manage_options', 'yatoon-performance', array( __CLASS__, 'render' ) );
    }

    public static function render() {
        global $wpdb;
        $stats = $wpdb->get_results(
            "SELECT action_name, COUNT(*) requests, ROUND(AVG(duration_ms)) average_ms,
             MAX(duration_ms) slowest_ms,
             SUM(CASE WHEN diagnostic_version >= 2 THEN 1 ELSE 0 END) detailed_requests,
             ROUND(AVG(CASE WHEN diagnostic_version >= 2 THEN remote_ms END)) average_remote_ms,
             ROUND(AVG(CASE WHEN diagnostic_version >= 2 THEN query_count END)) average_queries,
             SUM(CASE WHEN duration_ms >= CASE WHEN action_name='yatoon_upload_design_reference' THEN 8000 ELSE 5000 END THEN 1 ELSE 0 END) slow_requests,
             ROUND(100 * SUM(CASE WHEN duration_ms >= CASE WHEN action_name='yatoon_upload_design_reference' THEN 8000 ELSE 5000 END THEN 1 ELSE 0 END) / COUNT(*), 2) slow_percent
             FROM {$wpdb->prefix}yatoon_performance
             WHERE created_at >= UTC_TIMESTAMP() - INTERVAL 30 DAY
            GROUP BY action_name ORDER BY average_ms DESC"
        );
        $bottlenecks = $wpdb->get_results(
            "SELECT action_name, bottleneck, COUNT(*) requests, ROUND(AVG(duration_ms)) average_ms
             FROM {$wpdb->prefix}yatoon_performance
             WHERE created_at >= UTC_TIMESTAMP() - INTERVAL 30 DAY AND diagnostic_version >= 2
             GROUP BY action_name, bottleneck ORDER BY action_name, requests DESC"
        );
        $jobs = $wpdb->get_results(
            "SELECT * FROM {$wpdb->prefix}yatoon_jobs WHERE status IN ('failed','retry','running') ORDER BY id DESC LIMIT 50"
        );
        $baseline = (array) get_option( self::BASELINE_OPTION, array() );
        $baseline_nonce = wp_create_nonce( 'yatoon_performance_baseline' );
        ?>
        <div class="wrap yatoon-performance-page"><h1><?php esc_html_e( 'Performance & Reliability', 'yatoon-booking-system' ); ?></h1><p><?php esc_html_e( 'Real request timings from the last 30 days. Use this page to find whether delays come from image upload, availability, or final confirmation.', 'yatoon-booking-system' ); ?></p>
        <section class="card yatoon-baseline-card"><div><h2><?php esc_html_e( 'Lean-mode baseline', 'yatoon-booking-system' ); ?></h2><p><?php esc_html_e( 'Runs a private read-only check and records PHP memory, database queries, bundled front-end bytes, server time, and browser round-trip time.', 'yatoon-booking-system' ); ?></p></div><button type="button" class="button button-primary" id="yatoon-run-baseline"><?php esc_html_e( 'Run baseline', 'yatoon-booking-system' ); ?></button><div id="yatoon-baseline-result" role="status" aria-live="polite">
        <?php if ( $baseline ) : ?>
            <strong><?php echo esc_html( $baseline['recorded_at'] ?? '' ); ?></strong> &middot;
            <?php echo esc_html( sprintf( 'Profile %s | Server %sms | Queries %d | Peak memory %sMB | Front-end %sKB', $baseline['runtime_profile'] ?? 'custom', $baseline['server_ms'] ?? '-', $baseline['query_count'] ?? 0, $baseline['peak_memory_mb'] ?? '-', round( ( $baseline['frontend_bytes'] ?? 0 ) / 1024, 1 ) ) ); ?>
        <?php else : ?><?php esc_html_e( 'No baseline recorded yet.', 'yatoon-booking-system' ); ?><?php endif; ?>
        </div></section>
        <p class="description"><?php esc_html_e( 'Operational target: fewer than 1% of booking and availability requests over 5 seconds. Image uploads use an 8-second threshold. Investigate at 1-3%; urgent above 3%. Rates remain in collecting state until 30 samples are available.', 'yatoon-booking-system' ); ?></p>
        <table class="widefat striped"><thead><tr><th>Request</th><th>Runs</th><th>Detailed samples</th><th>Average</th><th>P50</th><th>P95</th><th>P99</th><th>Slowest</th><th>Remote</th><th>Queries</th><th>Over threshold</th><th>Slow rate</th></tr></thead><tbody>
        <?php if ( ! $stats ) : ?><tr><td colspan="12">No production timings recorded yet.</td></tr><?php endif; ?>
        <?php foreach ( $stats as $row ) : list( $p50, $p95, $p99 ) = self::percentiles( $row->action_name ); $collecting = absint( $row->requests ) < 30; $tail_status = $collecting ? 'collecting' : ( floatval( $row->slow_percent ) > 3 ? 'urgent' : ( floatval( $row->slow_percent ) >= 1 ? 'watch' : 'good' ) ); ?><tr><td><?php echo esc_html( str_replace( 'yatoon_', '', $row->action_name ) ); ?></td><td><?php echo absint( $row->requests ); ?></td><td><?php echo absint( $row->detailed_requests ); ?></td><td><?php echo esc_html( number_format( $row->average_ms / 1000, 2 ) ); ?>s</td><td><?php echo esc_html( number_format( $p50 / 1000, 2 ) ); ?>s</td><td><?php echo esc_html( number_format( $p95 / 1000, 2 ) ); ?>s</td><td><?php echo esc_html( number_format( $p99 / 1000, 2 ) ); ?>s</td><td><?php echo esc_html( number_format( $row->slowest_ms / 1000, 2 ) ); ?>s</td><td><?php echo absint( $row->detailed_requests ) ? esc_html( number_format( $row->average_remote_ms / 1000, 2 ) ) . 's' : '&mdash;'; ?></td><td><?php echo absint( $row->detailed_requests ) ? absint( $row->average_queries ) : '&mdash;'; ?></td><td><?php echo absint( $row->slow_requests ); ?></td><td><span class="yatoon-tail-status <?php echo esc_attr( $tail_status ); ?>"><?php echo $collecting ? esc_html__( 'Collecting', 'yatoon-booking-system' ) : esc_html( number_format( $row->slow_percent, 2 ) ) . '%'; ?></span></td></tr><?php endforeach; ?>
        </tbody></table><h2 style="margin-top:28px">Where time is spent</h2><p class="description">Technical timing only: URLs, request bodies, response bodies, customer details, and credentials are never recorded. Categorized data begins collecting after this update.</p><table class="widefat striped"><thead><tr><th>Request</th><th>Likely bottleneck</th><th>Runs</th><th>Average</th></tr></thead><tbody><?php if ( ! $bottlenecks ) : ?><tr><td colspan="4">Collecting categorized timings from new requests.</td></tr><?php endif; ?><?php foreach ( $bottlenecks as $item ) : ?><tr><td><?php echo esc_html( str_replace( 'yatoon_', '', $item->action_name ) ); ?></td><td><?php echo esc_html( str_replace( '-', ' ', $item->bottleneck ) ); ?></td><td><?php echo absint( $item->requests ); ?></td><td><?php echo esc_html( number_format( $item->average_ms / 1000, 2 ) ); ?>s</td></tr><?php endforeach; ?></tbody></table><style>.yatoon-tail-status{display:inline-block;min-width:54px;text-align:center;padding:3px 7px;border-radius:999px;font-weight:700}.yatoon-tail-status.good{color:#166534;background:#dcfce7}.yatoon-tail-status.watch{color:#92400e;background:#fef3c7}.yatoon-tail-status.urgent{color:#991b1b;background:#fee2e2}.yatoon-tail-status.collecting{color:#334155;background:#e2e8f0}.yatoon-performance-page .widefat{display:block;max-width:100%;overflow-x:auto}.yatoon-performance-page .widefat thead,.yatoon-performance-page .widefat tbody{display:table;width:100%;table-layout:auto}</style><h2 style="margin-top:28px">Jobs needing attention</h2><table class="widefat striped"><thead><tr><th>ID</th><th>Type</th><th>Status</th><th>Attempts</th><th>Error</th><th></th></tr></thead><tbody>
        <?php if ( ! $jobs ) : ?><tr><td colspan="6">No failed or delayed background jobs.</td></tr><?php endif; ?>
        <?php foreach ( $jobs as $job ) : ?><tr><td>#<?php echo absint( $job->id ); ?></td><td><?php echo esc_html( $job->job_type ); ?></td><td><?php echo esc_html( $job->status ); ?></td><td><?php echo absint( $job->attempts ); ?></td><td><?php echo esc_html( $job->last_error ); ?></td><td><a class="button" href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=yatoon_retry_job&job_id=' . absint( $job->id ) ), 'yatoon_retry_job' ) ); ?>">Retry</a></td></tr><?php endforeach; ?>
        </tbody></table><style>.yatoon-baseline-card{max-width:none;margin:20px 0;padding:20px;display:grid;grid-template-columns:minmax(0,1fr) auto;gap:10px 20px;align-items:center}.yatoon-baseline-card h2,.yatoon-baseline-card p{margin:0 0 6px}.yatoon-baseline-card #yatoon-baseline-result{grid-column:1/-1;padding:12px;border-radius:6px;background:#f6f7f7;overflow-wrap:anywhere}@media(max-width:640px){.yatoon-baseline-card{grid-template-columns:1fr}.yatoon-baseline-card .button{width:100%}}</style><script>(function(){var button=document.getElementById('yatoon-run-baseline'),result=document.getElementById('yatoon-baseline-result');if(!button||!result)return;button.addEventListener('click',function(){var start=performance.now();button.disabled=true;result.textContent=<?php echo wp_json_encode( __( 'Running read-only benchmark…', 'yatoon-booking-system' ) ); ?>;var body=new URLSearchParams({action:'yatoon_run_performance_baseline',nonce:<?php echo wp_json_encode( $baseline_nonce ); ?>});fetch(ajaxurl,{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/x-www-form-urlencoded;charset=UTF-8'},body:body.toString()}).then(function(response){return response.json();}).then(function(payload){if(!payload.success)throw new Error(payload.data&&payload.data.message?payload.data.message:'Benchmark failed');var d=payload.data,roundTrip=(performance.now()-start).toFixed(1);result.textContent='Profile '+d.runtime_profile+' | Browser '+roundTrip+'ms | Server '+d.server_ms+'ms | Queries '+d.query_count+' | Peak memory '+d.peak_memory_mb+'MB | Front-end '+(d.frontend_bytes/1024).toFixed(1)+'KB';}).catch(function(error){result.textContent=error.message;}).finally(function(){button.disabled=false;});});}());</script></div>
        <?php
    }
}
