<?php
// Standalone check for the slow-cleanup warning: php -d zend.assertions=1 tests/slow-hook-warning.php
// No WordPress, no framework. The warning's whole value is that it blames the right thing, so the
// decision rule is what this pins — called directly, because the cases worth testing are minutes
// long and nothing here should sleep for them.

function __( $s, $d = null ) { return $s; }
function is_wp_error( $t ) { return false; }

class FakeWpdb { public $num_queries = 0; public $last_error = ''; }
$wpdb = new FakeWpdb();
require __DIR__ . '/../classes/core.php';

class Probe extends Meow_WPMC_Core {
	public $lines = array();
	function log( $data = null, $force = false ) { $this->lines[] = (string) $data; }
	function arm() {
		foreach ( array( 'debug_logs' => true, 'slow_hook_warned' => false ) as $k => $v ) {
			$p = new ReflectionProperty( 'Meow_WPMC_Core', $k );
			$p->setAccessible( true );
			$p->setValue( $this, $v );
		}
	}
	public function warned() {
		foreach ( $this->lines as $line ) if ( strpos( $line, '⚠' ) === 0 ) return $line;
		return null;
	}
}

$judge = new ReflectionMethod( 'Meow_WPMC_Core', 'warn_if_hooks_are_slow' );
$judge->setAccessible( true );

function verdict( $judge, $elapsed, $queries, $label = 'wp_delete_attachment' ) {
	$p = ( new ReflectionClass( 'Probe' ) )->newInstanceWithoutConstructor();
	$p->arm();
	$judge->invoke( $p, $label, $elapsed, $queries );
	return $p->warned();
}

// The case that started this: 94 queries against two minutes. No database is slow enough for that
// to be query time, so it has to read as code running between the queries.
$w = verdict( $judge, 128.0, 94 );
assert( $w !== null, 'two minutes for one file warns' );
assert( strpos( $w, 'another plugin' ) !== false, 'and names the cause: ' . $w );
assert( strpos( $w, 'not Media Cleaner' ) !== false, 'and clears Media Cleaner explicitly' );
assert( strpos( $w, 'Deactivate' ) !== false, 'and says what to do about it' );
assert( strpos( $w, '94' ) !== false, 'quoting the evidence it judged on' );

// Hundreds of queries for one file explains the time by itself, however fast each one is, and must
// not be blamed on a plugin's network calls.
$w = verdict( $judge, 5.2, 600 );
assert( $w !== null && strpos( $w, 'into the database' ) !== false, 'query volume is named as such: ' . $w );
assert( strpos( $w, 'another plugin' ) === false, 'and no plugin is accused of it' );

// A genuinely slow database — few queries, but each one crawling — is also not a plugin's fault.
$w = verdict( $judge, 6.0, 80 );
assert( strpos( $w, 'into the database' ) !== false, '75ms a query reads as the database: ' . $w );

// A normal delete says nothing. A warning that fires on healthy sites is noise nobody reads.
assert( verdict( $judge, 0.05, 12 ) === null, 'a fast delete warns about nothing' );
assert( verdict( $judge, 1.0, 40 ) === null, 'and neither does a merely unremarkable one' );
assert( verdict( $judge, 4.9, 5 ) === null, 'the threshold holds just under it' );

// Said once per request: the explanation is identical for every file in the batch.
$p = ( new ReflectionClass( 'Probe' ) )->newInstanceWithoutConstructor();
$p->arm();
for ( $i = 0; $i < 5; $i++ ) $judge->invoke( $p, 'wp_delete_attachment', 60.0, 90 );
$count = 0;
foreach ( $p->lines as $line ) if ( strpos( $line, '⚠' ) === 0 ) $count++;
assert( $count === 1, 'the warning is said once, not once per file: ' . $count );

// And the wiring: only a step declared as running third-party code can raise it.
$p = ( new ReflectionClass( 'Probe' ) )->newInstanceWithoutConstructor();
$p->arm();
$p->timed( 'forget_stale_trash_rows', function () { usleep( 60000 ); return true; } );
assert( $p->warned() === null, 'an unflagged step stays silent' );
assert( count( $p->lines ) > 0, 'though it is still announced before it runs' );
assert( strpos( $p->lines[0], '→ forget_stale_trash_rows' ) === 0, 'by name: ' . $p->lines[0] );

echo "ok\n";
