<?php
declare(strict_types=1);
final class ConfigWriteAudit
{
    private $runtime;private $tenant;
    public function __construct(string $runtime,int $tenant){if(!in_array($tenant,[2147001421,2147001422],true))throw new RuntimeException('Audit tenant');$this->runtime=$runtime;$this->tenant=$tenant;}
    private static function parts(string $s):array{$out=[];$buf='';$quote='';$n=strlen($s);for($i=0;$i<$n;$i++){$c=$s[$i];if($quote!==''){$buf.=$c;if($c==='\\'&&$i+1<$n){$buf.=$s[++$i];continue;}if($c===$quote){if($i+1<$n&&$s[$i+1]===$quote){$buf.=$s[++$i];continue;}$quote='';}continue;}if($c==="'"||$c==='"'||$c==='`'){$quote=$c;$buf.=$c;continue;}if($c===','){$out[]=trim($buf);$buf='';continue;}$buf.=$c;}if($quote!=='')throw new RuntimeException('Unclosed audit literal');$out[]=trim($buf);return $out;}
    private static function owner(string $v):int{$v=trim($v);if(strlen($v)>1&&$v[0]==="'"&&substr($v,-1)==="'")$v=substr($v,1,-1);if(!preg_match('/^[0-9]+$/D',$v)||(int)$v<=0)throw new RuntimeException('Invalid store literal');return (int)$v;}
    private static function key(string $v):string{if(!preg_match('/^\x27(basic|examine)\x27$/D',trim($v),$m))throw new RuntimeException('Invalid controlled setting key');return $m[1];}
    private static function assignments(string $s):array{$out=[];foreach(self::parts($s) as $p){if(!preg_match('/^\s*[`"]?([a-z0-9_]+)[`"]?\s*=\s*(.*)$/iD',$p,$m))throw new RuntimeException('Unsupported assignment');$k=strtolower($m[1]);if(isset($out[$k]))throw new RuntimeException('Duplicate field');$out[$k]=$m[2];}return $out;}
    public function observe($sql):void{$sql=(string)$sql;if(!preg_match('/^\s*(INSERT|UPDATE|DELETE|REPLACE|MERGE|TRUNCATE|ALTER|DROP|CREATE)\b/i',$sql,$m))return;$op=strtoupper($m[1]);$table='unknown';$pass=false;$reason='Unsupported frozen DML';$data=[];$scope=[];
        try{
            if($op==='INSERT'&&preg_match('/^\s*INSERT\s+INTO\s+([^\s(]+)\s*\((.*?)\)\s*VALUES\s*\((.*)\)\s*;?\s*$/is',$sql,$p)){$table=strtolower(str_replace(['`','"'],'',$p[1]));$cols=self::parts($p[2]);$vals=self::parts($p[3]);if(count($cols)!==count($vals))throw new RuntimeException('Arity');foreach($cols as $i=>$col){$k=strtolower(trim($col,' `"'));if(isset($data[$k]))throw new RuntimeException('Duplicate insert field');$data[$k]=$vals[$i];}}
            elseif($op==='INSERT'&&preg_match('/^\s*INSERT\s+INTO\s+([^\s]+)\s+SET\s+(.*?)\s*;?\s*$/is',$sql,$p)){$table=strtolower(str_replace(['`','"'],'',$p[1]));$data=self::assignments($p[2]);}
            elseif($op==='UPDATE'&&preg_match('/^\s*UPDATE\s+([^\s]+)\s+SET\s+(.*?)\s+WHERE\s+(.*?)\s*;?\s*$/is',$sql,$p)){$table=strtolower(str_replace(['`','"'],'',$p[1]));$data=self::assignments($p[2]);$where=trim(str_replace(['(',')','`','"'],'',$p[3]));$atoms=preg_split('/\s+AND\s+/i',$where);foreach($atoms as $atom){if(!preg_match('/^(?:'.preg_quote($table,'/').'\.)?(key|store_id)\s*=\s*(.*?)\s*$/iD',trim($atom),$a))throw new RuntimeException('Unsupported scope atom');$k=strtolower($a[1]);$value=$k==='key'?self::key($a[2]):self::owner($a[2]);if(isset($scope[$k])&&$scope[$k]!==$value)throw new RuntimeException('Conflicting repeated scope');$scope[$k]=$value;}$pass=isset($scope['key'],$scope['store_id'])&&$scope['store_id']===$this->tenant;$reason='Every atom is exact controlled key and own store, including duplicates';}
            if(!in_array($table,['yoshop_h5_setting','yoshop_wxapp_setting'],true))throw new RuntimeException('Forbidden table');foreach(array_keys($data) as $key)if(!in_array($key,['key','describe','values','store_id','update_time'],true))throw new RuntimeException('Unexpected field');$allowed=$table==='yoshop_h5_setting'?['basic']:['basic','examine'];
            if($op==='INSERT'){$pass=isset($data['store_id'],$data['key'])&&self::owner($data['store_id'])===$this->tenant&&in_array(self::key($data['key']),$allowed,true);$reason='Explicit own unique setting tuple';}
            if($op==='UPDATE'){$pass=$pass&&in_array($scope['key'],$allowed,true);if(isset($data['key']))$pass=$pass&&self::key($data['key'])===$scope['key'];if(isset($data['store_id']))$pass=$pass&&self::owner($data['store_id'])===$this->tenant;}
        }catch(Throwable $e){$pass=false;$reason=$e->getMessage();}
        file_put_contents($this->runtime.'/private-write-sql.jsonl',json_encode(['sql'=>$sql,'pass'=>$pass])."\n",FILE_APPEND|LOCK_EX);file_put_contents($this->runtime.'/write-audit.jsonl',json_encode(['operation'=>$op,'table'=>$table,'tenant'=>$this->tenant,'owned_scope_verified'=>$pass,'reason'=>$reason])."\n",FILE_APPEND|LOCK_EX);if(!$pass)throw new RuntimeException('Frozen setting audit rejected '.$op.' '.$table.' '.$reason);
    }
}
