<?php
namespace app\adminVue\controller;

use think\facade\Db;
use think\facade\Cache;

class V2Leave extends V2Base
{
    const FIELDS='id,tid,aid,mid,type,goto,perleave_date,reason,update,status,mark,communicate,leave_date,submit_date';
    const TYPES=['9a'=>26,'9b'=>27,'8a'=>352,'8c'=>354,'8d'=>355];
    private function result($data){return json(['code'=>0,'msg'=>'','data'=>$data]);}
    private function today(){return new \DateTimeImmutable('today',new \DateTimeZone('Asia/Shanghai'));}
    private function number($value,$min=1,$max=2147483647)
    {
        if(!is_scalar($value)||!preg_match('/^[0-9]+$/D',(string)$value)||(float)$value<$min||(float)$value>$max)throw new \InvalidArgumentException('数字参数无效');
        return (int)$value;
    }
    private function day($value)
    {
        if(!is_string($value))throw new \InvalidArgumentException('最后工作日格式无效');
        $date=\DateTimeImmutable::createFromFormat('!Y-m-d',$value,new \DateTimeZone('Asia/Shanghai'));
        if(!$date||$date->format('Y-m-d')!==$value||$value<'1970-01-02'||$value>'2037-12-31')throw new \InvalidArgumentException('日期格式无效');
        return $date;
    }
    private function stamp($value,$day=false){return (int)$value>0?(new \DateTimeImmutable('@'.(int)$value))->setTimezone(new \DateTimeZone('Asia/Shanghai'))->format($day?'Y-m-d':'Y-m-d H:i:s'):'';}
    private function canBatch(){return $this->canCreate();}
    private function canCreate(){return (int)$this->actor['dpt']===6;}
    private function permit($mode)
    {
        $current=Db::name('admin_user')->where(['id'=>$this->actor['id'],'status'=>1])->find();
        if(!$current||!hash_equals((string)$current['password'],(string)$this->actor['password']))$this->fail('登录资料已变化，请重新登录',401);
        $this->actor=$current;
        if($mode==='batch'?!$this->canBatch():!$this->canCreate())$this->fail('当前账号无权办理此操作',403);
    }
    private function version($row,$member){return hash('sha256',json_encode([$row,$member],JSON_UNESCAPED_UNICODE));}
    private function member($eid)
    {
        if(!is_string($eid)||!preg_match('/^[0-9]{1,20}$/D',$eid))throw new \InvalidArgumentException('工号格式无效，请每行填写一个工号');
        $members=Db::name('member_list')->where('eid',$eid)->field('id,eid,ename,tid,status,hiredate,leavedate')->select()->toArray();
        if(count($members)!==1)throw new \InvalidArgumentException($members?'工号对应多条员工档案，请先核对':'工号不存在');
        return $members[0];
    }
    private function row($id)
    {
        $row=Db::name('member_perleave')->whereIn('status',[1,2,3,4])->field(self::FIELDS)->find($id);
        if(!$row)$this->fail('离职记录不存在',404);return $row;
    }
    private function present($rows,$supplier=false)
    {
        if(!$rows)return [];
        $members=Db::name('member_list')->whereIn('id',array_column($rows,'mid'))->field('id,eid,ename,tid,status,hiredate,leavedate')->select()->toArray();$members=array_column($members,null,'id');
        $teams=Db::name('team_list')->whereIn('id',array_column($rows,'tid'))->column('team','id');
        $leaders=Db::name('admin_user')->whereIn('id',array_column($rows,'aid'))->column('truename','id');
        $types=Db::name('member_base')->where('mid',1)->field('id,title,content')->select()->toArray();$types=array_column($types,null,'id');
        $details=[];if($supplier){$values=Db::name('member_interview')->whereIn('m_id',array_column($rows,'mid'))->field('id,m_id,supplier,source_type,phone')->order('id desc')->select()->toArray();foreach($values as $value)if(!isset($details[$value['m_id']]))$details[$value['m_id']]=$value;}
        $list=[];foreach($rows as $row){$member=$members[$row['mid']]??null;$type=$types[$row['type']]??null;
            $item=['id'=>(int)$row['id'],'employeeNo'=>(string)($member['eid']??''),'name'=>$member['ename']??'员工资料缺失','team'=>$teams[$row['tid']]??'班组资料缺失',
                'leaderName'=>$leaders[$row['aid']]??'班长未设置','typeName'=>$type?trim($type['title'].' - '.$type['content']):'离职类型缺失',
                'expectedDate'=>$this->stamp($row['perleave_date'],true),'lastWorkDay'=>$this->stamp($row['leave_date'],true),'submittedAt'=>$this->stamp($row['submit_date']),'updatedAt'=>$this->stamp($row['update']),
                'reason'=>$row['reason'],'communicate'=>$row['communicate'],'mark'=>$row['mark'],'status'=>(int)$row['status'],'statusName'=>[1=>'预离职',2=>'已离职',3=>'已撤销',4=>'超期取消'][(int)$row['status']],
                'version'=>$this->version($row,$member),'canComplete'=>$this->canCreate()&&(int)$row['status']===1&&$member&&(int)$member['status']===1&&!(int)$row['leave_date']&&!(int)$row['submit_date']];
            if($supplier){$detail=$details[$row['mid']]??[];$source=(string)($detail['source_type']??'');$benefit=['1'=>'小时工','2'=>'重庆社保','3'=>'学生工'][$source]??$source;
                $item=['ID'=>(int)$row['id'],'姓名'=>$item['name'],'工号'=>$item['employeeNo'],'班组'=>$item['team'],'班长'=>$item['leaderName'],'入职时间'=>$this->stamp($member['hiredate']??0,true),'劳务公司'=>$detail['supplier']??'','福利类型'=>$benefit,'手机号'=>(string)($detail['phone']??''),'申请离职时间'=>$item['updatedAt'],'预离职时间'=>$item['expectedDate'],'离职类型'=>$item['typeName'],'离职原因'=>$row['reason']];
            }$list[]=$item;
        }return $list;
    }
    public function options()
    {
        $types=Db::name('member_base')->where('mid',1)->whereIn('id',array_values(self::TYPES))->field('id,title,content')->select()->toArray();$types=array_column($types,null,'id');$options=[];
        foreach(self::TYPES as $code=>$id)if(isset($types[$id]))$options[]=['code'=>$code,'label'=>$code.' '.$types[$id]['content']];
        return $this->result(['today'=>$this->today()->format('Y-m-d'),'types'=>$options,'canCreate'=>$this->canCreate(),'canBatch'=>$this->canBatch()]);
    }
    public function index()
    {
        try{$status=$this->number($this->request->get('status',1),0,4);$page=$this->number($this->request->get('page',1),1,1000000);$limit=$this->number($this->request->get('limit',15),1,100);}catch(\InvalidArgumentException $error){$this->fail($error->getMessage());}
        $kw=$this->request->get('keyword','');if(!is_string($kw)||mb_strlen($kw)>100)$this->fail('关键词格式无效');
        $q=Db::name('member_perleave')->whereIn('status',[1,2,3,4]);if($status)$q->where('status',$status);
        if(trim($kw)!==''){$ids=Db::name('member_list')->whereLike('eid|ename','%'.addcslashes(trim($kw),'%_\\').'%')->column('id');$q->whereIn('mid',$ids?:[-1]);}
        $total=(clone $q)->count();return $this->result(['list'=>$this->present($q->field(self::FIELDS)->order('id desc')->page($page,$limit)->select()->toArray()),'total'=>$total]);
    }
    public function detail()
    {
        try{$id=$this->number($this->request->get('id'));}catch(\InvalidArgumentException $error){$this->fail($error->getMessage());}
        return $this->result($this->present([$this->row($id)])[0]);
    }
    public function employee()
    {
        try{$member=$this->member($this->request->get('eid'));}catch(\InvalidArgumentException $error){$this->fail($error->getMessage());}
        if((int)$member['status']!==1)$this->fail('该员工已离职');
        return $this->result(['employeeNo'=>$member['eid'],'name'=>$member['ename'],'team'=>Db::name('team_list')->where('id',$member['tid'])->value('team')?:'班组资料缺失']);
    }
    public function export()
    {
        $kind=$this->request->get('kind','sync');if(!in_array($kind,['sync','supplier'],true))$this->fail('导出类型无效');
        $q=Db::name('member_perleave');
        try{
            if($kind==='sync'){$start=$this->day($this->request->get('startDate'));$end=$this->day($this->request->get('endDate'));if($start>$end)throw new \InvalidArgumentException('开始日期不能晚于结束日期');
                $cutoff=$this->today()->modify('-15 days')->getTimestamp();$q->where(function($outer)use($cutoff,$start,$end){$outer->where(function($pending)use($cutoff){$pending->where('status',1)->where('perleave_date','>=',$cutoff);})->whereOr(function($done)use($start,$end){$done->where('status',2)->where('submit_date','>=',$start->getTimestamp())->where('submit_date','<',$end->modify('+1 day')->getTimestamp());});});
            }else $q->where('status',1);
            $upper=$this->number($this->request->get('upperId',(clone $q)->max('id')?:0),0);$cursor=$this->number($this->request->get('cursor',0),0);
        }catch(\InvalidArgumentException $error){$this->fail($error->getMessage());}
        $q->where('id','<=',$upper);$total=(clone $q)->count();if($cursor)$q->where('id','<',$cursor);
        $rows=$q->field(self::FIELDS)->order('id desc')->limit(201)->select()->toArray();$more=count($rows)>200;if($more)array_pop($rows);
        return $this->result(['list'=>$kind==='sync'?$rows:$this->present($rows,true),'total'=>$total,'upperId'=>$upper,'nextCursor'=>$more?(int)end($rows)['id']:null]);
    }
    private function locked($action)
    {
        $config=config('database.connections.'.config('database.default'));$prefix=$config['prefix'];if(!preg_match('/^[A-Za-z0-9_]+$/D',$prefix))$this->fail('数据库配置无效',500);
        $connection=Db::connect()->getConnection();$pdo=$connection->getPdo();$tables=[];
        foreach(['member_perleave'=>'WRITE','member_list'=>'WRITE','admin_user'=>'READ','team_list'=>'READ','member_base'=>'READ'] as $table=>$access)$tables[]='`'.$prefix.$table.'` '.$access;
        $pdo->exec('SET SESSION lock_wait_timeout=10');$pdo->exec('LOCK TABLES '.implode(',',$tables));
        try{return $action();}finally{$connection->free();$pdo->exec('UNLOCK TABLES');}
    }
    private function prepare($mode,$input)
    {
        $this->permit($mode);$errors=[];$plan=[];
        if($mode==='batch'){
            $text=$input['employeeNos']??null;if(!is_string($text)||strlen($text)>30000)$this->fail('工号列表无效，最多1000个工号');
            $rows=[];$seen=[];foreach(preg_split('/\r\n|\n|\r/',$text) as $index=>$eid){$eid=trim($eid);if($eid==='')continue;if(isset($seen[$eid]))continue;$seen[$eid]=true;$rows[]=['line'=>$index+1,'eid'=>$eid,'lastWorkDay'=>$input['lastWorkDay']??null];}
        }elseif($mode==='complete'){
            $rows=[['line'=>1,'id'=>$input['id']??null,'version'=>$input['version']??null,'lastWorkDay'=>$input['lastWorkDay']??null]];
        }else{$rows=$input['rows']??null;if(!is_array($rows)||array_values($rows)!==$rows)$this->fail('上传名单格式无效');}
        if(!$rows||count($rows)>1000)$this->fail('请输入1至1000名员工');
        $seen=[];foreach($rows as $index=>$values){$line=$index+1;$eid='';try{
            if(!is_array($values))throw new \InvalidArgumentException('行格式无效');$line=$this->number($values['line']??($index+1),1,1000000);
            if($mode==='complete'){$row=Db::name('member_perleave')->field(self::FIELDS)->find($this->number($values['id']));if(!$row)throw new \InvalidArgumentException('申请不存在');$member=Db::name('member_list')->where('id',$row['mid'])->field('id,eid,ename,tid,status,hiredate,leavedate')->find();if(!$member)throw new \InvalidArgumentException('员工资料缺失');$eid=(string)$member['eid'];
                if(!is_string($values['version'])||!hash_equals($this->version($row,$member),$values['version']))throw new \InvalidArgumentException('申请或员工资料已变化，请刷新后办理');
            }else{$eid=$values['eid']??null;$member=$this->member($eid);$pending=Db::name('member_perleave')->where(['mid'=>$member['id'],'status'=>1])->field(self::FIELDS)->select()->toArray();
                if($mode==='batch'){if(count($pending)!==1)throw new \InvalidArgumentException($pending?'存在多条预离职申请，请核对':'未提交待办理的预离职申请（或已办理/撤销/超期取消）');$row=$pending[0];}
                else{if($pending)throw new \InvalidArgumentException('已有预离职申请，请通过原申请或批量离职办理');
                    if(Db::name('member_perleave')->where(['mid'=>$member['id'],'status'=>2])->where('leave_date','>=',max(1,(int)$member['hiredate']))->find())throw new \InvalidArgumentException('已有本次在职期间的离职办结记录，请核对员工档案，勿重复新增');$row=null;}
            }
            if(isset($seen[$member['id']]))throw new \InvalidArgumentException('工号重复');$seen[$member['id']]=true;
            if((int)$member['status']!==1)throw new \InvalidArgumentException('员工已离职，不能重复办理');
            $day=$this->day($values['lastWorkDay']??null);if($day>$this->today())throw new \InvalidArgumentException('最后工作日不能晚于今天');
            if((int)$member['hiredate']>0&&$day->format('Y-m-d')<$this->stamp($member['hiredate'],true))throw new \InvalidArgumentException('最后工作日不能早于入职日');
            if($row){if((int)$row['status']!==1||(int)$row['leave_date']>0||(int)$row['submit_date']>0)throw new \InvalidArgumentException('申请已办理或状态异常');
                if(Db::name('member_perleave')->where(['mid'=>$member['id'],'status'=>1])->count()!==1)throw new \InvalidArgumentException('存在多条预离职申请，请核对');}
            else{$code=$values['type']??null;if(!is_string($code)||!isset(self::TYPES[$code]))throw new \InvalidArgumentException('离职类型仅允许9a、9b、8a、8c、8d');$type=self::TYPES[$code];if(!Db::name('member_base')->where(['mid'=>1,'id'=>$type])->find())throw new \InvalidArgumentException('离职类型不存在');
                $name=$values['name']??null;if(!is_string($name)||trim($name)!==$member['ename'])throw new \InvalidArgumentException('姓名与员工档案不符');
                $reason=$values['reason']??null;if(!is_string($reason)||trim($reason)===''||mb_strlen(trim($reason))>255)throw new \InvalidArgumentException('离职原因必填，最多255字');
                $team=Db::name('team_list')->where('id',$member['tid'])->field('id,aid')->find();if(!$team)throw new \InvalidArgumentException('员工班组资料缺失');
                $new=['mid'=>$member['id'],'tid'=>$member['tid'],'aid'=>$team['aid'],'type'=>$type,'reason'=>trim($reason),'perleave_date'=>$day->getTimestamp()];
            }
            $plan[]=['line'=>$line,'eid'=>$eid,'name'=>$member['ename'],'member'=>$member,'row'=>$row,'new'=>$row?null:$new,'lastWorkDay'=>$day->getTimestamp()];
        }catch(\InvalidArgumentException $error){$errors[]=['line'=>$line,'eid'=>is_scalar($eid)?(string)$eid:'','message'=>$error->getMessage()];}}
        return ['plan'=>$plan,'errors'=>$errors];
    }
    private function ticketKey($ticket){return 'admin-v2:leave:'.hash('sha256',$this->requestToken().'|'.$ticket);}
    public function preview()
    {
        $input=$this->request->post();$mode=$input['mode']??null;if(!in_array($mode,['batch','complete','create'],true))$this->fail('办理模式无效');
        $prepared=$this->locked(function()use($mode,$input){return $this->prepare($mode,$input);});$ticket=null;
        if(!$prepared['errors']){$ticket=bin2hex(random_bytes(32));Cache::set($this->ticketKey($ticket),['mode'=>$mode,'input'=>$input,'snapshot'=>hash('sha256',json_encode($prepared['plan'])),'state'=>'ready'],600);}
        return $this->result(['ticket'=>$ticket,'total'=>count($prepared['plan'])+count($prepared['errors']),'errors'=>$prepared['errors'],'preview'=>array_map(function($item){return ['line'=>$item['line'],'eid'=>$item['eid'],'name'=>$item['name'],'lastWorkDay'=>$this->stamp($item['lastWorkDay'],true)];},$prepared['plan'])]);
    }
    public function commit()
    {
        $ticket=$this->request->post('ticket');if(!is_string($ticket)||!preg_match('/^[a-f0-9]{64}$/D',$ticket))$this->fail('请重新校验办理名单');$key=$this->ticketKey($ticket);
        $config=config('database.connections.'.config('database.default'));$lock='leave:'.substr(hash('sha256',$config['database'].'|'.$key),0,48);$got=Db::query('SELECT GET_LOCK(?,5) acquired',[$lock]);if((int)$got[0]['acquired']!==1)$this->fail('名单正在办理，请稍后查询结果',409);
        try{
            $saved=Cache::get($key);if(!$saved)$this->fail('办理校验已过期，请重新检查名单',409);$this->permit($saved['mode']);if($saved['state']==='done')return $this->result($saved['result']);if($saved['state']!=='ready')$this->fail('该名单已开始处理，请核对员工及申请状态，勿重复提交',409);
            $result=$this->locked(function()use($saved,$key){
                $prepared=$this->prepare($saved['mode'],$saved['input']);if($prepared['errors']||!hash_equals($saved['snapshot'],hash('sha256',json_encode($prepared['plan']))))$this->fail('名单或申请资料已变化，请重新校验后办理',409);
                if(!Cache::set($key,array_merge($saved,['state'=>'processing']),86400))$this->fail('无法记录办理进度，未开始写入',503);
                $result=['completed'=>[],'failed'=>[],'remaining'=>[]];$items=$prepared['plan'];
                foreach($items as $index=>$item){$recordWritten=false;try{
                    if($item['row']){$id=(int)$item['row']['id'];$changed=Db::name('member_perleave')->where(['id'=>$id,'status'=>1])->update(['status'=>2,'leave_date'=>$item['lastWorkDay'],'submit_date'=>time()]);if($changed!==1)throw new \RuntimeException('申请状态变化');}
                    else{$data=array_merge($item['new'],['status'=>2,'leave_date'=>$item['lastWorkDay'],'submit_date'=>time(),'update'=>time()]);$id=(int)Db::name('member_perleave')->insertGetId($data);}
                    $recordWritten=true;$changed=Db::name('member_list')->where(['id'=>$item['member']['id'],'status'=>1])->update(['status'=>2,'leavedate'=>$item['lastWorkDay']]);if($changed!==1)throw new \RuntimeException('员工状态变化');
                    $result['completed'][]=['line'=>$item['line'],'eid'=>$item['eid'],'id'=>$id];
                }catch(\Throwable $error){$result['failed'][]=['line'=>$item['line'],'eid'=>$item['eid'],'message'=>$recordWritten?'离职记录已办结，但员工档案未更新，请联系HR核对；不要重复提交':'本行写入异常，请核对实际记录，未继续办理后续员工'];foreach(array_slice($items,$index+1) as $rest)$result['remaining'][]=['line'=>$rest['line'],'eid'=>$rest['eid']];break;}}
                return $result;
            });
            try{Cache::set($key,['mode'=>$saved['mode'],'state'=>'done','result'=>$result],86400);}catch(\Throwable $error){}
            return $this->result($result);
        }finally{Db::query('SELECT RELEASE_LOCK(?)',[$lock]);}
    }
}
