<?php
namespace app\adminVue\controller;

use think\facade\Db;
use app\mobile\service\SuggestionTransfer;

class V2Suggestion extends V2Base
{
    private function number($value,$max=2147483647)
    {
        if((!is_string($value)&&!is_int($value))||!preg_match('/^[1-9][0-9]{0,9}$/D',(string)$value)||(float)$value>$max)$this->fail('数字参数不正确');
        return (int)$value;
    }
    private function text($key,$max=100)
    {
        $v=$this->request->get($key,'');if(!is_string($v)||mb_strlen($v)>$max)$this->fail('查询参数不正确');return trim($v);
    }
    private function dateRange($required=false)
    {
        $result=[];
        foreach(['startDate','endDate']as$key){$v=$this->request->get($key,'');if($v===''){if($required)$this->fail('请选择日期区间');$result[]=null;continue;}
            if(!is_string($v))$this->fail('日期格式不正确');$d=\DateTimeImmutable::createFromFormat('!Y-m-d',$v,new \DateTimeZone('Asia/Shanghai'));
            if(!$d||$d->format('Y-m-d')!==$v||$v<'2000-01-01'||$v>'2099-12-31')$this->fail('日期格式不正确');$result[]=$d->getTimestamp();}
        if($result[0]!==null&&$result[1]!==null&&$result[0]>$result[1])$this->fail('开始日期不能晚于结束日期');return $result;
    }
    private function fingerprint($value){return hash_hmac('sha256',json_encode($value),$this->requestToken());}
    private function assignable($row){return ((int)$row['status']===1&&(int)$row['adopt']===0)||SuggestionTransfer::eligible($row);}
    private function isConfiguredAssigner()
    {
        $rows=Db::name('onsite_base')->where('mid',88)->column('content');
        return count($rows)===1&&in_array((int)$this->actor['id'],array_map('intval',explode(',',(string)$rows[0])),true);
    }
    private function formatted($v,$format='Y-m-d H:i')
    {
        return (int)$v>0?(new \DateTimeImmutable('@'.(int)$v))->setTimezone(new \DateTimeZone('Asia/Shanghai'))->format($format):'';
    }
    private function present($rows)
    {
        $names=Db::name('admin_user')->column('truename','id');$departments=Db::name('admin_org')->where('mid',0)->column('title','id');$items=[];$canAssign=$this->isConfiguredAssigner();
        foreach($rows as$row){$status=(int)$row['status'];$adopt=(int)$row['adopt'];
            $state=$status===1&&$adopt===0?'待分配':($status===2&&$adopt===0?'待反馈':($status===2&&$adopt===1?'进行中':($status===3&&$adopt===1?'待确认':($status===4?'已办结':'状态异常'))));
            $item=['id'=>(int)$row['id'],'employeeNo'=>(string)$row['eid'],'name'=>$row['ename'],'department'=>$departments[$row['dpt']]??(string)$row['dpt'],'team'=>$row['team'],
                'createdDate'=>$this->formatted($row['creat_date'],'Y-m-d'),'title'=>$row['title'],'suggestion'=>$row['suggestion'],
                'departmentId'=>(int)$row['due_dpt'],'departmentName'=>$departments[$row['due_dpt']]??'未分配','ownerId'=>(int)$row['owner'],'ownerName'=>$row['owner']?($names[$row['owner']]??'人员已变更'):'未分配',
                'adopt'=>$adopt,'adoptName'=>[0=>$status===4?'未记录':'未反馈',1=>'采纳',2=>'未采纳'][$adopt]??'结果异常','status'=>$status,'statusName'=>$state,
                'reason'=>html_entity_decode((string)$row['reason'],ENT_QUOTES,'UTF-8'),'dueDate'=>$this->formatted($row['due_date'],'Y-m-d'),
                'feedbackAt'=>$this->formatted($row['feedback_time']),'submittedAt'=>$this->formatted($row['submit_time']),'closedAt'=>$status===4?$this->formatted($row['update']):'',
                'confirmationName'=>[1=>'提报人/班组确认',2=>'责任部门确认'][(int)$row['selfshut']]??'未记录',
                'hasImage'=>trim((string)$row['img'])!=='','canAssign'=>$canAssign&&$this->assignable($row),'version'=>$this->fingerprint($row),
                'pendingTransfer'=>SuggestionTransfer::pending($row)];
            $items[]=$item;
        }
        return $items;
    }
    private function query()
    {
        $q=Db::name('onsite_suggestion_list')->where('status','>',0);[$start,$end]=$this->dateRange();
        if($start!==null)$q->where('creat_date','>=',$start);if($end!==null)$q->where('creat_date','<',$end+86400);
        $keyword=$this->text('keyword');
        if($keyword!==''){$like='%'.addcslashes($keyword,'%_\\').'%';$owners=Db::name('admin_user')->whereLike('truename',$like)->column('id');
            $q->where(function($or)use($like,$owners){$or->whereLike('eid|ename|title|suggestion',$like);if($owners)$or->whereOr('owner','in',$owners);});}
        foreach(['departmentId'=>'due_dpt','ownerId'=>'owner']as$key=>$field){$v=$this->request->get($key,'');if($v!=='')$q->where($field,$this->number($v));}
        $state=$this->text('state',20);
        if($state!==''){if(!in_array($state,['unassigned','pending','ongoing','confirm','closed'],true))$this->fail('状态不正确');
            $map=['unassigned'=>[1,0],'pending'=>[2,0],'ongoing'=>[2,1],'confirm'=>[3,1],'closed'=>[4,null]];
            $q->where('status',$map[$state][0]);if($map[$state][1]!==null)$q->where('adopt',$map[$state][1]);}
        $adopt=$this->request->get('adopt','');if($adopt!==''){if((!is_int($adopt)&&!is_string($adopt))||!in_array((string)$adopt,['0','1','2'],true))$this->fail('采纳结果不正确');$q->where('adopt',(int)$adopt);}
        return $q;
    }
    public function index()
    {
        SuggestionTransfer::acceptExpired();
        $page=$this->number($this->request->get('page',1),1000000);$limit=$this->number($this->request->get('limit',15),100);$q=$this->query();$total=(clone$q)->count();
        return json(['code'=>0,'msg'=>'','data'=>['list'=>$this->present($q->order('creat_date desc,id desc')->page($page,$limit)->select()->toArray()),'total'=>$total]]);
    }
    private function departmentOptions()
    {
        $mappings=Db::name('member_base')->where('mid',3)->field('mark,title')->order('id')->select()->toArray();$group=[];
        foreach($mappings as$m)$group[(int)$m['mark']][]=(string)$m['title'];
        $people=Db::name('admin_user')->where('status',1)->field('id,truename')->select()->toArray();$names=array_column($people,'truename','id');$result=[];
        foreach(Db::name('admin_org')->where('mid',0)->field('id,title')->order('id')->select()as$d){$ids=$group[$d['id']]??[];$owner=count($ids)===1&&ctype_digit($ids[0])?(int)$ids[0]:0;
            $issue=count($ids)>1?'部门责任人配置重复':(!$owner?'部门尚未配置责任人':(!isset($names[$owner])?'部门责任人不存在或已离职':''));
            $result[]=['id'=>(int)$d['id'],'title'=>$d['title'],'ownerId'=>$owner,'ownerName'=>$names[$owner]??'','available'=>$issue==='','issue'=>$issue,
                'version'=>$this->fingerprint([(array)$d,$ids,$names[$owner]??null])];}
        return $result;
    }
    public function options()
    {
        SuggestionTransfer::acceptExpired();
        $owners=Db::name('admin_user')->whereIn('id',Db::name('onsite_suggestion_list')->where('status','>',0)->where('owner','>',0)->distinct(true)->column('owner')?:[0])->field('id,truename')->order('id')->select()->toArray();
        foreach($owners as&$owner)$owner['id']=(int)$owner['id'];unset($owner);
        return json(['code'=>0,'msg'=>'','data'=>['departments'=>$this->departmentOptions(),'owners'=>$owners,'today'=>(new \DateTimeImmutable('now',new \DateTimeZone('Asia/Shanghai')))->format('Y-m-d')]]);
    }
    public function detail()
    {
        SuggestionTransfer::acceptExpired();
        $row=Db::name('onsite_suggestion_list')->where('id',$this->number($this->request->get('id',0)))->where('status','>',0)->find();if(!$row)$this->fail('建议不存在或已删除',404);
        $data=$this->present([$row])[0];$data['transferHistory']=SuggestionTransfer::history($row['id']);
        return json(['code'=>0,'msg'=>'','data'=>$data]);
    }
    public function assign()
    {
        SuggestionTransfer::acceptExpired();
        if(!$this->isConfiguredAssigner())$this->fail('你未被设置为分配人员，请联系rightid>98的账号配置',403);
        $id=$this->number($this->request->post('id'));$department=$this->number($this->request->post('departmentId'),127);$version=$this->request->post('version');$departmentVersion=$this->request->post('departmentVersion');
        foreach([$version,$departmentVersion]as$v)if(!is_string($v)||!preg_match('/^[a-f0-9]{64}$/D',$v))$this->fail('请重新打开分配窗口');
        $changed=false;$notify=false;
        $row=SuggestionTransfer::locked(function()use($id,$department,$version,$departmentVersion,&$changed,&$notify){
            if(!$this->isConfiguredAssigner())$this->fail('分配权限已变化，请刷新页面',403);
            $row=Db::name('onsite_suggestion_list')->where('id',$id)->find();if(!$row||(int)$row['status']<=0)$this->fail('建议不存在或已删除',404);
            if(!hash_equals($this->fingerprint($row),$version)||!$this->assignable($row))$this->fail('建议已被处理或发生变化，请刷新后重新分配',409);
            $selected=null;foreach($this->departmentOptions()as$d)if($d['id']===$department)$selected=$d;
            if(!$selected||!$selected['available'])$this->fail($selected['issue']??'责任部门不存在');
            if(!hash_equals($selected['version'],$departmentVersion))$this->fail('部门责任人配置已变化，请重新打开分配窗口',409);
            $changed=(int)$row['due_dpt']!==$department||(int)$row['owner']!==$selected['ownerId']||(int)$row['status']!==2;
            $notify=$changed&&(int)$row['owner']!==$selected['ownerId'];
            if($changed){
                if(!empty($row['transfer_id'])){
                    if($this->request->post('cancelTransfer')!==true && $this->request->post('cancelTransfer')!=='1' && $this->request->post('cancelTransfer')!==1)$this->fail('存在待接收的转移申请，请确认取消申请后重新分配',409);
                    SuggestionTransfer::cancel($row,(int)$this->actor['id'],'后台重新分配，取消本次转移');
                }
                SuggestionTransfer::updateSuggestion($row,['due_dpt'=>$department,'owner'=>$selected['ownerId'],'status'=>2,'transfer_id'=>0,'transfer_to'=>0,'update'=>time()]);
                $row=Db::name('onsite_suggestion_list')->where('id',$id)->find();
            }
            return $row;
        });
        if($changed)try{\app\mobile\service\MessageInbox::invalidate();}catch(\Throwable $error){\think\facade\Log::warning('suggestion inbox invalidation unavailable');}
        $notice=$notify?(new V2SuggestionNotice())->send($row):['status'=>'unchanged','message'=>$changed?'分配已保存，责任人未变化，无需重复通知':'分配信息未变化，无需重复提交'];
        return json(['code'=>0,'msg'=>'','data'=>['changed'=>$changed,'notification'=>$notice,'record'=>$this->present([$row])[0]]]);
    }
    private function requireSettingsManager()
    {
        if((int)$this->actor['rightid']<=98)$this->fail('仅rightid>98的账号可以设置分配人员',403);
    }
    public function assignerSettings()
    {
        $this->requireSettingsManager();
        $rows=Db::name('onsite_base')->where('mid',88)->order('id')->select()->toArray();
        if(count($rows)>1)$this->fail('分配人员配置存在重复记录，请先核对mid=88的数据',409);
        $ids=$rows?array_values(array_filter(array_map('intval',explode(',',(string)$rows[0]['content'])))):[];
        $departments=Db::name('admin_org')->where('mid',0)->column('title','id');
        $people=Db::name('admin_user')->where('status',1)->field('id,username,truename,dpt')->order('truename,id')->select()->toArray();$found=[];
        foreach($people as&$p){$p['id']=(int)$p['id'];$p['departmentName']=$departments[$p['dpt']]??'未设置';$p['disabled']=false;$found[]=$p['id'];}unset($p);
        foreach(array_diff($ids,$found)as$id)$people[]=['id'=>$id,'username'=>'','truename'=>'原配置人员已离职或不存在（ID '.$id.'）','disabled'=>true];
        return json(['code'=>0,'msg'=>'','data'=>['personIds'=>$ids,'people'=>$people,'version'=>$this->fingerprint($rows)]]);
    }
    public function saveAssigners()
    {
        $this->requireSettingsManager();$ids=$this->request->post('personIds');$version=$this->request->post('version');
        if(!is_array($ids)||!$ids||count($ids)>100)$this->fail('请至少选择一名分配人员');
        $ids=array_map(function($id){return $this->number($id);},array_values($ids));
        if(count(array_unique($ids))!==count($ids))$this->fail('分配人员不能重复');sort($ids,SORT_NUMERIC);$content=implode(',',$ids);
        if(strlen($content)>255)$this->fail('选择人员过多，超出现有配置字段长度');
        if(!is_string($version)||!preg_match('/^[a-f0-9]{64}$/D',$version))$this->fail('请重新打开设置窗口');
        $config=config('database.connections.'.config('database.default'));$prefix=$config['prefix'];if(!preg_match('/^[a-zA-Z0-9_]*$/D',$prefix))$this->fail('数据库配置无效',500);
        $pdo=Db::connect()->getConnection()->getPdo();$locked=false;
        try{
            $pdo->exec('LOCK TABLES `'.$prefix.'onsite_base` WRITE, `'.$prefix.'admin_user` READ, `'.$prefix.'auth` READ, `'.$prefix.'message` WRITE');$locked=true;
            if((int)Db::name('admin_user')->where(['id'=>$this->actor['id'],'status'=>1])->value('rightid')<=98)$this->fail('设置权限已变化，请刷新页面',403);
            $rows=Db::name('onsite_base')->where('mid',88)->order('id')->select()->toArray();
            if(count($rows)>1||!hash_equals($this->fingerprint($rows),$version))$this->fail('分配人员配置已变化，请重新打开设置窗口',409);
            if(Db::name('admin_user')->whereIn('id',$ids)->where('status',1)->count()!==count($ids))$this->fail('所选人员已离职或不存在，请重新选择');
            if($rows)Db::name('onsite_base')->where(['id'=>$rows[0]['id'],'mid'=>88])->update(['content'=>$content]);
            else Db::name('onsite_base')->insert(['mid'=>88,'title'=>'suggestionAssigners','content'=>$content,'mark'=>'合理化建议分配人员']);
            // 名单保存时立即关闭失去权限的存量待分配消息，不依赖该人员再次打开小程序。
            $names=Db::name('admin_user')->whereIn('id',$ids)->where('status',1)->column('username','id');$allowedAuth=[];
            foreach(Db::name('auth')->whereIn('admin_id',$ids)->where('status',1)->field('id,admin_id,username')->select()->toArray() as $account){
                if(isset($names[$account['admin_id']])&&(string)$names[$account['admin_id']]===(string)$account['username'])$allowedAuth[]=(int)$account['id'];
            }
            $messages=Db::name('message')->where(['business'=>'suggestion','node'=>'assign','state'=>'pending']);
            if($allowedAuth)$messages->whereNotIn('auth_id',$allowedAuth);
            $messages->update(['state'=>'closed','updated_at'=>time()]);
        }finally{if($locked)$pdo->exec('UNLOCK TABLES');}
        return json(['code'=>0,'msg'=>'分配人员设置已保存','data'=>['personIds'=>$ids]]);
    }
    public function departmentSettings()
    {
        $this->requireSettingsManager();
        $departments=Db::name('admin_org')->where('mid',0)->field('id,title')->order('id')->select()->toArray();
        $mappings=Db::name('member_base')->where('mid',3)->order('id')->select()->toArray();$group=[];
        foreach($mappings as $row)$group[(int)$row['mark']][]=$row;
        $people=Db::name('admin_user')->where('status',1)->field('id,username,truename')->order('truename,id')->select()->toArray();$names=[];
        foreach($people as &$person){$person['id']=(int)$person['id'];$person['disabled']=false;$names[$person['id']]=$person['truename'];}unset($person);
        $list=[];$missing=[];
        foreach($departments as $d){$rows=$group[$d['id']]??[];$raw=count($rows)===1?(string)$rows[0]['title']:'';$owner=ctype_digit($raw)?(int)$raw:0;
            $issue=count($rows)>1?'存在重复窗口配置，请先核对数据':(!$owner?'尚未配置窗口':(!isset($names[$owner])?'原窗口已离职或不存在，请重新选择':''));
            if($owner&&!isset($names[$owner]))$missing[$owner]=true;
            $list[]=['id'=>(int)$d['id'],'title'=>$d['title'],'ownerId'=>$owner,'ownerName'=>$names[$owner]??'','issue'=>$issue,'duplicate'=>count($rows)>1,'version'=>$this->fingerprint([$d,$rows])];
        }
        foreach(array_keys($missing) as $id)$people[]=['id'=>$id,'username'=>'','truename'=>'原窗口已离职或不存在（ID '.$id.'）','disabled'=>true];
        return json(['code'=>0,'msg'=>'','data'=>['departments'=>$list,'people'=>$people]]);
    }
    public function saveDepartment()
    {
        $this->requireSettingsManager();$id=$this->number($this->request->post('departmentId'));$raw=$this->request->post('ownerId');
        $owner=($raw===0||$raw==='0'||$raw==='')?0:$this->number($raw);$version=$this->request->post('version');
        if(!is_string($version)||!preg_match('/^[a-f0-9]{64}$/D',$version))$this->fail('请重新加载部门窗口设置');
        $config=config('database.connections.'.config('database.default'));$prefix=$config['prefix'];if(!preg_match('/^[a-zA-Z0-9_]*$/D',$prefix))$this->fail('数据库配置无效',500);
        Db::query('SELECT 1');$pdo=Db::connect()->getConnection()->getPdo();$locked=false;
        try{
            $pdo->exec('LOCK TABLES `'.$prefix.'member_base` WRITE, `'.$prefix.'admin_org` READ, `'.$prefix.'admin_user` READ');$locked=true;
            if((int)Db::name('admin_user')->where(['id'=>$this->actor['id'],'status'=>1])->value('rightid')<=98)$this->fail('设置权限已变化，请刷新页面',403);
            $department=Db::name('admin_org')->where(['id'=>$id,'mid'=>0])->field('id,title')->find();if(!$department)$this->fail('部门不存在');
            $rows=Db::name('member_base')->where(['mid'=>3,'mark'=>$id])->order('id')->select()->toArray();
            if(count($rows)>1)$this->fail('该部门存在重复窗口配置，请先核对数据',409);
            if(!hash_equals($this->fingerprint([$department,$rows]),$version))$this->fail('部门窗口配置已变化，请重新加载后修改',409);
            if($owner&&!Db::name('admin_user')->where(['id'=>$owner,'status'=>1])->find())$this->fail('窗口人员已离职或不存在，请重新选择');
            if($rows)Db::name('member_base')->where('id',$rows[0]['id'])->update(['title'=>$owner?(string)$owner:'']);
            elseif($owner)Db::name('member_base')->insert(['mid'=>3,'mark'=>$id,'title'=>(string)$owner]);
        }finally{if($locked)$pdo->exec('UNLOCK TABLES');}
        return $this->departmentSettings();
    }
    public function image()
    {
        $row=Db::name('onsite_suggestion_list')->where('id',$this->number($this->request->get('id',0)))->where('status','>',0)->field('img')->find();$path=str_replace('\\','/',(string)($row['img']??''));
        if(!$row||strpos($path,'/storage/')!==0||strpos($path,'..')!==false)$this->fail('图片不存在或建议已删除',404);
        $root=realpath($this->app->getRootPath().'public/storage/');$file=$root?realpath($root.'/'.substr($path,9)):false;
        if(!$file||strpos($file,$root.DIRECTORY_SEPARATOR)!==0||!is_file($file))$this->fail('本地缺少该图片文件，请同步对应的 storage 图片目录',404);
        if(!is_readable($file))$this->fail('图片文件无读取权限',403);if(filesize($file)>5*1024*1024)$this->fail('图片文件超过5MB，暂无法读取');
        $info=@getimagesize($file);if(!$info||!in_array($info['mime'],['image/jpeg','image/png','image/webp'],true))$this->fail('图片格式不支持');
        $bytes=file_get_contents($file);if($bytes===false)$this->fail('图片读取失败',503);
        return json(['code'=>0,'msg'=>'','data'=>['dataUrl'=>'data:'.$info['mime'].';base64,'.base64_encode($bytes)]]);
    }
    public function export()
    {
        [$start,$end]=$this->dateRange(true);$q=Db::name('onsite_suggestion_list')->where(function($or)use($start,$end){$or->whereBetween('creat_date',[$start,$end+86399])->whereOr('update','between',[$start,$end+86399]);});
        $upper=$this->request->get('upperId','');$upper=$upper===''?(int)(clone$q)->max('id'):$this->number($upper);
        if(!$upper)return json(['code'=>0,'msg'=>'','data'=>['list'=>[],'total'=>0,'upperId'=>0,'nextCursor'=>null]]);
        $q->where('id','<=',$upper);$total=(clone$q)->count();$cursor=$this->request->get('cursor','');if($cursor!=='')$q->where('id','>',$this->number($cursor));
        $rows=$q->order('id')->limit(201)->select()->toArray();$more=count($rows)>200;if($more)array_pop($rows);
        return json(['code'=>0,'msg'=>'','data'=>['list'=>$rows,'total'=>$total,'upperId'=>$upper,'nextCursor'=>$more?(string)end($rows)['id']:null]]);
    }
}
