<?php
namespace app\mobile\controller;

use think\facade\Db;
use think\facade\Log;

/** 访客申请复用原表、顺序审批及门岗状态2，不搬迁历史数据。 */
class OmsVisitor extends OmsBase
{
    public function messageItems($account,$since,\app\mobile\service\MessageInbox $inbox) {
        if(!$inbox->allowsSource($this))$this->fail('接口不存在',404);
        if(!(int)$account['admin_id'])return [];
        $aid=(int)$account['admin_id'];
        $handled=Db::name('common_visitor_step')->where('aid',$aid)->whereIn('status',[0,2])->where('update','>=',$since)->column('s_id');
        $rows=Db::name('common_visitor_list')->where(function($q)use($aid,$since,$handled){
            $q->where(['status'=>1,'aid'=>$aid])->whereOr(function($own)use($aid,$since){$own->where('owner',$aid)->whereIn('status',[-1,0,2])->where('create_date','>=',$since);});
            if($handled)$q->whereOr('id','in',$handled);
        })->select()->toArray();$items=[];
        $this->preparePresentation($rows);
        foreach($rows as $row){
            $p=$this->present($row,$account);$fields=['申请人'=>$p['ownerName'],'来访单位'=>$p['visitor'],'人数'=>$p['num'].'人','访问日期'=>$p['start'].' 至 '.$p['end'],'来访事由'=>$p['reason']];$route='/pages/visitor/detail?id='.$row['id'];
            $steps=$this->steps($row);$arrival=(int)$row['create_date'];$latest=$arrival;
            foreach($steps as $step){
                $node='approve-'.(int)$step['order'];$latest=max($latest,(int)$step['update']);
                if((int)$step['aid']===$aid && (int)$step['update']>0 && in_array((int)$step['status'],[0,2],true))$items[]=\app\mobile\service\MessageInbox::item('visitor',$row['id'],$node,'访客审批已处理',(int)$step['status']===2?'已通过':'已驳回',$fields,$route,'done',$step['update']);
                if((int)$step['order']<(int)$row['step_now'])$arrival=max($arrival,(int)$step['update']);
            }
            if($p['canApprove'])$items[]=\app\mobile\service\MessageInbox::item('visitor',$row['id'],'approve-'.$row['step_now'],'访客待审批','第'.$row['step_now'].'步待审批',$fields,$route,'pending',$arrival);
            if((int)$row['owner']===$aid && in_array((int)$row['status'],[-1,0,2],true))$items[]=\app\mobile\service\MessageInbox::item('visitor',$row['id'],'result-'.$row['status'],'访客申请'.$p['statusName'],$p['statusName'],$fields+['驳回原因'=>$p['rejectReason']],$route,'notice',$latest);
        }return $items;
    }
    public static function adminFor($account) {
        return (int)($account['admin_id'] ?? 0)>0 ? Db::name('admin_user')->where(['id'=>$account['admin_id'],'status'=>1,'username'=>$account['username']])->where('job','<>',79)->find() : null;
    }
    private function admin($account) { $admin=self::adminFor($account); if (!$admin) $this->fail('访客管理需要有效的后台人员身份',403); return $admin; }
    private function result($data=null) { return json(['code'=>0,'message'=>'成功','data'=>$data]); }
    private function integer($value,$label) {
        if (!is_scalar($value) || !preg_match('/^[1-9][0-9]{0,9}$/',(string)$value) || (float)$value>2147483647) $this->fail($label.'不正确');
        return (int)$value;
    }
    private function text($value,$max,$label,$optional=false) {
        if (!is_string($value) || mb_strlen($value)>$max || (!$optional && trim($value)==='')) $this->fail($label.'不正确');
        return trim($value);
    }
    private function date($value) {
        if (!is_string($value)) $this->fail('日期不正确');
        $date=\DateTimeImmutable::createFromFormat('!Y-m-d',$value,new \DateTimeZone('Asia/Shanghai'));
        if (!$date || $date->format('Y-m-d')!==$value || $date->getTimestamp()<0 || $date->getTimestamp()>2147483647-86399) $this->fail('日期不正确');
        return $date->getTimestamp();
    }
    private $presentation=null;
    private function preparePresentation($rows) {
        $steps=[];$ids=[];
        if($rows)foreach(Db::name('common_visitor_step')->whereIn('s_id',array_column($rows,'id'))->order('order,id')->select()->toArray() as $step){$steps[$step['s_id']][]=$step;$ids[]=$step['aid'];}
        foreach($rows as $row)$ids=array_merge($ids,[$row['owner'],$row['aid']],explode(',',(string)$row['receive']));
        $this->presentation=['steps'=>$steps,'names'=>$ids?Db::name('admin_user')->whereIn('id',array_unique($ids))->column('truename','id'):[],
            'types'=>Db::name('onsite_base')->where('mid',718)->column('title','mark'),
            'departments'=>$rows?Db::name('admin_org')->whereIn('id',array_column($rows,'dpt'))->column('title','id'):[]];
    }
    private function steps($row) { if($this->presentation!==null)return $this->presentation['steps'][$row['id']]??[];return Db::name('common_visitor_step')->where('s_id',$row['id'])->order('order,id')->select()->toArray(); }
    private function validChain($row,$steps) {
        $now=(int)$row['step_now'];
        if (!$steps || count($steps)!==(int)$row['step'] || $now<1 || $now>count($steps)) return false;
        foreach ($steps as $i=>$step) {
            if ((int)$step['order']!==$i+1 || (int)$step['aid']<1 || (int)$step['status']!==($i+1<$now?2:1)) return false;
            if ($i+1===$now && (int)$step['aid']!==(int)$row['aid']) return false;
        }
        return true;
    }
    private function permissions($row,$account,$steps) {
        $pending=(int)$row['status']===1;
        return ['canApprove'=>$pending && (int)$row['aid']===(int)$account['admin_id'] && $this->validChain($row,$steps),
            'canWithdraw'=>$pending && (int)$row['owner']===(int)$account['admin_id'],
            'chainWarning'=>$pending && !$this->validChain($row,$steps)?'审批步骤存在异常，请联系管理员核对后处理。':''];
    }
    private function row($account,$id) {
        $row=Db::name('common_visitor_list')->find($this->integer($id,'申请编号'));
        if (!$row) $this->fail('申请不存在',404);
        $steps=$this->steps($row); $related=array_merge([(int)$row['owner']],array_map('intval',explode(',',(string)$row['receive'])),array_map('intval',array_column($steps,'aid')));
        if (!in_array((int)$account['admin_id'],$related,true)) $this->fail('无权查看此申请',403);
        return $row;
    }
    private function present($row,$account) {
        $steps=$this->steps($row);$receivers=array_filter(array_map('intval',explode(',',(string)$row['receive'])));
        $ids=array_unique(array_merge($receivers,[(int)$row['owner'],(int)$row['aid']],array_column($steps,'aid')));
        $names=$this->presentation!==null?$this->presentation['names']:Db::name('admin_user')->whereIn('id',$ids)->column('truename','id');
        $types=$this->presentation!==null?$this->presentation['types']:Db::name('onsite_base')->where('mid',718)->column('title','mark');
        $date=function($time,$format='Y-m-d H:i') { return (int)$time>0?date($format,(int)$time):''; };
        $status=(int)$row['status'];$chain=[];$rejectReason='';
        foreach ($steps as $step) {
            $state=(int)$step['status'];$updated=(int)$step['update'];$order=(int)$step['order'];
            $label=$state===2?'已通过':($state===0?($updated>0 && trim((string)$step['mark'])!==''?'已驳回':'历史状态待核对'):($status===1 && $order===(int)$row['step_now']?'待审批':($status===1?'待流转':'未处理')));
            if ($state===0 && trim((string)$step['mark'])!=='') $rejectReason=(string)$step['mark'];
            $chain[]=['id'=>(int)$step['id'],'order'=>$order,'adminId'=>(int)$step['aid'],'name'=>$names[$step['aid']] ?? '人员已变更','status'=>$state,'statusName'=>$label,'time'=>$date($updated),'reason'=>(string)$step['mark']];
        }
        $arrivals=[];foreach(explode(',',(string)$row['remark']) as $time) if (ctype_digit($time) && (int)$time>0) $arrivals[]=$date($time);
        return array_merge(['id'=>(int)$row['id'],'type'=>(int)$row['type'],'typeName'=>$types[$row['type']] ?? '历史类型','status'=>$status,'statusName'=>[-1=>'已撤回',0=>'已驳回',1=>'审批中',2=>'已通过'][$status] ?? '历史状态',
            'ownerId'=>(int)$row['owner'],'ownerName'=>$names[$row['owner']] ?? '人员已变更','department'=>($this->presentation!==null?($this->presentation['departments'][$row['dpt']]??''):Db::name('admin_org')->where('id',$row['dpt'])->value('title')) ?: '',
            'start'=>$date($row['start'],'Y-m-d'),'end'=>$date($row['end'],'Y-m-d'),'createdAt'=>$date($row['create_date']),
            'reason'=>htmlspecialchars_decode((string)$row['reason'],ENT_QUOTES),'visitor'=>(string)$row['visitor'],'num'=>(int)$row['num'],'room'=>(string)$row['room'],'mark'=>(string)$row['mark'],
            'receiverNames'=>array_map(function($id) use($names){return $names[$id] ?? '人员已变更';},array_values($receivers)),
            'currentApprover'=>$status===1?($names[$row['aid']] ?? '人员已变更'):'','stepNow'=>(int)$row['step_now'],'steps'=>$chain,'rejectReason'=>$rejectReason,'arrivals'=>$arrivals],$this->permissions($row,$account,$steps));
    }
    public function options() {
        return $this->guarded(function() {
            $this->requireMethod('GET');$this->admin($this->current());$departments=Db::name('admin_org')->column('title','id');$people=[];
            foreach(Db::name('admin_user')->where('status',1)->field('id,truename,username,dpt,job')->order('job,id')->select()->toArray() as $person) $people[]=['id'=>(int)$person['id'],'name'=>$person['truename'],'departmentName'=>$departments[$person['dpt']] ?? '未设置部门','positionName'=>$departments[$person['job']] ?? '未设置职位','employeeNo'=>(string)$person['username'],'label'=>($departments[$person['dpt']] ?? '未设置部门').' · '.$person['truename'].' · '.$person['username']];
            $types=[];foreach(Db::name('onsite_base')->where('mid',718)->field('title,mark')->select()->toArray() as $type) $types[]=['id'=>(int)$type['mark'],'name'=>$type['title']];
            return $this->result(['types'=>$types,'people'=>$people]);
        });
    }
    public function records() {
        return $this->guarded(function() {
            $this->requireMethod('GET');$account=$this->current();$this->admin($account);$page=$this->integer($this->request->get('page',1),'页码');
            $scope=$this->request->get('scope','mine');$query=Db::name('common_visitor_list');
            if ($scope==='mine') $query->where('owner',$account['admin_id']);
            elseif ($scope==='pending') $query->where(['status'=>1,'aid'=>$account['admin_id']]);
            elseif ($scope==='processed' || $scope==='approve') {
                $ids=Db::name('common_visitor_step')->where('aid',$account['admin_id'])->where('update','>',0)->column('s_id');
                if ($scope==='processed') $query->whereIn('id',$ids ?: [0]);
                else $query->where(function($q) use($account,$ids) {
                    $q->where(function($pending) use($account){$pending->where(['status'=>1,'aid'=>$account['admin_id']]);})->whereOr('id','in',$ids ?: [0]);
                });
            } else $this->fail('列表类型不正确');
            $status=$this->request->get('status','all');
            if ($status!=='all') {
                if (!is_scalar($status)) $this->fail('状态不正确');
                $statuses=explode(',',(string)$status);
                if (!$statuses || count($statuses)>4 || array_diff($statuses,['-1','0','1','2'])) $this->fail('状态不正确');
                $query->whereIn('status',array_map('intval',$statuses));
            }
            $start=$this->request->get('start','');$end=$this->request->get('end','');
            if ($start!=='' || $end!=='') { $from=$this->date($start);$to=$this->date($end)+86399;if($from>$to)$this->fail('结束日期不能早于开始日期');$query->where('start','<=',$to)->where('end','>=',$from); }
            $total=(clone $query)->count();$items=$query->order('id desc')->page($page,10)->select()->toArray();
            return $this->result(['items'=>$this->summaries($items),'total'=>$total,'page'=>$page]);
        });
    }
    private function summaries($rows) {
        if (!$rows) return [];
        // 列表只读取卡片所需字段，姓名与类型批量查询；审批链只在详情核验。
        $ids=array_unique(array_merge(array_column($rows,'owner'),array_column($rows,'aid')));
        $names=Db::name('admin_user')->whereIn('id',$ids)->column('truename','id');
        $types=Db::name('onsite_base')->where('mid',718)->column('title','mark');
        return array_map(function($row) use($names,$types) {
            $status=(int)$row['status'];$arrivals=[];
            foreach(explode(',',(string)$row['remark']) as $time) if(ctype_digit($time) && (int)$time>0)$arrivals[]=date('Y-m-d H:i',(int)$time);
            return ['id'=>(int)$row['id'],'typeName'=>$types[$row['type']] ?? '历史类型','status'=>$status,'statusName'=>[-1=>'已撤回',0=>'已驳回',1=>'审批中',2=>'已通过'][$status] ?? '历史状态',
                'ownerName'=>$names[$row['owner']] ?? '人员已变更','reason'=>htmlspecialchars_decode((string)$row['reason'],ENT_QUOTES),'num'=>(int)$row['num'],
                'start'=>(int)$row['start']>0?date('Y-m-d',(int)$row['start']):'','end'=>(int)$row['end']>0?date('Y-m-d',(int)$row['end']):'',
                'createdAt'=>(int)$row['create_date']>0?date('Y-m-d H:i',(int)$row['create_date']):'','currentApprover'=>$status===1?($names[$row['aid']] ?? '人员已变更'):'','arrivals'=>$arrivals];
        },$rows);
    }
    public function detail() { return $this->guarded(function() { $this->requireMethod('GET');$account=$this->current();$this->admin($account);return $this->result($this->present($this->row($account,$this->request->get('id')),$account)); }); }
    private function writeLock($action) {
        // ponytail: 低频访客操作共用表锁，覆盖旧连接写入；MyISAM补偿无法替代崩溃回滚。
        $prefix=$this->app->config->get('database.connections.'.$this->app->config->get('database.default').'.prefix');
        if (!preg_match('/^[a-zA-Z0-9_]*$/',$prefix)) throw new \RuntimeException('Invalid prefix');
        Db::query('SELECT 1');$connection=Db::connect()->getConnection();$pdo=$connection->getPdo();
        $pdo->exec('LOCK TABLES `'.$prefix.'common_visitor_list` WRITE, `'.$prefix.'common_visitor_step` WRITE, `'.$prefix.'admin_user` READ, `'.$prefix.'onsite_base` READ');
        try { return $action(); } finally { $connection->free();$pdo->exec('UNLOCK TABLES'); }
    }
    private function personIds($raw,$label) {
        if (!is_array($raw) || !$raw || count($raw)>100) $this->fail('请选择1至100位'.$label);
        $ids=array_map(function($id) use($label){return $this->integer($id,$label);},$raw);
        if (count(array_unique($ids))!==count($ids)) $this->fail($label.'不能重复');
        return $ids;
    }
    public function create() {
        return $this->guarded(function() {
            $this->requireMethod('POST');$account=$this->current();$this->admin($account);
            $submission=$this->text($this->request->post('requestId'),64,'提交标识');if(!preg_match('/^[a-zA-Z0-9_-]{16,64}$/',$submission))$this->fail('提交标识不正确');
            $type=$this->integer($this->request->post('type'),'来访类型');$start=$this->date($this->request->post('start'));$end=$this->date($this->request->post('end'))+86399;if($start>$end)$this->fail('结束日期不能早于开始日期');
            $receivers=$this->personIds($this->request->post('receiverIds'),'接待人');$approvers=$this->personIds($this->request->post('approverIds'),'审批人');$receive=implode(',',$receivers);if(strlen($receive)>255)$this->fail('接待人员过多');
            $data=['type'=>$type,'start'=>$start,'end'=>$end,'reason'=>$this->text($this->request->post('reason'),255,'来访原因'),'visitor'=>$this->text($this->request->post('visitor'),255,'访客名单'),'num'=>$this->integer($this->request->post('num'),'访客人数'),'receive'=>$receive,'room'=>$this->text($this->request->post('room',''),50,'会议室',true),'mark'=>$this->text($this->request->post('mark',''),500,'备注',true)];
            $key='visitor:'.substr(hash('sha256',$this->cacheKey($account['username'])),0,48);$lock=Db::query('SELECT GET_LOCK(?,5) acquired',[$key]);if((int)$lock[0]['acquired']!==1)$this->fail('提交处理中，请稍后重试',409);
            try {
                $receiptKey=$this->cacheKey('visitor:receipt:'.hash('sha256',$account['username'].':'.$submission));$fingerprint=hash('sha256',json_encode([$data,$approvers]));$receipt=$this->cache()->get($receiptKey);
                if ($receipt) {if($receipt['fingerprint']!==$fingerprint)$this->fail('提交标识已用于其他内容',409);return $this->result(['id'=>$receipt['id']]);}
                $row=$this->writeLock(function() use($account,$data,$receivers,$approvers) {
                    $admin=$this->admin($account);if(!Db::name('onsite_base')->where(['mid'=>718,'mark'=>$data['type']])->find())$this->fail('来访类型已变更，请刷新');
                    $people=array_unique(array_merge($receivers,$approvers));if(Db::name('admin_user')->whereIn('id',$people)->where('status',1)->count()!==count($people))$this->fail('接待人或审批人已停用，请重新选择');
                    $row=array_merge($data,['owner'=>(int)$account['admin_id'],'dpt'=>$admin['dpt'],'create_date'=>time(),'status'=>1,'step'=>count($approvers),'step_now'=>1,'aid'=>$approvers[0],'remark'=>'']);
                    $id=Db::name('common_visitor_list')->insertGetId($row);
                    try {foreach($approvers as $i=>$aid)Db::name('common_visitor_step')->insert(['s_id'=>$id,'order'=>$i+1,'aid'=>$aid,'status'=>1,'update'=>0,'mark'=>'']);}
                    catch(\Throwable $error) {Db::name('common_visitor_step')->where('s_id',$id)->delete();Db::name('common_visitor_list')->where('id',$id)->delete();throw $error;}
                    $row['id']=$id;return $row;
                });
                try {$this->cache()->set($receiptKey,['id'=>$row['id'],'fingerprint'=>$fingerprint],86400);}catch(\Throwable $error){Log::warning('oms visitor receipt unavailable');}
            } finally {Db::query('SELECT RELEASE_LOCK(?)',[$key]);}
            $this->notify($row,'pending');return $this->result(['id'=>$row['id']]);
        });
    }
    public function approve() {
        return $this->guarded(function() {
            $this->requireMethod('POST');$account=$this->current();$this->admin($account);$decision=$this->request->post('decision');if(!in_array($decision,['pass','reject'],true))$this->fail('审批操作不正确');
            $expected=$this->integer($this->request->post('stepNow'),'审批步骤');$reason=$decision==='reject'?$this->text($this->request->post('reason'),255,'驳回原因'):'';
            $row=$this->writeLock(function() use($account,$decision,$expected,$reason) {
                $this->admin($account);$row=$this->row($account,$this->request->post('id'));$steps=$this->steps($row);
                if(!$this->permissions($row,$account,$steps)['canApprove'] || (int)$row['step_now']!==$expected)$this->fail('申请状态已变化或审批步骤异常，请刷新',409);
                $current=$steps[$expected-1];$changes=$decision==='reject'?['status'=>0,'aid'=>0]:($expected===count($steps)?['status'=>2,'aid'=>0]:['status'=>1,'step_now'=>$expected+1,'aid'=>(int)$steps[$expected]['aid']]);
                if($changes['status']===1 && !Db::name('admin_user')->where(['id'=>$changes['aid'],'status'=>1])->find())$this->fail('下一级审批人已停用，请联系管理员',409);
                try {Db::name('common_visitor_step')->where('id',$current['id'])->update(['status'=>$decision==='reject'?0:2,'update'=>time(),'mark'=>$reason]);Db::name('common_visitor_list')->where('id',$row['id'])->update($changes);}
                catch(\Throwable $error) {Db::name('common_visitor_step')->where('id',$current['id'])->update(['status'=>$current['status'],'update'=>$current['update'],'mark'=>$current['mark']]);throw $error;}
                return array_merge($row,$changes);
            });
            $this->notify($row,(int)$row['status']===1?'pending':($decision==='reject'?'rejected':'passed'));return $this->result();
        });
    }
    public function withdraw() {
        return $this->guarded(function() {
            $this->requireMethod('POST');$account=$this->current();$this->admin($account);
            $row=$this->writeLock(function() use($account) {
                $this->admin($account);$row=$this->row($account,$this->request->post('id'));
                if((int)$row['owner']!==(int)$account['admin_id'])$this->fail('只有申请人可以撤回',403);
                if((int)$row['status']!==1)$this->fail('只有审批中的申请可以撤回，请刷新',409);
                Db::name('common_visitor_list')->where('id',$row['id'])->update(['status'=>-1,'aid'=>0]);return array_merge($row,['status'=>-1,'aid'=>0]);
            });
            $this->notify($row,'withdrawn');return $this->result();
        });
    }
    protected function notify($row,$event) {
        try {
            $service=$this->templateService();$page='pages/visitor/detail?id='.$row['id'];
            if ($event==='pending') return $service->send('workflow_pending',$service->admins([$row['aid']]),[
                'name'=>'访客入厂审批','initiator'=>$service->adminName($row['owner']),'time'=>$service::time($row['create_date']),
                'reason'=>$row['reason'],'step'=>'第'.$row['step_now'].'级审批',
            ],$page);
            if ($event==='withdrawn') return $service->send('feedback_result',$service->admins([$row['owner']]),[
                'type'=>'访客申请已撤回','content'=>$row['reason'],'owner'=>$service->adminName($row['owner']),
                'reply'=>'申请人已撤回本次访客入厂申请','time'=>$service::time(time()),
            ],$page);
            $step=Db::name('common_visitor_step')->where('s_id',$row['id'])->where('order',$row['step_now'])->find();
            return $service->send($event==='rejected'?'workflow_rejected':'workflow_approved',$service->admins([$row['owner']]),[
                'name'=>'访客入厂审批','approver'=>$service->adminName($step['aid'] ?? 0),'number'=>(string)$row['id'],'time'=>$service::time($step['update'] ?? time()),
            ],$page);
        } catch (\Throwable $error) { Log::warning('oms visitor notification unavailable: '.$error->getMessage()); return false; }
    }
}
