<?php
declare(strict_types=1);
// A deliberately limited validator for the frozen plain INSERT/primary-key UPDATE
// emitted by StaffPower. Unsupported syntax fails closed; no SQL enters public reports.
final class DbWriteAudit
{
    private $runtime;
    public function __construct(string $runtime){$this->runtime=$runtime;}
    private static function parts(string $input):array
    {
        $out=[];$buf='';$quote='';$length=strlen($input);
        for($i=0;$i<$length;$i++){$c=$input[$i];
            if($quote!==''){$buf.=$c;if($c==='\\'&&$i+1<$length){$buf.=$input[++$i];continue;}
                if($c===$quote){if($i+1<$length&&$input[$i+1]===$quote){$buf.=$input[++$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('Unsupported unclosed SQL audit literal');$out[]=trim($buf);return $out;
    }
    private static function scalar(string $v):string
    {
        $v=trim($v);if(strlen($v)>1&&$v[0]==="'"&&substr($v,-1)==="'")$v=substr($v,1,-1);
        if(!preg_match('/^-?[0-9]+(?:\.[0-9]+)?$/D',$v))throw new RuntimeException('Non-numeric owner audit literal');return $v;
    }
    private static function owner(string $literal,int $expected):bool
    {
        $v=self::scalar($literal);return preg_match('/^[0-9]+$/D',$v)===1&&(int)$v===$expected&&$expected>0;
    }
    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]);$map=json_decode((string)file_get_contents($this->runtime.'/private-owned-map.json'),true);$pass=false;$reason='Unsupported write shape';$table='unknown';
        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]);$values=self::parts($p[3]);if(count($cols)!==count($values))throw new RuntimeException('Audit arity');$data=[];
                foreach($cols as $i=>$col)$data[strtolower(trim($col,' `"'))]=$values[$i];
                $pass=in_array($table,['yoshop_meal_recommend','yoshop_meal_recommend_history','yoshop_nurtition_target','yoshop_nurtrition'],true)
                    &&isset($data['staff_id'],$data['store_id'])&&self::owner($data['staff_id'],(int)($map['staff_id']??0))
                    &&self::owner($data['store_id'],2147001021);
                $reason='Explicit inserted staff and store owner';
            }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=[];
                foreach(self::parts($p[2]) as $assignment){if(!preg_match('/^\s*[`"]?([a-z0-9_]+)[`"]?\s*=\s*(.*)$/iD',$assignment,$a))throw new RuntimeException('Unsupported insert-set assignment');$data[strtolower($a[1])]=$a[2];}
                $pass=in_array($table,['yoshop_meal_recommend','yoshop_meal_recommend_history','yoshop_nurtition_target','yoshop_nurtrition'],true)
                    &&isset($data['staff_id'],$data['store_id'])&&self::owner($data['staff_id'],(int)($map['staff_id']??0))
                    &&self::owner($data['store_id'],2147001021);
                $reason='Explicit insert-set staff and store owner';
            }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]));
                $where=trim(str_replace(['(',')','`','"'],'',$p[3]));
                if(!in_array($table,['yoshop_nurtrition','yoshop_nurtition_target','yoshop_meal_recommend'],true))throw new RuntimeException('Forbidden nutrition update table');
                if($table==='yoshop_nurtrition'&&strpos($where,'staff_id')!==false){
                    $atoms=preg_split('/\s+AND\s+/i',$where);$fields=[];
                    foreach($atoms as $atom){if(!preg_match('/^(?:yoshop_nurtrition\.)?(store_id|staff_id|user_id|set_date|is_delete)\s*=\s*(.*)$/iD',trim($atom),$a))throw new RuntimeException('Unsupported plan replacement scope');$k=strtolower($a[1]);if(isset($fields[$k]))throw new RuntimeException('Duplicate replacement scope');$fields[$k]=trim($a[2]," '");}
                    $keys=array_keys($fields);sort($keys);$pass=$keys===['is_delete','set_date','staff_id','store_id','user_id']&&(int)$fields['store_id']===2147001021&&(string)$fields['store_id']==='2147001021'
                        &&(string)$fields['staff_id']===(string)$map['staff_id']&&(string)$fields['user_id']==='2147001021'&&$fields['is_delete']==='0'&&in_array($fields['set_date'],['2030-04-02','2030-04-03','2030-04-04'],true);
                }else{
                    if(!preg_match('/^(?:'.preg_quote($table,'/').'\.)?store_id\s*=\s*\x27?2147001021\x27?\s+AND\s+id\s*=\s*(\x27?[0-9]+\x27?)\s*$/iD',$where,$w))throw new RuntimeException('Non-owned scoped primary-key update');
                    $id=(int)self::scalar($w[1]);$pass=in_array($id,$map[$table==='yoshop_meal_recommend'?'current_ids':($table==='yoshop_nurtition_target'?'target_ids':'plan_ids')]??[],true);
                }
                foreach(self::parts($p[2]) as $assignment){if(preg_match('/^\s*[`"]?id[`"]?\s*=/i',$assignment)){$pass=false;continue;}
                    if(!preg_match('/^\s*[`"]?(staff_id|store_id)[`"]?\s*=\s*(.*)$/i',$assignment,$a))continue;
                    $expected=strtolower($a[1])==='staff_id'?(int)$map['staff_id']:2147001021;$pass=$pass&&self::owner($a[2],$expected);}
                $reason='Only independently verified owned current primary key';
            }
        }catch(Throwable $e){$reason=$e->getMessage();$pass=false;}
        file_put_contents($this->runtime.'/private-dml-audit-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,'owned_scope_verified'=>$pass,'reason'=>$reason])."\n",FILE_APPEND|LOCK_EX);
        if(!$pass)throw new RuntimeException('Frozen write audit rejected '.$op.' '.$table.' '.$reason);
    }
}
