<?php
namespace app\adminVue\controller;
use think\facade\Db;

class V2InventoryData
{
    const TABLES=['address'=>'onsite_inventory','base'=>'onsite_inventory_base','super'=>'onsite_inventory_super'];
    const FIELDS=[
        'address'=>['id','project','line','rack','location','kanban','unit_c','type','mark','highlight','super','compose','pull_line','copper','wire','status'],
        'base'=>['id','kanban','unit_c','unit_p'],
        'super'=>['id','project','kanban','children','num','unit']
    ];
    public static function connection() {Db::query('SELECT 1');$c=Db::connect()->getConnection();return [$c->getPdo(),$c->getConfig('prefix'),$c->getConfig('database')];}
    public static function quote($name) {if(!preg_match('/^[a-zA-Z0-9_]+$/D',$name))throw new \RuntimeException('表名无效');return '`'.$name.'`';}
    public static function kind($kind) {if(!is_string($kind)||!isset(self::TABLES[$kind]))throw new \InvalidArgumentException('请选择有效数据类型');return $kind;}
    public static function version($row) {ksort($row);return hash('sha256',json_encode(array_map(function($v){return $v===null?null:(string)$v;},$row)));}
    public static function lock($work) {
        list($pdo,$prefix,$db)=self::connection();$name='v2inventory:'.substr(hash('sha256',$db.$prefix),0,40);$q=$pdo->prepare('SELECT GET_LOCK(?,0)');$q->execute([$name]);
        if((int)$q->fetchColumn()!==1)throw new \InvalidArgumentException('其他盘点数据操作正在进行，请稍后重试');
        try{return $work($pdo,$prefix);}finally{$q=$pdo->prepare('SELECT RELEASE_LOCK(?)');$q->execute([$name]);}
    }
    public static function window($event,$now=null) {
        $start=(int)($event['freeze_start']??0);$end=(int)($event['freeze_end']??0);$now=$now??time();
        if(($start>0)!==($end>0)||($start&&$end<$start))throw new \InvalidArgumentException('盘点禁止更新区间配置异常，请管理员修正');
        $format=function($t){return $t?(new \DateTimeImmutable('@'.$t))->setTimezone(new \DateTimeZone('Asia/Shanghai'))->format('Y-m-d H:i:s'):'';};
        return ['locked'=>$start>0&&$now>=$start&&$now<=$end,'start'=>$format($start),'end'=>$format($end),'title'=>$event['title']??'未设置盘点事件'];
    }
    public static function policy() {return self::window(Db::name('onsite_base')->where(['id'=>260,'mid'=>260])->find()?:[]);}
    public static function guard() {$p=self::policy();if($p['locked'])throw new \InvalidArgumentException('当前禁止更新基础数据：'.$p['start'].' 至 '.$p['end'].'（'.$p['title'].'）');}
    public static function normalize($kind,$input,$owner) {
        $kind=self::kind($kind);$out=[];
        foreach(self::FIELDS[$kind] as $f){
            $v=$input[$f]??'';if(!is_scalar($v)&&$v!==null)throw new \InvalidArgumentException($f.'格式无效');$v=trim((string)$v);
            if(in_array($f,['id','project','line'],true)){if(!preg_match('/^[1-9][0-9]{0,9}$/D',$v)||(float)$v>2147483647)throw new \InvalidArgumentException($f.'须为有效正整数');$v=(int)$v;}
            elseif(in_array($f,['highlight','super','compose','pull_line','copper','wire'],true)){if(!in_array($v,['','0','1'],true))throw new \InvalidArgumentException($f.'只能为空、0或1');$v=$v===''?null:(int)$v;}
            elseif($f==='type'){if(!in_array($v,['0','1','2'],true))throw new \InvalidArgumentException('盘点方式须为0、1或2');$v=(int)$v;}
            elseif($f==='status'){if(!in_array($v,['0','1'],true))throw new \InvalidArgumentException('状态须为0或1');$v=(int)$v;}
            elseif($f==='num'){if(!preg_match('/^(0|[1-9][0-9]*)(\.[0-9]{1,2})?$/D',$v)||(float)$v>999.99)throw new \InvalidArgumentException('用量须为0至999.99，最多2位小数');}
            else {
                $max=$f==='mark'?255:($f==='unit'?10:(in_array($f,['unit_c','unit_p','children'],true)?20:($kind==='base'&&$f==='kanban'?50:100)));
                if(mb_strlen($v)>$max||strpos($v,"\0")!==false)throw new \InvalidArgumentException($f.'长度或内容无效');
                if(in_array($f,['kanban','rack','location','children'],true)&&$v==='')throw new \InvalidArgumentException($f.'不能为空');
            }
            $out[$f]=$v;
        }
        if($kind!=='base'){$out['owner']=$owner;$out['update']=time();}
        if($kind==='super'&&$out['kanban']===$out['children'])throw new \InvalidArgumentException('超级看板不能与子物料相同');return $out;
    }
    public static function fingerprint($pdo,$tables) {
        $out=[];foreach($tables as $kind=>$t){$q=$pdo->query('CHECKSUM TABLE '.self::quote($t).' EXTENDED');$out[$kind]=$q->fetch(\PDO::FETCH_ASSOC)['Checksum'];}return hash('sha256',json_encode($out));
    }
    public static function validate($pdo,$tables,$prefix,$warnings=false) {
        $a=self::quote($tables['address']);$b=self::quote($tables['base']);$s=self::quote($tables['super']);$team=self::quote($prefix.'team_base');$errors=[];
        $checks=[
            ['物料号重复',"SELECT kanban AS value FROM $b GROUP BY kanban HAVING COUNT(*)>1 LIMIT 20"],
            ['超级看板子物料重复',"SELECT CONCAT(project,':',kanban,':',children) AS value FROM $s GROUP BY project,kanban,children HAVING COUNT(*)>1 LIMIT 20"],
            ['地址项目或产线ID无效',"SELECT a.id AS value FROM $a a LEFT JOIN $team p ON p.id=a.project AND p.mid=1 LEFT JOIN $team l ON l.id=a.line AND l.mid=2 AND l.mark=a.project WHERE p.id IS NULL OR l.id IS NULL LIMIT 20"],
            ['BOM项目ID无效',"SELECT s.id AS value FROM $s s LEFT JOIN $team p ON p.id=s.project AND p.mid=1 WHERE p.id IS NULL LIMIT 20"],
            ['BOM子物料缺少基础数据或下级BOM',"SELECT DISTINCT s.id AS value FROM $s s LEFT JOIN $b b ON b.kanban=s.children LEFT JOIN $s child ON child.project=s.project AND child.kanban=s.children WHERE b.id IS NULL AND child.id IS NULL LIMIT 20"],
            ['普通地址物料不存在',"SELECT a.id AS value FROM $a a LEFT JOIN $b b ON b.kanban=a.kanban WHERE b.id IS NULL AND COALESCE(a.super,0)=0 AND COALESCE(a.compose,0)=0 AND COALESCE(a.pull_line,0)=0 AND COALESCE(a.copper,0)=0 AND COALESCE(a.wire,0)=0 LIMIT 20"],
            ['地址缺少超级看板BOM',"SELECT a.id AS value FROM $a a LEFT JOIN $s s ON s.project=a.project AND s.kanban=a.kanban WHERE a.super=1 AND s.id IS NULL LIMIT 20"],
            ['地址位置重复',"SELECT MIN(id) AS value FROM $a GROUP BY project,line,rack,location,IF(location LIKE '%批量%',kanban,'') HAVING COUNT(*)>1 LIMIT 20"]
        ];
        foreach($checks as $i=>list($message,$sql)){
            // An intranet snapshot may contain historical duplicates or incomplete BOMs.
            // Preserve them verbatim, but surface them for review; only invalid ownership blocks sync.
            if(in_array($i,[2,3],true)===$warnings)continue;
            foreach($pdo->query($sql)->fetchAll(\PDO::FETCH_COLUMN) as $v)$errors[]=$message.'（ID或键：'.$v.'）';
        }
        if($warnings)foreach($pdo->query("SELECT id FROM $s WHERE num=0 OR unit='' LIMIT 20")->fetchAll(\PDO::FETCH_COLUMN) as $id)$errors[]='BOM用量为0或单位为空（ID：'.$id.'），按原值保留';
        return $errors;
    }
    public static function indexes($pdo,$tables) {
        foreach(['address'=>'project,line,rack,location','base'=>'kanban','super'=>'project,kanban,children'] as $k=>$cols){
            $names=$pdo->query('SHOW INDEX FROM '.self::quote($tables[$k]))->fetchAll(\PDO::FETCH_ASSOC);
            if(!in_array('v2_inv_lookup',array_column($names,'Key_name'),true))$pdo->exec('ALTER TABLE '.self::quote($tables[$k]).' ADD INDEX v2_inv_lookup ('.$cols.')');
        }
    }
    public static function live($prefix) {$out=[];foreach(self::TABLES as $k=>$t)$out[$k]=$prefix.$t;return $out;}
    public static function counts($pdo,$tables) {$out=[];foreach($tables as $k=>$t)$out[$k]=(int)$pdo->query('SELECT COUNT(*) FROM '.self::quote($t))->fetchColumn();return $out;}
}
