<?php
// Execute the production client-check flow with only `new Redis()` replaced.
// FakeRedis never opens sockets; each child retains original version guards.
if($argc!==2||PHP_VERSION!=='7.3.32'||phpversion('redis')!=='5.3.7'){
 fwrite(STDERR,"Use PHP7.3.32 + phpredis5.3.7: php mock-client-regression.php /path/scripts/check-php-client.php\n");exit(2);
}
$source=file_get_contents($argv[1]);
if($source===false||substr($source,0,5)!=='<?php'||substr_count($source,'new Redis()')!==1){fwrite(STDERR,"Unexpected source form\n");exit(2);}
$sourceHash=hash('sha256',$source);
$body=str_replace('new Redis()','new FakeRedis()',substr($source,5));
$mock= <<<'MOCK'
class FakeRedis {
 public static $events=[];
 private $db=0;
 private $data=[];
 private function event($op,$key=null){
  $e=['op'=>$op,'db'=>$this->db];
  if($key!==null){$e['key_hash']=hash('sha256',$key);$e['reserved_prefix']=preg_match('/^__zhct_installcheck:[a-f0-9]{24}$/',$key)===1;}
  self::$events[]=$e;
 }
 public function connect($host,$port,$timeout){if($host!=='127.0.0.1'||$port!==6379)throw new RuntimeException('Unexpected production check endpoint');$this->event('fake_connect_no_network');return true;}
 public function info($section){$this->event('info');return ['redis_version'=>MOCK_CASE==='wrong_version'?'0.0.0':'6.2.7','config_file'=>MOCK_CASE==='wrong_config'?'/wrong/redis.conf':'/opt/zhct-redis627/etc/redis.conf'];}
 public function select($db){if(!in_array($db,[8,9],true))throw new RuntimeException('Unexpected logical database');$this->db=$db;$this->event('select');return true;}
 public function setex($key,$ttl,$value){$this->event('setex',$key);if($this->db===9&&MOCK_CASE==='db9_write_false')return false;$this->data[$this->db][$key]=$value;return true;}
 public function get($key){$this->event('get',$key);if($this->db===9&&MOCK_CASE==='db9_wrong_read')return 'wrong-test-value';return $this->data[$this->db][$key]??false;}
 public function ttl($key){$this->event('ttl',$key);return $this->db===9&&MOCK_CASE==='db9_bad_ttl'?-1:59;}
 public function exists($key){$this->event('exists',$key);return isset($this->data[$this->db][$key])?1:0;}
 public function del($key){$this->event('del',$key);$had=isset($this->data[$this->db][$key]);unset($this->data[$this->db][$key]);return $had?1:0;}
 public function close(){$this->event('close');return true;}
 public function __call($name,$args){$this->event('forbidden_'.$name);throw new RuntimeException('Unexpected Redis operation');}
}
register_shutdown_function(function(){file_put_contents(MOCK_AUDIT,json_encode(['events'=>FakeRedis::$events]));});
MOCK;
$folder=sys_get_temp_dir().'/zhct-redis-mock-'.bin2hex(random_bytes(12));
if(!mkdir($folder,0700))throw new RuntimeException('Cannot create private mock directory');
$report=['passed'=>true,'source_sha256'=>$sourceHash,'production_file_unchanged'=>true,'network_used'=>false,'php'=>PHP_VERSION,'phpredis'=>phpversion('redis'),'cases'=>[]];
try{
 foreach(['normal','wrong_config','wrong_version','db9_write_false','db9_wrong_read','db9_bad_ttl']as$case){
  $program=$folder.'/'.$case.'.php';$audit=$folder.'/'.$case.'.audit.json';
  file_put_contents($program,"<?php\ndefine('MOCK_CASE',".var_export($case,true).");\ndefine('MOCK_AUDIT',".var_export($audit,true).");\n".$mock."\n".$body);
  chmod($program,0600);
  $cmd=escapeshellarg(PHP_BINARY).' -c '.escapeshellarg(php_ini_loaded_file()).' '.escapeshellarg($program);
  $pipes=[];$p=proc_open($cmd,[0=>['pipe','r'],1=>['pipe','w'],2=>['pipe','w']],$pipes);
  if(!is_resource($p))throw new RuntimeException('Child creation failed');fclose($pipes[0]);$stdout=stream_get_contents($pipes[1]);$stderr=stream_get_contents($pipes[2]);fclose($pipes[1]);fclose($pipes[2]);$exit=proc_close($p);
  $events=json_decode(file_get_contents($audit),true)['events'];$out=json_decode(trim($stdout),true);
  $writes=array_values(array_filter($events,function($e){return $e['op']==='setex';}));$deletes=array_values(array_filter($events,function($e){return $e['op']==='del';}));
  $badOps=array_filter($events,function($e){return strpos($e['op'],'forbidden_')===0;});
  $ok=!$badOps;
  if($case==='normal'){
   $keys=array_values(array_unique(array_column($writes,'key_hash')));$ok=$ok&&$exit===0&&($out['passed']??false)===true&&count($writes)===2&&array_column($writes,'db')===[8,9]&&count($keys)===1&&count($deletes)===2&&array_column($deletes,'db')===[8,9];
   foreach($deletes as$d)$ok=$ok&&$d['reserved_prefix']===true&&in_array($d['key_hash'],$keys,true);
   foreach([8,9]as$db){$gets=array_filter($events,function($e)use($db){return $e['op']==='get'&&$e['db']===$db;});$ttls=array_filter($events,function($e)use($db){return $e['op']==='ttl'&&$e['db']===$db;});$ok=$ok&&count($gets)>0&&count($ttls)>0;}
  }elseif(in_array($case,['wrong_config','wrong_version'],true))$ok=$ok&&$exit!==0&&count($writes)===0&&count($deletes)===0&&($out['passed']??false)!==true;
  else{
   $ok=$ok&&$exit!==0&&($out['passed']??false)!==true&&count($deletes)===2;
   $keys=array_values(array_unique(array_column($writes,'key_hash')));
   foreach($deletes as$d)$ok=$ok&&$d['reserved_prefix']===true&&in_array($d['key_hash'],$keys,true);
  }
  $report['cases'][]=['case'=>$case,'passed'=>$ok,'child_exit'=>$exit,'write_attempts'=>count($writes),'delete_attempts'=>count($deletes),'reported_success'=>($out['passed']??false)===true,'network_used'=>false];
  if(!$ok)$report['passed']=false;
  unlink($program);unlink($audit);
 }
 if(hash_file('sha256',$argv[1])!==$sourceHash)throw new RuntimeException('Source changed during tests');
}finally{
 foreach(glob($folder.'/*')as$f){if(is_file($f))unlink($f);}rmdir($folder);
}
echo json_encode($report,JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT),"\n";exit($report['passed']?0:1);
