<?php
declare(strict_types=1);
ini_set('display_errors','0');ini_set('log_errors','0');error_reporting(E_ALL);
set_error_handler(function($severity){throw new ErrorException('Sanitized PHP warning',0,$severity);});
$stage='start';$n=0;$queryHashes=[];$executions=0;$cases=[];
function recSame($e,$a): void {global $n;++$n;if($e!==$a){throw new RuntimeException('Recovery assertion '.$n);}}
function recReject(callable $f): void {$ok=false;try{$f();}catch(InvalidArgumentException|UnexpectedValueException $e){$ok=true;}recSame(true,$ok);}
function expectedRow(array $values,array $names,int $mode)
{
    if($mode===PDO::FETCH_NUM){return $values;}
    $out=[];foreach($names as $i=>$name){if($mode===PDO::FETCH_BOTH){$out[$i]=$values[$i];}$out[$name]=$values[$i];}
    return $mode===PDO::FETCH_OBJ?(object)$out:$out;
}
function sameRow($e,$a): void
{if(is_object($e)){recSame(stdClass::class,is_object($a)?get_class($a):gettype($a));recSame(get_object_vars($e),get_object_vars($a));}else{recSame($e,$a);}}
function bindExecute(PDOStatement $s,array $values): void
{global $executions;foreach($values as $i=>$v){$s->bindValue($i+1,$v,$v===null?PDO::PARAM_NULL:PDO::PARAM_STR);}recSame(true,$s->execute());++$executions;}
function outcome(callable $f): array
{try{return ['kind'=>'value','value'=>$f()];}catch(Throwable $e){return ['kind'=>'error','class'=>get_class($e),'code'=>(string)$e->getCode()];}}
try{
    require __DIR__.'/../old-driver-shadow-fetch-prototype/ProbeRuntime.php';require __DIR__.'/IteratorShadowStatement.php';require __DIR__.'/../old-driver-shadow-fetch-prototype/SyntheticFixtures.php';
    $stage='runtime_identity';$identity=runtimeIdentity();$signatures=internalSignatures();$fixtures=syntheticFixtures();$rejections=syntheticRejections();$plans=array_merge(array_column($fixtures,'plan'),array_values($rejections));
    $registry=new ShadowRegistry($plans);$pdo=probePdo(IteratorShadowStatement::class,[$registry,PDO::FETCH_ASSOC]);$native=probePdo();
    $stage='mapped_components';$identity['mapped']=mappedComponents($identity);
    $stage='attributes_target';$attributes=effectiveAttributes($pdo);$target=independentTarget($pdo);$nativeTarget=independentTarget($native);
    $prepare=function(string $key)use($pdo,$fixtures){global $queryHashes;$sql=$fixtures[$key]['plan']->sql();$queryHashes[]=hash('sha256',$sql);$s=$pdo->prepare($sql);recSame(IteratorShadowStatement::class,get_class($s));return $s;};
    $modes=[PDO::FETCH_NUM,PDO::FETCH_ASSOC,PDO::FETCH_BOTH,PDO::FETCH_OBJ];
    foreach($fixtures as $key=>$fixture){if(!isset($fixture['cases'])){continue;}
        foreach($fixture['cases'] as $i=>$pair){
            $stage='numeric_'.$key.'_'.$i;$start=$n;$s=$prepare($key);
            foreach($modes as $mode){
                bindExecute($s,[$pair[0]]);sameRow(expectedRow([$pair[1]],['value'],$mode),$s->fetch($mode));recSame(false,$s->fetch($mode));
                bindExecute($s,[$pair[0]]);$rows=$s->fetchAll($mode);recSame(1,count($rows));sameRow(expectedRow([$pair[1]],['value'],$mode),$rows[0]);recSame([],$s->fetchAll($mode));
            }
            bindExecute($s,[$pair[0]]);recSame($pair[1],$s->fetchColumn());recSame(false,$s->fetchColumn());
            bindExecute($s,[$pair[0]]);recSame([$pair[1]],$s->fetchAll(PDO::FETCH_COLUMN));
            bindExecute($s,[$pair[0]]);recSame(1,$s->columnCount());recSame('value',$s->getColumnMeta(0)['name']);recReject(function()use($s){$s->getColumnMeta(1);});
            recSame(['value'=>$pair[1]],$s->fetch());
            $cases[]=['case'=>$stage,'assertions'=>$n-$start,'status'=>'pass'];
        }
    }
    $stage='two_rows_all_modes';$start=$n;$s=$prepare('pair');$values=['9007199254740993.01',null];$expected=[[1,'9007199254740993.01'],[2,null]];
    foreach($modes as $mode){
        bindExecute($s,$values);foreach($expected as $e){sameRow(expectedRow($e,['k','v'],$mode),$s->fetch($mode));}recSame(false,$s->fetch($mode));
        bindExecute($s,$values);$rows=$s->fetchAll($mode);recSame(2,count($rows));foreach($expected as $i=>$e){sameRow(expectedRow($e,['k','v'],$mode),$rows[$i]);}
    }
    foreach([0=>[1,2],1=>['9007199254740993.01',null]] as $i=>$e){bindExecute($s,$values);recSame($e,$s->fetchAll(PDO::FETCH_COLUMN,$i));bindExecute($s,$values);foreach($e as $v){recSame($v,$s->fetchColumn($i));}recSame(false,$s->fetchColumn($i));}
    bindExecute($s,$values);recSame([1=>'9007199254740993.01',2=>null],$s->fetchAll(PDO::FETCH_KEY_PAIR));
    foreach($modes as $mode){bindExecute($s,$values);recSame(true,$s->setFetchMode($mode));sameRow(expectedRow($expected[0],['k','v'],$mode),$s->fetch());}
    $cases[]=['case'=>$stage,'assertions'=>$n-$start,'status'=>'pass'];
    $stage='key_pair_native_key_semantics';$start=$n;$s=$prepare('text_pair');
    foreach([[['007','1.20','8',null],['007'=>'1.20',8=>null]],[['dup','1.20','dup','2.30'],['dup'=>'2.30']]] as $case){bindExecute($s,$case[0]);recSame($case[1],$s->fetchAll(PDO::FETCH_KEY_PAIR));}
    $cases[]=['case'=>$stage,'assertions'=>$n-$start,'status'=>'pass'];
    $stage='empty';$start=$n;$s=$prepare('empty');foreach($modes as $mode){bindExecute($s,$values);recSame([],$s->fetchAll($mode));recSame(false,$s->fetch($mode));}
    bindExecute($s,$values);recSame([],$s->fetchAll(PDO::FETCH_COLUMN,1));recSame(false,$s->fetchColumn(1));bindExecute($s,$values);recSame([],$s->fetchAll(PDO::FETCH_KEY_PAIR));
    $cases[]=['case'=>$stage,'assertions'=>$n-$start,'status'=>'pass'];
    $stage='mixed_untouched_columns';$start=$n;$s=$prepare('mixed');bindExecute($s,['9007199254740993.01']);
    recSame(['i'=>7,'v'=>'9007199254740993.01','f'=>1.25,'txt'=>'007','n'=>null],$s->fetch());
    $raw=$native->prepare($fixtures['mixed']['plan']->sql());bindExecute($raw,['9007199254740993.01']);$rawRow=$raw->fetch();
    foreach(['i'=>7,'f'=>1.25,'txt'=>'007','n'=>null] as $k=>$v){recSame($v,$rawRow[$k]);}
    $cases[]=['case'=>$stage,'assertions'=>$n-$start,'status'=>'pass'];
    $stage='statement_lifecycle';$start=$n;$s=$prepare('decimal30_2');$other=$prepare('decimal30_2');
    bindExecute($s,['9007199254740993.01']);bindExecute($other,['-1.20']);recSame('-1.20',$other->fetchColumn());recSame('9007199254740993.01',$s->fetchColumn());
    recSame(true,$s->closeCursor());$bound='1.20';$s->bindParam(1,$bound,PDO::PARAM_STR);recSame(true,$s->execute());++$executions;recSame('1.20',$s->fetchColumn());
    $bound='9007199254740993.01';recSame(true,$s->execute());++$executions;recSame('9007199254740993.01',$s->fetchColumn());
    $bound=null;recSame(true,$s->execute());++$executions;recSame(null,$s->fetchColumn());recSame(false,$s->fetchColumn());
    $cases[]=['case'=>$stage,'assertions'=>$n-$start,'status'=>'pass'];
    $stage='unsupported_modes_do_not_consume';$start=$n;$s=$prepare('pair');
    $bad=[
        function()use($s){$s->fetch(PDO::FETCH_CLASS);},function()use($s){$s->fetch(PDO::FETCH_LAZY);},function()use($s){$s->fetch(PDO::FETCH_BOUND);},
        function()use($s){$s->fetch(PDO::FETCH_ASSOC,PDO::FETCH_ORI_PRIOR);},function()use($s){$s->fetchAll(PDO::FETCH_NAMED);},
        function()use($s){$s->fetchAll(PDO::FETCH_GROUP|PDO::FETCH_ASSOC);},function()use($s){$s->fetchAll(PDO::FETCH_UNIQUE|PDO::FETCH_ASSOC);},
        function()use($s){$s->fetchAll(PDO::FETCH_FUNC,'strlen');},function()use($s){$s->fetchAll(PDO::FETCH_INTO,new stdClass());},
        function()use($s){$s->fetchAll(PDO::FETCH_OBJ,'stdClass');},function()use($s){$s->fetchColumn(2);},function()use($s){$s->fetchColumn(-1);},
        function()use($s){$s->fetchObject();},function()use($s){$dummy=null;$s->bindColumn(1,$dummy);},
        function()use($s){$s->setFetchMode(PDO::FETCH_CLASS);},function()use($s){$s->setFetchMode(PDO::FETCH_COLUMN,1);},
    ];
    foreach($bad as $call){bindExecute($s,$values);recReject($call);recSame(['k'=>1,'v'=>'9007199254740993.01'],$s->fetch(PDO::FETCH_ASSOC));}
    bindExecute($s,$values);recReject(function()use($s){$s->nextRowset();});recReject(function()use($s){$s->fetch();});bindExecute($s,$values);recSame(['k'=>1,'v'=>'9007199254740993.01'],$s->fetch());
    $one=$prepare('decimal30_2');bindExecute($one,['1.20']);recReject(function()use($one){$one->fetchAll(PDO::FETCH_KEY_PAIR);});recSame('1.20',$one->fetchColumn());
    $cases[]=['case'=>$stage,'assertions'=>$n-$start,'status'=>'pass'];
    $stage='no_plan_native_passthrough';$start=$n;
    $unknownSql="SELECT 7 AS DM_SHADOW_0,'007' AS DM_SHADOW_USER FROM DUAL";
    foreach([$unknownSql,$fixtures['decimal30_2']['plan']->sql().' '] as $unknown){
        foreach($modes as $mode){$a=$native->prepare($unknown);$b=$pdo->prepare($unknown);$args=$unknown===$unknownSql?[]:['9007199254740993.01'];bindExecute($a,$args);bindExecute($b,$args);
            $ea=$a->fetch($mode);$eb=$b->fetch($mode);sameRow($ea,$eb);recSame($a->columnCount(),$b->columnCount());recSame($a->getColumnMeta(0),$b->getColumnMeta(0));}
    }
    foreach(['all','column','object','named','class','bound','next'] as $api){
        $a=$native->query($unknownSql);$b=$pdo->query($unknownSql);
        $call=function(PDOStatement $x)use($api){
            if($api==='all'){return $x->fetchAll();}if($api==='column'){return $x->fetchColumn();}
            if($api==='object'){return get_object_vars($x->fetchObject());}if($api==='named'){return $x->fetchAll(PDO::FETCH_NAMED);}
            if($api==='class'){$x->setFetchMode(PDO::FETCH_CLASS,'stdClass');return get_object_vars($x->fetch());}
            if($api==='bound'){$v=null;$x->bindColumn(1,$v,PDO::PARAM_INT);return [$x->fetch(PDO::FETCH_BOUND),$v];}
            return $x->nextRowset();
        };recSame(outcome(function()use($call,$a){return $call($a);}),outcome(function()use($call,$b){return $call($b);}));
    }
    $cases[]=['case'=>$stage,'assertions'=>$n-$start,'status'=>'pass'];
    $stage='trusted_plan_rejections';$start=$n;
    foreach($rejections as $key=>$plan){$queryHashes[]=$plan->sqlHash();$s=$pdo->prepare($plan->sql());bindExecute($s,[]);recReject(function()use($s){$s->fetch();});}
    $cases[]=['case'=>$stage,'assertions'=>$n-$start,'status'=>'pass'];
    $stage='foreach_observation';$s=$prepare('pair');bindExecute($s,$values);$iter=[];foreach($s as $row){$iter[]=$row;}
    $iterExpected=[['k'=>1,'v'=>'9007199254740993.01'],['k'=>2,'v'=>null]];
    recSame($iterExpected,$iter);$iterator=['status'=>$iter===$iterExpected?'supported_observed':'unsupported_bypass','rows'=>$iter,'expected'=>$iterExpected];
    $stage='complete';$planHashes=[];foreach($fixtures as $key=>$fixture){$planHashes[$key]=$fixture['plan']->sqlHash();}foreach($rejections as $key=>$plan){$planHashes['reject_'.$key]=$plan->sqlHash();}
    echo json_encode(['status'=>'pass','scope'=>'synthetic_fetch_apis_only','stage'=>$stage,'assertions'=>$n,'cases'=>$cases,'identity'=>$identity,'attributes'=>$attributes,'target'=>$target,'native_target'=>$nativeTarget,'signatures'=>$signatures,'plan_sql_sha256'=>$planHashes,'prepared_sql_sha256'=>$queryHashes,'execute_calls'=>$executions,'iterator'=>$iterator,'real_orm_getter_tested'=>false,'business_reads'=>0,'dml'=>0,'ddl'=>0],JSON_UNESCAPED_SLASHES|JSON_PRESERVE_ZERO_FRACTION).PHP_EOL;
}catch(Throwable $e){echo json_encode(['status'=>'fail','stage'=>$stage,'assertions'=>$n,'class'=>get_class($e),'code'=>(string)$e->getCode(),'prepared_sql_sha256'=>$queryHashes,'execute_calls'=>$executions,'cases'=>$cases,'dml'=>0,'ddl'=>0]).PHP_EOL;exit(1);}
