<?php
namespace app\mobile\service;

use think\facade\Db;

/** 只保存待审批申请；调整结果直接写人员表，内网同步始终可覆盖。 */
class MemberPosition
{
    private static function fail($message,$status=409) { PositionDevice::fail($message,$status); }
    public static function leaderTeams($admin) {
        if(!$admin)return [];
        $names=Db::name('team_base')->column('title','id');
        $rows=Db::name('team_list')->where(['aid'=>$admin['id'],'status'=>1])->where('type','<',4)->field('id,team,project')->order('project,team,id')->select()->toArray();
        foreach($rows as &$r){$r['id']=(int)$r['id'];$r['project']=(int)$r['project'];$r['projectName']=$names[$r['project']]??'';}unset($r);return $rows;
    }
    private static function isJumper($s,$member) {
        $p=$s['positions'][(int)$member['poid']]??null;
        return $p && mb_strpos((string)$p['position'],'机动工')!==false;
    }
    private static function jumperCanManage($own,$target,$member) {
        if(!$own || !self::isJumper($own,$member))return false;
        if((int)$own['team']['id']===(int)$target['team']['id'])return true;
        if((int)$own['team']['aid']<=0 || (int)$own['team']['aid']!==(int)$target['team']['aid'])return false;
        foreach($target['positions'] as $p)if(mb_strpos((string)$p['position'],'机动工')!==false)return false;
        return true;
    }
    public static function managerTeams($account,$admin) {
        $rows=self::leaderTeams($admin);$m=\app\mobile\controller\OmsLeaveApply::memberFor($account);
        if($m){$own=self::state($m['tid']);if($own && self::isJumper($own,$m)){
            $q=Db::name('team_list')->where('status',1)->where('type','<',4);
            if((int)$own['team']['aid']>0)$q->where('aid',$own['team']['aid']);else $q->where('id',$m['tid']);
            foreach($q->field('id,team,project')->order('team,id')->select()->toArray() as $t){$target=self::state($t['id']);if($target && self::jumperCanManage($own,$target,$m) && !in_array((int)$t['id'],array_column($rows,'id'),true))$rows[]=['id'=>(int)$t['id'],'team'=>$t['team'],'project'=>(int)$t['project'],'projectName'=>Db::name('team_base')->where('id',$t['project'])->value('title') ?: ''];}
        }}return $rows;
    }
    private static function locked($work) {
        $c=Db::connect()->getConfig();$p=$c['prefix'];if(!preg_match('/^[A-Za-z0-9_]*$/D',$p))throw new \RuntimeException('Invalid prefix');
        $key='employee:'.substr(hash('sha256',$c['database'].'|'.$p),0,48);$got=Db::query('SELECT GET_LOCK(?,5) acquired',[$key]);
        if((int)$got[0]['acquired']!==1)self::fail('人员资料正在同步或调整，请稍后重试');
        $pdo=null;
        try {
            $pdo=Db::connect()->getConnection()->getPdo();$tables=[];
            foreach(['member_list'=>'WRITE','member_position_request'=>'WRITE','team_list'=>'READ','team_base'=>'READ','team_plan_capacity'=>'READ','team_plan_position'=>'READ','admin_user'=>'READ'] as $table=>$access)$tables[]='`'.$p.$table.'` '.$access;
            $pdo->exec('SET SESSION lock_wait_timeout=10');$pdo->exec('LOCK TABLES '.implode(',',$tables));
            self::purgeInvalid();return $work();
        } finally {if($pdo)$pdo->exec('UNLOCK TABLES');Db::query('SELECT RELEASE_LOCK(?)',[$key]);}
    }
    private static function state($tid) {
        $team=Db::name('team_list')->find((int)$tid);
        if(!$team || (int)$team['status']!==1 || (int)$team['type']>=4 || !Db::name('team_plan_capacity')->find((int)$team['plan_id']))return null;
        $positions=Db::name('team_plan_position')->where(['plan_id'=>$team['plan_id'],'status'=>1])->field('id,position,type,order')->order('order,id')->select()->toArray();
        $members=Db::name('member_list')->where(['tid'=>$team['id'],'status'=>1])->field('id,eid,ename,tid,poid,indirect,status')->order('eid,id')->select()->toArray();
        return ['team'=>$team,'positions'=>array_column($positions,null,'id'),'members'=>array_column($members,null,'id')];
    }
    private static function requiredState($tid) { $s=self::state($tid);if(!$s)self::fail('班组或当前产能方案未配置、已失效，请联系班长或同步最新资料',422);return $s; }
    private static function occupied($s,$poid) {return array_values(array_filter($s['members'],function($m)use($poid){return (int)$m['poid']===(int)$poid;}));}
    private static function fingerprint($s,$mid,$poid) {
        if(!isset($s['members'][$mid],$s['positions'][$poid]) || (int)$s['members'][$mid]['poid']===$poid)return '';
        $m=$s['members'][$mid];$owners=self::occupied($s,$poid);if(count($owners)!==1 || (int)$owners[0]['id']===$mid)return '';
        return hash('sha256',json_encode([(int)$m['id'],(int)$m['tid'],(int)$m['poid'],(int)$s['team']['plan_id'],$poid,(int)$owners[0]['id']]));
    }
    /** 可在人员导入现有表锁内调用；不新增任何历史记录。 */
    public static function purgeInvalid() {
        $states=[];$invalid=[];
        foreach(Db::name('member_position_request')->order('id')->select()->toArray() as $r){
            $tid=(int)$r['tid'];if(!array_key_exists($tid,$states))$states[$tid]=self::state($tid);$s=$states[$tid];
            if(!$s || (int)$s['team']['plan_id']!==(int)$r['plan_id'] || self::fingerprint($s,(int)$r['mid'],(int)$r['to_poid'])!==$r['fingerprint'])$invalid[]=$r['id'];
        }
        if($invalid)Db::name('member_position_request')->whereIn('id',$invalid)->delete();
    }
    private static function version($s) {return hash('sha256',json_encode($s));}
    private static function memberView($s,$m) {
        $p=$s['positions'][(int)$m['poid']]??null;
        return ['id'=>(int)$m['id'],'eid'=>$m['eid'],'name'=>$m['ename'],'positionId'=>(int)$m['poid'],'positionName'=>$p['position']??'未设置有效岗位','validPosition'=>(bool)$p];
    }
    private static function choices($s) {
        $rows=[];foreach($s['positions'] as $p){$owners=[];foreach(self::occupied($s,$p['id']) as $m)$owners[]=['id'=>(int)$m['id'],'name'=>$m['ename'],'eid'=>$m['eid']];$rows[]=['id'=>(int)$p['id'],'position'=>$p['position'],'occupants'=>$owners];}return $rows;
    }
    private static function baseView($s) {return ['teamId'=>(int)$s['team']['id'],'teamName'=>$s['team']['team'],'positions'=>self::choices($s),'version'=>self::version($s)];}
    private static function mineState($account) {
        $member=\app\mobile\controller\OmsLeaveApply::memberFor($account);if(!$member)self::fail('仅关联在职员工的本人可申请岗位',403);
        $s=self::requiredState($member['tid']);if(!isset($s['members'][$member['id']]))self::fail('员工班组已变化，请刷新');return [$s,$s['members'][$member['id']]];
    }
    private static function canSet($s,$account,$admin) {
        if($admin){$live=Db::name('admin_user')->where(['id'=>$admin['id'],'status'=>1,'username'=>$admin['username']])->find();if($live && (int)$s['team']['aid']===(int)$live['id'])return true;}
        $m=\app\mobile\controller\OmsLeaveApply::memberFor($account);if(!$m)return false;
        return self::jumperCanManage(self::state($m['tid']),$s,$m);
    }
    private static function managerState($account,$admin,$tid) {
        $s=self::requiredState($tid);if(!self::canSet($s,$account,$admin))self::fail('仅本班班长或符合本班/代管条件的有效机动工可设置组员岗位',403);return $s;
    }
    private static function checkVersion($s,$input) {if(!is_string($input['version']??null) || !hash_equals(self::version($s),$input['version']))self::fail('人员岗位或班组方案已变化，请刷新后重新操作');}
    private static function canSwap($s,$m,$other) {
        $old=(int)$m['poid'];return $old>0 && isset($s['positions'][$old]) && count(self::occupied($s,$old))===1 && (int)$other['poid']!==$old;
    }
    private static function requestView($s,$r) {
        $mid=(int)$r['mid'];$m=$s['members'][$mid];$other=self::occupied($s,$r['to_poid'])[0];
        return ['id'=>(int)$r['id'],'memberId'=>$mid,'name'=>$m['ename'],'eid'=>$m['eid'],'fromPosition'=>$s['positions'][(int)$m['poid']]['position']??'无有效岗位','targetPositionId'=>(int)$r['to_poid'],'targetPosition'=>$s['positions'][$r['to_poid']]['position'],'occupantId'=>(int)$other['id'],'occupantName'=>$other['ename'],'canSwap'=>self::canSwap($s,$m,$other),'createdAt'=>date('Y-m-d H:i',$r['created_at'])];
    }
    private static function canReview($s,$r,$account,$admin) {
        if(self::canSet($s,$account,$admin))return true;
        $id=(int)($account['user_id']??0);$m=$s['members'][$id]??null;
        if(!$m || (string)$m['eid']!==(string)$account['username'])return false;
        $owners=self::occupied($s,(int)$r['to_poid']);return count($owners)===1 && (int)$owners[0]['id']===$id;
    }
    public static function mine($account) {return self::locked(function()use($account){
        list($s,$m)=self::mineState($account);$r=Db::name('member_position_request')->where('mid',$m['id'])->find();$requests=[];
        $admin=\app\mobile\controller\OmsVisitor::adminFor($account);
        foreach(Db::name('member_position_request')->where('tid',$s['team']['id'])->order('created_at,id')->select()->toArray() as $item)if(self::canReview($s,$item,$account,$admin))$requests[]=self::requestView($s,$item);
        return array_merge(self::baseView($s),['member'=>self::memberView($s,$m),'pending'=>$r?self::requestView($s,$r):null,'requests'=>$requests]);
    });}
    public static function team($account,$admin,$tid) {return self::locked(function()use($account,$admin,$tid){$s=self::managerState($account,$admin,$tid);$members=[];foreach($s['members'] as $m)$members[]=self::memberView($s,$m);$requests=[];foreach(Db::name('member_position_request')->where('tid',$tid)->order('created_at,id')->select()->toArray() as $r)$requests[]=self::requestView($s,$r);return array_merge(self::baseView($s),['members'=>$members,'requests'=>$requests]);});}
    /** 单条UPDATE同时调整双方；表锁防止并发写入，MyISAM异常时恢复原值。 */
    private static function write($s,$assignments) {
        $before=[];$after=[];foreach($assignments as $mid=>$poid){$m=$s['members'][$mid];$before[$mid]=['poid'=>$m['poid'],'indirect'=>$m['indirect']];$after[$mid]=['poid'=>$poid,'indirect'=>$poid?((int)$s['positions'][$poid]['type']>4?2:1):$m['indirect']];}
        $update=function($values){$p=Db::connect()->getConfig('prefix');$sets=[];foreach(['poid','indirect'] as $field){$sql='`'.$field.'`=CASE `id`';foreach($values as $mid=>$row)$sql.=' WHEN '.(int)$mid.' THEN '.($row[$field]===null?'NULL':(int)$row[$field]);$sets[]=$sql.' END';}Db::execute('UPDATE `'.$p.'member_list` SET '.implode(',',$sets).' WHERE `id` IN ('.implode(',',array_map('intval',array_keys($values))).')');};
        try {$update($after);}catch(\Throwable $error){try{$update($before);}catch(\Throwable $restore){self::fail('岗位调整未完整完成，请班长立即刷新核对双方岗位；申请未标记完成',500);}self::fail('岗位保存失败，原岗位已恢复，请刷新后重试',500);}
        self::purgeInvalid();
    }
    public static function apply($account,$input) {return self::locked(function()use($account,$input){
        list($s,$m)=self::mineState($account);self::checkVersion($s,$input);$poid=(int)($input['positionId']??0);
        if(!isset($s['positions'][$poid]))self::fail('请选择本班当前方案的有效岗位',422);
        if((int)$m['poid']===$poid)self::fail('当前已经是该岗位',422);
        if(Db::name('member_position_request')->where('mid',$m['id'])->find())self::fail('已有待审批申请，请先撤回后再申请');
        $owners=self::occupied($s,$poid);if(count($owners)>1)self::fail('该岗位当前有多名员工，请班长先核对岗位分配',422);
        if(!$owners){self::write($s,[$m['id']=>$poid]);return ['result'=>'applied'];}
        Db::name('member_position_request')->insert(['mid'=>$m['id'],'tid'=>$m['tid'],'plan_id'=>$s['team']['plan_id'],'from_poid'=>(int)$m['poid'],'to_poid'=>$poid,'fingerprint'=>self::fingerprint($s,(int)$m['id'],$poid),'created_at'=>time()]);return ['result'=>'pending'];
    });}
    public static function withdraw($account,$id) {return self::locked(function()use($account,$id){list($s,$m)=self::mineState($account);if(!Db::name('member_position_request')->where(['id'=>(int)$id,'mid'=>$m['id']])->delete())self::fail('申请已处理或已失效，请刷新');return ['removed'=>true];});}
    private static function assignments($s,$mid,$poid,$mode) {
        if(!isset($s['members'][$mid]))self::fail('员工已离职或不属于本班组',422);
        $m=$s['members'][$mid];if(!$poid)return [$mid=>0];
        if(!isset($s['positions'][$poid]))self::fail('目标岗位不属于本班当前方案或已停用',422);
        if((int)$m['poid']===$poid)self::fail('员工当前已经在该岗位',422);
        $owners=self::occupied($s,$poid);if(count($owners)>1)self::fail('该岗位有多名员工，请先逐一核对或清空岗位',422);
        if(!$owners)return [$mid=>$poid];$other=$owners[0];
        if($mode==='swap'){if(!self::canSwap($s,$m,$other))self::fail('员工原岗位为空、失效或有多人占用，不能调换，请选择顶替',422);return [$mid=>$poid,$other['id']=>(int)$m['poid']];}
        if($mode==='replace')return [$mid=>$poid,$other['id']=>0];
        self::fail('目标岗位已有人，请明确选择调换或顶替',422);
    }
    public static function assign($account,$admin,$input) {return self::locked(function()use($account,$admin,$input){$s=self::managerState($account,$admin,(int)($input['teamId']??0));self::checkVersion($s,$input);self::write($s,self::assignments($s,(int)($input['memberId']??0),(int)($input['positionId']??0),$input['mode']??''));return ['result'=>'applied'];});}
    public static function decide($account,$admin,$input) {return self::locked(function()use($account,$admin,$input){
        $s=self::requiredState((int)($input['teamId']??0));$r=Db::name('member_position_request')->where(['id'=>(int)($input['requestId']??0),'tid'=>$s['team']['id']])->find();if(!$r)self::fail('申请已处理或已失效，请刷新');
        if(!self::canReview($s,$r,$account,$admin))self::fail('仅班长、符合本班/代管条件的机动工或目标岗位当前员工可确认此申请',403);
        self::checkVersion($s,$input);
        $mode=$input['mode']??'';if($mode==='reject'){Db::name('member_position_request')->where('id',$r['id'])->delete();return ['result'=>'rejected'];}
        if(!in_array($mode,['swap','replace'],true))self::fail('请选择调换、顶替或驳回',422);
        self::write($s,self::assignments($s,(int)$r['mid'],(int)$r['to_poid'],$mode));return ['result'=>'applied'];
    });}
}
