<?php
namespace app\mobile\controller;

use think\facade\Db;
use think\facade\Filesystem;
use think\facade\Log;

/** 迁移现有绩效流程；计分、两种周期及超期任务继续沿用旧规则。 */
class OmsPerformance extends OmsBase
{
    public function messageItems($account,$since,\app\mobile\service\MessageInbox $inbox) {
        if(!$inbox->allowsSource($this))$this->fail('接口不存在',404);
        $member=$this->member($account);$aid=(int)$account['admin_id'];$items=[];
        $rows=Db::name('kpi_record')->where('status','>',0)->where(function($q)use($member,$aid,$since){
            $q->where('id',-1);
            if($member)$q->whereOr(function($r)use($member,$since){$r->where('eid',$member['eid'])->where(function($s)use($since){$s->where('status',1)->whereOr('update','>=',$since);});});
            if($aid)$q->whereOr(function($r)use($aid,$since){$r->where('aid',$aid)->whereIn('status',[2,3,4])->where('update','>=',$since);});
        })->select()->toArray();
        $this->preparePresentation($rows,$account);
        foreach($rows as $row){$p=$this->present($row,$account);$fields=['考评人'=>$p['assessorName'],'被考评人'=>$p['employeeName'],'班组'=>$p['teamName'],'考评事项'=>$p['content'],'分数'=>$p['score'],'考评日期'=>$p['date']];$route='/pages/performance/detail?id='.$row['id'];
            if($p['canConfirm'])$items[]=\app\mobile\service\MessageInbox::item('performance',$row['id'],'confirm','绩效待本人确认','待确认',$fields,$route,'pending',$row['create_time']);
            if($member && (string)$row['eid']===(string)$member['eid'] && in_array((int)$row['status'],[2,3],true))$items[]=\app\mobile\service\MessageInbox::item('performance',$row['id'],'confirm','绩效确认已处理',$p['statusName'],$fields,$route,'done',$row['update']);
            if(in_array((int)$row['status'],[2,3,4],true))$items[]=\app\mobile\service\MessageInbox::item('performance',$row['id'],'result-'.$row['status'],'绩效'.$p['statusName'],$p['statusName'],$fields+['拒绝原因'=>$p['rejectReason']],$route,'notice',$row['update']);
        }return $items;
    }
    protected function now() { return time(); }
    private function result($data) { return json(['code'=>0,'message'=>'成功','data'=>$data]); }
    private function id($value) {
        if (!is_scalar($value) || !preg_match('/^[1-9][0-9]{0,9}$/',(string)$value) || (float)$value>2147483647) $this->fail('编号不正确');
        return (int)$value;
    }
    private function member($account) { return OmsLeaveApply::memberFor($account); }
    public static function assessorFor($account) {
        if ((int)($account['admin_id'] ?? 0)<=0) return null;
        $admin=Db::name('admin_user')->where(['id'=>$account['admin_id'],'username'=>$account['username'],'status'=>1])->find();
        if (!$admin || !Db::name('team_list')->where('aid',$admin['id'])->count()) return null;
        return $admin;
    }
    private function admin($account) { return self::assessorFor($account); }
    private function requireAdmin($account) { $admin=$this->admin($account); if (!$admin) $this->fail('仅有有效后台身份且已关联班组的班长可进行绩效评估',403); return $admin; }
    private function period($assessed=false) {
        $month=$this->request->get('month',date('Y-m',$this->now()));
        if (!is_string($month) || !preg_match('/^(20[0-9]{2})-(0[1-9]|1[0-2])$/',$month)) $this->fail('请选择有效月份');
        $first=strtotime($month.'-01');
        // 原员工页22日至21日；原评估记录页23日至22日，迁移时不改变口径。
        $start=strtotime('-1 month',$first)+($assessed?22:21)*86400;
        $end=$first+($assessed?21:20)*86400;
        return ['month'=>$month,'start'=>$start,'end'=>$end,'label'=>date('Y-m-d',$start).' 至 '.date('Y-m-d',$end)];
    }
    private function clauses($admin) {
        $dpt=(int)$admin['dpt']>4?0:(int)$admin['dpt'];
        return Db::name('kpi_list')->where(['status'=>1,'dpt'=>$dpt])->order('job,item,id')->select()->toArray();
    }
    public function options() {
        return $this->guarded(function () {
            $this->requireMethod('GET'); $account=$this->current(); $admin=$this->admin($account); $member=$this->member($account);
            $clauses=$admin?$this->clauses($admin):[];
            foreach ($clauses as &$clause) { $clause['id']=(int)$clause['id']; $clause['type']=(int)$clause['type']; } unset($clause);
            return $this->result(['canAssess'=>(bool)$admin,'canViewMine'=>!$admin && (bool)$member,'clauses'=>$clauses,'today'=>date('Y-m-d',$this->now()),'defaultView'=>$admin?'create':'mine']);
        });
    }
    public function summary() {
        return $this->guarded(function () {
            $this->requireMethod('GET'); $account=$this->current();
            $admin=Db::name('admin_user')->where(['id'=>(int)$account['admin_id'],'username'=>$account['username'],'status'=>1])->find();
            if (!$admin || (int)$account['admin_id']<=0) $this->fail('仅有效后台身份可查看绩效汇总',403);
            $include=$this->request->get('includeLeft','1');
            if (!is_scalar($include) || !in_array((string)$include,['0','1'],true)) $this->fail('离职筛选参数不正确');
            $period=$this->period(true);
            $query=Db::name('kpi_record')->where('status','>',0)->whereBetween('kpi_date',[$period['start'],$period['end']]);
            if ((string)$include==='0') $query->whereIn('eid',function ($q) { $q->name('member_list')->where('status',1)->field('eid'); });
            $rows=$query->field('tid,ROUND(SUM(CASE WHEN score>0 THEN score ELSE 0 END),1) AS add_score,ROUND(SUM(CASE WHEN score<0 THEN -score ELSE 0 END),1) AS reduce_score,ROUND(SUM(CASE WHEN status=1 AND score>0 THEN score ELSE 0 END),1) AS pending_add,ROUND(SUM(CASE WHEN status=1 AND score<0 THEN -score ELSE 0 END),1) AS pending_reduce')->group('tid')->order('tid')->select()->toArray();
            $teams=Db::name('team_list')->column('id,team,project,aid','id');
            $projects=Db::name('team_base')->where('mid',1)->column('title','id');
            $leaders=Db::name('admin_user')->column('dpt','id');
            $departments=Db::name('admin_org')->where('mid',0)->column('title','id');
            $groups=['department'=>[],'project'=>[],'team'=>[]];
            foreach ($rows as $row) {
                $tid=(int)$row['tid']; $team=$teams[$tid] ?? []; $pid=(int)($team['project'] ?? 0);
                // 班组没有部门字段，沿班组负责人关联其当前部门；缺失关联保留在未归属项。
                $did=(int)($leaders[$team['aid'] ?? 0] ?? 0);
                $dimensions=['department'=>[$did,$departments[$did] ?? '未归属部门'],'project'=>[$pid,$projects[$pid] ?? '未归属项目'],'team'=>[$tid,$team['team'] ?? '班组资料缺失']];
                foreach ($dimensions as $scope=>$dimension) {
                    list($id,$name)=$dimension;
                    if (!isset($groups[$scope][$id])) $groups[$scope][$id]=['id'=>$id,'name'=>$name,'add'=>0,'reduce'=>0,'pendingAdd'=>0,'pendingReduce'=>0,'total'=>0];
                    foreach (['add'=>'add_score','reduce'=>'reduce_score','pendingAdd'=>'pending_add','pendingReduce'=>'pending_reduce'] as $key=>$field) $groups[$scope][$id][$key]=round($groups[$scope][$id][$key]+(float)$row[$field],1);
                    $groups[$scope][$id]['total']=round($groups[$scope][$id]['add']-$groups[$scope][$id]['reduce'],1);
                }
            }
            foreach ($groups as &$group) $group=array_values($group); unset($group);
            return $this->result(['period'=>$period['label'],'groups'=>$groups]);
        });
    }
    private function teamEmployees($member,$start) {
        if (!$member) return [];
        return Db::name('member_list')->where('tid',$member['tid'])->where(function ($q) use ($start) {
            $q->where('status',1)->whereOr(function ($q) use ($start) { $q->where('status',2)->where('leavedate','>=',$start); });
        })->column('eid');
    }
    private $presentation=null;
    private function preparePresentation($rows,$account) {
        $members=[];$ids=array_column($rows,'eid');
        if($ids)foreach(Db::name('member_list')->whereIn('eid',$ids)->order('id')->select()->toArray() as $member)if(!isset($members[$member['eid']]))$members[$member['eid']]=$member;
        $this->presentation=['members'=>$members,
            'clauses'=>$rows?Db::name('kpi_list')->whereIn('id',array_column($rows,'item'))->column('*','id'):[],
            'teams'=>$rows?Db::name('team_list')->whereIn('id',array_column($rows,'tid'))->column('team','id'):[],
            'admins'=>$rows?Db::name('admin_user')->whereIn('id',array_column($rows,'aid'))->column('truename','id'):[],
            'member'=>$this->member($account),'admin'=>$this->admin($account)];
    }
    private function present($row,$account) {
        $map=$this->presentation;if($map===null){$this->preparePresentation([$row],$account);$map=$this->presentation;}
        $employee=$map['members'][$row['eid']]??null;$clause=$map['clauses'][$row['item']]??null;
        $member=$map['member'];$admin=$map['admin'];
        return ['id'=>(int)$row['id'],'eid'=>(string)$row['eid'],'employeeName'=>$employee['ename'] ?? '人员资料缺失',
            'teamId'=>(int)$row['tid'],'teamName'=>($map['teams'][$row['tid']]??'') ?: '班组资料缺失',
            'assessorName'=>($map['admins'][$row['aid']]??'') ?: '评估人资料缺失',
            'date'=>date('Y-m-d',$row['kpi_date']),'createdAt'=>date('Y-m-d H:i:s',$row['create_time']),
            'updatedAt'=>date('Y-m-d H:i:s',$row['update']),'category'=>($clause['job'] ?? '').' - '.($clause['item'] ?? ''),
            'content'=>$clause['content'] ?? '条款资料缺失','standard'=>$clause['scores'] ?? '', 'score'=>(float)$row['score'],
            'status'=>(int)$row['status'],'statusName'=>[0=>'已删除',1=>'待确认',2=>'已拒绝',3=>'已确认',4=>'自动确认'][(int)$row['status']] ?? '未知状态',
            'mark'=>(string)$row['mark'],'rejectReason'=>(string)$row['rej_mark'],
            'imageUrl'=>strpos((string)$row['img'],'/storage/')===0?'https://www.cqoms.com'.$row['img']:'',
            'canConfirm'=>(int)$row['status']===1 && (int)$account['user_type']<3 && $member && (int)$row['eid']===(int)$member['eid'],
            'canDelete'=>(int)$row['status']===1 && $admin && (int)$row['aid']===(int)$admin['id']];
    }
    public function records() {
        return $this->guarded(function () {
            $this->requireMethod('GET'); $account=$this->current(); $scope=$this->request->get('scope','mine');
            if (!in_array($scope,['mine','team','assessed'],true)) $this->fail('查看类型不正确');
            $period=$this->period($scope!=='mine'); $query=Db::name('kpi_record')->where('status','>',0)->whereBetween('kpi_date',[$period['start'],$period['end']]);
            if ($scope==='team') {
                $admin=$this->requireAdmin($account);
                $teamIds=(clone $query)->where('aid',$admin['id'])->distinct(true)->column('tid');
                $teams=$teamIds ? (clone $query)->whereIn('tid',$teamIds)
                    ->field('tid,ROUND(SUM(CASE WHEN score>0 THEN score ELSE 0 END),1) AS add_score,ROUND(SUM(CASE WHEN score<0 THEN -score ELSE 0 END),1) AS reduce_score,ROUND(SUM(CASE WHEN status=1 AND score>0 THEN score ELSE 0 END),1) AS pending_add,ROUND(SUM(CASE WHEN status=1 AND score<0 THEN -score ELSE 0 END),1) AS pending_reduce')
                    ->group('tid')->order('tid')->select()->toArray() : [];
                $names=$teamIds ? Db::name('team_list')->whereIn('id',$teamIds)->column('team','id') : [];
                return $this->result(['items'=>[],'score'=>100,'period'=>$period['label'],'teams'=>array_map(function ($team) use ($names) {
                    return ['id'=>(int)$team['tid'],'name'=>$names[$team['tid']] ?? '班组资料缺失','add'=>(float)$team['add_score'],'reduce'=>(float)$team['reduce_score'],'pendingAdd'=>(float)$team['pending_add'],'pendingReduce'=>(float)$team['pending_reduce']];
                },$teams)]);
            }
            if ($scope==='assessed') { $admin=$this->requireAdmin($account); $query->where('aid',$admin['id']); }
            else {
                $member=$this->member($account); if (!$member) $this->fail('当前账号没有有效员工关联',403);
                $query->where('eid',$member['eid']);
            }
            $rows=$query->order('id desc')->select()->toArray(); $this->preparePresentation($rows,$account); $score=100;
            foreach ($rows as $row) $score+=(float)$row['score'];
            return $this->result(['items'=>array_map(function ($row) use ($account) { return $this->present($row,$account); },$rows),'score'=>$score,'period'=>$period['label'],'teams'=>[]]);
        });
    }
    private function row($account,$id) {
        $row=Db::name('kpi_record')->where('status','>',0)->find($this->id($id)); if (!$row) $this->fail('绩效记录不存在或已删除',404);
        $member=$this->member($account); $admin=$this->admin($account);
        $own=$member && (int)$member['eid']===(int)$row['eid']; $issued=$admin && (int)$admin['id']===(int)$row['aid'];
        $peer=$member && in_array((int)$row['eid'],array_map('intval',$this->teamEmployees($member,(int)$row['kpi_date'])),true);
        if (!$own && !$issued && !$peer) $this->fail('无权查看或处理此绩效',403);
        return $row;
    }
    public function detail() {
        return $this->guarded(function () { $this->requireMethod('GET'); $account=$this->current(); return $this->result($this->present($this->row($account,$this->request->get('id')),$account)); });
    }
    public function employee() {
        return $this->guarded(function () {
            $this->requireMethod('GET'); $account=$this->current(); $this->requireAdmin($account); $eid=$this->request->get('eid');
            if (!is_string($eid) || !preg_match('/^[0-9]{6}$/',$eid)) $this->fail('请输入6位工号');
            $member=Db::name('member_list')->where('eid',$eid)->find(); if (!$member) $this->fail('没有找到该工号',404);
            return $this->result(['eid'=>$eid,'name'=>$member['ename'],'teamId'=>(int)$member['tid'],'teamName'=>Db::name('team_list')->where('id',$member['tid'])->value('team') ?: '未记录']);
        });
    }
    private function saveImage() {
        $file=$this->request->file('image'); if (!$file) return null;
        if (!$file instanceof \think\file\UploadedFile || !$file->isValid() || $file->getSize()>5*1024*1024) $this->fail('请选择不超过5MB的图片');
        $types=['image/jpeg'=>'jpg','image/png'=>'png','image/webp'=>'webp']; $mime=$file->getMime();
        if (!isset($types[$mime]) || !@getimagesize($file->getPathname())) $this->fail('请选择JPG、PNG或WebP图片');
        $path=Filesystem::disk('public')->putFileAs('wx_upload/'.date('Ymd',$this->now()).'/performance',$file,bin2hex(random_bytes(12)).'.'.$types[$mime]);
        if (!$path) $this->fail('图片保存失败',503); return '/storage/'.str_replace('\\','/',$path);
    }
    private function text($key) {
        $value=$this->request->post($key,''); if (!is_string($value) || mb_strlen(trim($value))>255) $this->fail('说明最多255字'); return trim($value);
    }
    protected function submission($account,$requestId,$action) {
        $key=$this->cacheKey('performance:create:'.$account['admin_id'].':'.$requestId); $lock='perf:'.substr(hash('sha256',$key),0,48);
        $acquired=Db::query('SELECT GET_LOCK(?,5) AS acquired',[$lock]);
        if ((int)($acquired[0]['acquired'] ?? 0)!==1) $this->fail('正在提交，请稍后重试',409);
        try {
            $id=$this->cache()->get($key); if ($id) return ['id'=>(int)$id,'created'=>false];
            $row=$action(); $this->cache()->set($key,$row['id'],7*86400); return ['id'=>$row['id'],'created'=>true,'row'=>$row];
        } finally { Db::query('SELECT RELEASE_LOCK(?)',[$lock]); }
    }
    public function create() {
        return $this->guarded(function () {
            $this->requireMethod('POST'); $account=$this->current(); $admin=$this->requireAdmin($account);
            $requestId=$this->request->post('requestId'); if (!is_string($requestId) || !preg_match('/^[a-zA-Z0-9_-]{16,80}$/',$requestId)) $this->fail('提交标识不正确，请重新打开页面');
            $saved=$this->submission($account,$requestId,function () use ($admin) {
                $date=$this->request->post('date'); $timestamp=is_string($date)?strtotime($date):false;
                if (!$timestamp || date('Y-m-d',$timestamp)!==$date || !preg_match('/^20[0-9]{2}-[0-9]{2}-[0-9]{2}$/',$date)) $this->fail('考评日期不正确');
                if ($timestamp<strtotime(date('Y-m-d',$this->now()))-2*86400) $this->fail('绩效日期已超期');
                $teamId=$this->id($this->request->post('teamId')); $itemId=$this->id($this->request->post('itemId')); $eid=$this->request->post('eid');
                if (!is_string($eid) || !preg_match('/^[0-9]{6}$/',$eid) || !Db::name('member_list')->where('eid',$eid)->find()) $this->fail('员工工号不存在');
                if (!Db::name('team_list')->where(['id'=>$teamId,'status'=>1])->find()) $this->fail('班组已失效，请重新选择');
                // 全部启用班组可选；不以公网档案tid或评估人负责班组限制保存。
                if (!in_array($itemId,array_map('intval',array_column($this->clauses($admin),'id')),true)) $this->fail('条款已失效或不适用于当前部门');
                $score=$this->request->post('score'); if (!is_scalar($score) || !preg_match('/^[+-]?[0-9]+(?:\.[0-9])?$/',(string)$score) || abs((float)$score)>9999.9) $this->fail('实际分数须为数字，最多1位小数且符合存储范围');
                $mark=$this->text('mark'); $image=$this->saveImage();
                $row=['aid'=>(int)$admin['id'],'tid'=>$teamId,'eid'=>$eid,'item'=>$itemId,'score'=>(float)$score,'mark'=>$mark,'img'=>$image,'status'=>1,'create_time'=>$this->now(),'kpi_date'=>$timestamp,'update'=>$this->now()];
                try { $row['id']=(int)Db::name('kpi_record')->insertGetId($row); }
                catch (\Throwable $error) { if ($image) Filesystem::disk('public')->delete(substr($image,9)); throw $error; }
                return $row;
            });
            if ($saved['created']) $this->notify($saved['row'],'create');
            return $this->result(['id'=>$saved['id'],'created'=>$saved['created']]);
        });
    }
    public function confirm() {
        return $this->guarded(function () {
            $this->requireMethod('POST'); $account=$this->current(); $row=$this->row($account,$this->request->post('id')); $present=$this->present($row,$account);
            if (!$present['canConfirm']) $this->fail('仅本人待确认绩效可接受或拒绝',409);
            $decision=$this->request->post('decision'); if (!in_array($decision,['accept','reject'],true)) $this->fail('处理类型不正确');
            $reason=$decision==='reject'?$this->text('reason'):''; if ($decision==='reject' && $reason==='') $this->fail('请填写拒绝原因');
            $changed=Db::name('kpi_record')->where(['id'=>$row['id'],'status'=>1])->update(['status'=>$decision==='reject'?2:3,'rej_mark'=>$reason,'update'=>$this->now()]);
            if (!$changed) $this->fail('绩效状态已变化，请刷新',409);
            $row['rej_mark']=$reason; $this->notify($row,$decision); return $this->result(null);
        });
    }
    public function remove() {
        return $this->guarded(function () {
            $this->requireMethod('POST'); $account=$this->current(); $row=$this->row($account,$this->request->post('id'));
            if (!$this->present($row,$account)['canDelete']) $this->fail('仅评估人可删除本人待确认绩效',409);
            if (!Db::name('kpi_record')->where(['id'=>$row['id'],'status'=>1,'aid'=>$account['admin_id']])->update(['status'=>0,'update'=>$this->now()])) $this->fail('绩效状态已变化，请刷新',409);
            $this->notify($row,'remove'); return $this->result(null);
        });
    }
    protected function notify($row,$event) {
        try {
            $service=$this->templateService();$page='pages/performance/detail?id='.$row['id'];
            if ($event==='create') {
                $clause=Db::name('kpi_list')->find($row['item']);
                return $service->send('workflow_pending',$service->employees([$row['eid']]),[
                    'name'=>'绩效考评确认','initiator'=>$service->adminName($row['aid']),'time'=>$service::time($row['create_time']),
                    'reason'=>$row['score'].'分，'.($row['mark'] ?: ($clause['content'] ?? '请查看考评详情')),'step'=>'员工本人确认',
                ],$page);
            }
            if ($event==='remove') return $service->send('feedback_result',$service->employees([$row['eid']]),[
                'type'=>'绩效考评已撤销','content'=>'绩效考评'.$row['score'].'分','owner'=>$service->adminName($row['aid']),
                'reply'=>'原待确认考评已删除','time'=>$service::time($this->now()),
            ],'pages/performance/index');
            $member=Db::name('member_list')->where('eid',$row['eid'])->find();
            // aid 是发起考评的班长，跨班组考评仍回执给实际发起人。
            return $service->send($event==='reject'?'workflow_rejected':'workflow_approved',$service->admins([$row['aid']]),[
                'name'=>'绩效考评员工确认','approver'=>$member['ename'] ?? '员工','number'=>(string)$row['id'],'time'=>$service::time($this->now()),
            ],$page);
        } catch (\Throwable $error) { Log::warning('oms performance notification unavailable: '.$error->getMessage()); return false; }
    }
}
