<?php
declare(strict_types=1);
function cpPdo(bool $store=false): PDO
{
    $prefix=$store?'POC_':'POC_AI_';$dm=getenv('POC_DIALECT')==='dm';
    if($dm&&(getenv($prefix.'HOST')!=='127.0.0.1'||getenv($prefix.'PORT')!==($store?'5237':'5238')||getenv($prefix.'SCHEMA')!==($store?'ZHCT_APP':'AIZHCT_APP'))){throw new RuntimeException('Nonproduction target gate');}
    $dsn=$dm?'dm:host=127.0.0.1;port='.getenv($prefix.'PORT').';schema='.getenv($prefix.'SCHEMA'):'mysql:host='.getenv($prefix.'HOST').';port='.getenv($prefix.'PORT').';dbname='.getenv($prefix.'SCHEMA').';charset='.(getenv($prefix.'CHARSET')?:'utf8');
    $options=[PDO::ATTR_ERRMODE=>PDO::ERRMODE_EXCEPTION,PDO::ATTR_CASE=>PDO::CASE_LOWER];
    // MySQL standalone fixture/readback must use the application's existing native-prepare types.
    if(!$dm){$options[PDO::ATTR_STRINGIFY_FETCHES]=false;$options[PDO::ATTR_EMULATE_PREPARES]=false;}
    $pdo=new PDO($dsn,getenv($prefix.'USER'),getenv($prefix.'PASSWORD'),$options);
    $schema=$pdo->query($dm?"SELECT SYS_CONTEXT('USERENV','CURRENT_SCHEMA') FROM DUAL":'SELECT DATABASE()')->fetchColumn();if($schema!==getenv($prefix.'SCHEMA')){throw new RuntimeException('Effective schema gate');}
    return $pdo;
}
function cpRows(PDO $pdo,string $sql,array $args=[]): array
{$s=$pdo->prepare($sql);$s->execute($args);$out=$s->fetchAll(PDO::FETCH_ASSOC);foreach($out as &$r){$r=array_change_key_case($r,CASE_LOWER);foreach($r as &$v){if(is_resource($v)){$v=stream_get_contents($v);}}unset($v);}unset($r);return $out;}
function cpDigest(array $rows): array
{$parts=[];foreach($rows as $r){ksort($r);$json=json_encode($r,JSON_UNESCAPED_UNICODE|JSON_PRESERVE_ZERO_FRACTION|JSON_THROW_ON_ERROR);$parts[]=hash('sha256',$json);}sort($parts);return ['rows'=>count($rows),'sha256'=>hash('sha256',implode("\n",$parts))];}
function cpActors(PDO $ai,PDO $store,string $lane): array
{
    $nick=$lane==='B'?'CDXPARMOB426CA4B0':'CDXPARMOB9411C2CB';$actors=cpRows($ai,'SELECT * FROM yoshop_user WHERE nick_name=? AND store_id=10001 AND is_delete=0',[$nick]);
    if(count($actors)!==1||(int)$actors[0]['user_id']<=0){throw new RuntimeException('Actor uniqueness gate');}$id=(int)$actors[0]['user_id'];
    $mapping=cpRows($store,'SELECT * FROM ydy_user WHERE ai_user_id=? AND is_delete=0',[$id]);if(count($mapping)!==1||!isset($mapping[0]['password_version'])){throw new RuntimeException('Store mapping uniqueness/version gate');}
    $merchant=cpRows($ai,'SELECT * FROM yoshop_store WHERE store_id=10001');if(count($merchant)!==1||!empty($merchant[0]['is_delete'])||!empty($merchant[0]['is_recycle'])){throw new RuntimeException('Merchant gate');}
    return ['actor_id'=>$id,'password_version'=>(int)$mapping[0]['password_version'],'actor_hash'=>cpDigest($actors),'store_mapping_hash'=>cpDigest($mapping),'merchant_hash'=>cpDigest($merchant)];
}
function cpTableSnapshot(PDO $pdo): array
{$out=[];foreach(['coupon','user_coupon'] as $t){$out[$t]=cpDigest(cpRows($pdo,'SELECT * FROM yoshop_'.$t));}return $out;}
function cpBootstrap(string $root,string $runtime): \think\App
{
    if(!defined('TP_ENV')){define('TP_ENV','poc');define('BUSINESS','aizhct');define('ENTERPRISE_NO','10001');}
    $loader=require $root.'/vendor/autoload.php';$loader->setPsr4('app\\',$root.'/app');$loader->setPsr4('cores\\',$root.'/cores');require_once __DIR__.'/external_stubs.php';
    $app=new \think\App($root);$app->setEnvName('poc');$app->setRuntimePath($runtime.'/runtime/');$app->initialize();$app->debug(false);$app->env->set('APP_DEBUG',false);\think\App::$store_id=10001;
    if(realpath(root_path())!==realpath($root)){throw new RuntimeException('Actual new private root gate');}
    $app->config->set(['default'=>'file','stores'=>['file'=>['type'=>'File','path'=>$runtime.'/cache/','prefix'=>'','expire'=>0]]],'cache');
    $app->config->set(['close'=>true,'default'=>'file','channels'=>['file'=>['type'=>'FileFormat','path'=>$runtime.'/logs/','close'=>true]]],'log');
    $app->db->setConfig($app->config);return $app;
}
