<?php
namespace app\mobile\controller;

use think\facade\Db;
use think\facade\Log;

/** 员工餐预提报，沿用明细及dpt=0汇总，截止后只读。 */
class OmsMealPreReport extends OmsBase
{
    protected function now() { return time(); }
    public static function adminFor($account) { return OmsVisitor::adminFor($account); }
    private function admin() { $admin=self::adminFor($this->current());if(!$admin)$this->fail('报餐预提报需要有效后台身份',403);return $admin; }
    private function result($data=null) { return json(['code'=>0,'message'=>'成功','data'=>$data]); }
    private function dateValue($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)$this->fail('日期不正确');return $date->getTimestamp();
    }
    private function formatTime($time,$format='Y-m-d') { return (new \DateTimeImmutable('@'.(int)$time))->setTimezone(new \DateTimeZone('Asia/Shanghai'))->format($format); }
    private function text($value,$max,$optional=false) { if(!is_string($value) || mb_strlen($value,'UTF-8')>$max || (!$optional && trim($value)===''))$this->fail('描述或备注不正确');return trim($value); }
    private function countValue($value) { if($value==='' || $value===null)return 0;if(!is_scalar($value) || !preg_match('/^(0|[1-9][0-9]{0,9})$/',(string)$value) || (float)$value>2147483647)$this->fail('餐数必须为非负整数');return (int)$value; }
    private function serialize($row) {
        $result=['id'=>(int)$row['id'],'date'=>$this->formatTime($row['meal_date']),'title'=>(string)$row['title'],'mark'=>(string)$row['mark'],'updatedAt'=>$this->formatTime($row['update'],'Y-m-d H:i:s'),'deadline'=>$this->formatTime($row['meal_date']-10800,'Y-m-d H:i'),'editable'=>$this->now()<=$row['meal_date']-10800,'total'=>0];
        foreach(range(1,4) as $i){$key='meal'.$i;$result[$key]=(int)$row[$key];$result['total']+=$result[$key];}return $result;
    }
    // ponytail: 低频提报共用表锁，覆盖旧版写入口；MyISAM进程崩溃不能回滚。
    public static function lockTable() {
        Db::query('SELECT 1');$connection=Db::connect()->getConnection();$prefix=$connection->getConfig('prefix');if(!preg_match('/^[a-zA-Z0-9_]+$/',$prefix))throw new \RuntimeException('Invalid prefix');
        $pdo=$connection->getPdo();$pdo->exec('LOCK TABLES `'.$prefix.'common_meal_list` WRITE, `'.$prefix.'admin_user` READ');return $pdo;
    }
    protected function syncSummary($date) {
        $totals=Db::name('common_meal_list')->where(['meal_date'=>$date,'status'=>1])->where('dpt','>',0)->field('SUM(COALESCE(meal1,0)) meal1,SUM(COALESCE(meal2,0)) meal2,SUM(COALESCE(meal3,0)) meal3,SUM(COALESCE(meal4,0)) meal4,COUNT(*) n')->find();
        $summary=Db::name('common_meal_list')->where(['meal_date'=>$date,'dpt'=>0]);$rows=(clone $summary)->select()->toArray();
        if(!(int)$totals['n']){ $summary->delete();return; }
        $data=['meal_date'=>$date,'dpt'=>0,'owner'=>null,'status'=>1,'update'=>$this->now()];foreach(range(1,4) as $i){$key='meal'.$i;if((float)$totals[$key]>2147483647)$this->fail('当天汇总数量超出范围');$data[$key]=(int)$totals[$key];}
        if($rows){Db::name('common_meal_list')->where('id',$rows[0]['id'])->update($data);foreach(array_slice($rows,1) as $row)Db::name('common_meal_list')->where('id',$row['id'])->delete();}else Db::name('common_meal_list')->insert($data);
    }
    private function mutate($date,$action) {
        $pdo=self::lockTable();$before=[];
        try {
            $before=Db::name('common_meal_list')->where('meal_date',$date)->select()->toArray();
            try { $result=$action();$this->syncSummary($date);return $result; }
            catch(\Throwable $error){Db::name('common_meal_list')->where('meal_date',$date)->delete();if($before)Db::name('common_meal_list')->insertAll($before);throw $error;}
        } finally { $pdo->exec('UNLOCK TABLES'); }
    }
    public function options() { return $this->guarded(function(){ $this->requireMethod('GET');$this->admin();$today=$this->dateValue($this->formatTime($this->now()));return $this->result(['today'=>$this->formatTime($today),'defaultDate'=>$this->formatTime($today+86400),'serverTime'=>$this->now()*1000]); }); }
    public function day() { return $this->guarded(function(){ $this->requireMethod('GET');$admin=$this->admin();$date=$this->dateValue($this->request->get('date'));$rows=Db::name('common_meal_list')->where(['owner'=>$admin['id'],'meal_date'=>$date,'status'=>1])->where('dpt','>',0)->order('id')->select()->toArray();if(count($rows)>1)$this->fail('当日存在重复记录，请联系管理员核对',409);return $this->result(['record'=>$rows?$this->serialize($rows[0]):null,'editable'=>$this->now()<=$date-10800,'deadline'=>$this->formatTime($date-10800,'Y-m-d H:i'),'serverTime'=>$this->now()*1000]); }); }
    public function records() { return $this->guarded(function(){ $this->requireMethod('GET');$admin=$this->admin();$page=max(1,(int)$this->request->get('page',1));$query=Db::name('common_meal_list')->where(['owner'=>$admin['id'],'status'=>1])->where('dpt','>',0);$total=(clone $query)->count();$rows=$query->order('meal_date desc,id desc')->limit(($page-1)*15,15)->select()->toArray();return $this->result(['items'=>array_map([$this,'serialize'],$rows),'total'=>$total,'page'=>$page]); }); }
    public function summary() { return $this->guarded(function(){ $this->requireMethod('GET');$this->admin();$date=$this->dateValue($this->request->get('date'));$totals=Db::name('common_meal_list')->where(['meal_date'=>$date,'status'=>1])->where('dpt','>',0)->field('SUM(COALESCE(meal1,0)) meal1,SUM(COALESCE(meal2,0)) meal2,SUM(COALESCE(meal3,0)) meal3,SUM(COALESCE(meal4,0)) meal4')->find();$result=['date'=>$this->formatTime($date),'total'=>0];foreach(range(1,4) as $i){$key='meal'.$i;$result[$key]=(int)$totals[$key];$result['total']+=$result[$key];}return $this->result($result); }); }
    public function save() { return $this->guarded(function(){
        $this->requireMethod('POST');$admin=$this->admin();if((int)$admin['dpt']<=0)$this->fail('后台部门未设置，请联系管理员');$date=$this->dateValue($this->request->post('date'));$title=$this->text($this->request->post('title'),50);$mark=$this->text($this->request->post('mark',''),255,true);
        $values=[];foreach(range(1,4) as $i){$key='meal'.$i;$values[$key]=$this->countValue($this->request->post($key));}if(array_sum($values)<=0)$this->fail('至少填写一个大于0的餐数');
        $changed=false;
        $row=$this->mutate($date,function()use($admin,$date,$title,$mark,$values,&$changed){
            $fresh=self::adminFor(['admin_id'=>$admin['id'],'username'=>$admin['username']]);if(!$fresh || (int)$fresh['dpt']<=0)$this->fail('后台身份或部门已变更',403);
            if($this->now()>$date-10800)$this->fail('已超过前一天21:00截止时间，不能修改或新增',409);
            $rows=Db::name('common_meal_list')->where(['owner'=>$admin['id'],'meal_date'=>$date])->order('id')->select()->toArray();if(count($rows)>1)$this->fail('当日存在重复记录，请联系管理员核对',409);
            if($rows && ((int)$rows[0]['dpt']<=0 || (int)$rows[0]['status']!==1))$this->fail('当日记录不可编辑',409);
            $data=array_merge($values,['meal_date'=>$date,'owner'=>(int)$admin['id'],'dpt'=>$rows?(int)$rows[0]['dpt']:(int)$fresh['dpt'],'title'=>$title,'mark'=>$mark,'status'=>1]);
            if($rows){$row=$rows[0];foreach($data as $key=>$value)if((string)$row[$key]!==(string)$value)$changed=true;if(!$changed)return $row;$data['update']=$this->now();Db::name('common_meal_list')->where('id',$row['id'])->update($data);$data['id']=(int)$row['id'];}
            else{$changed=true;$data['update']=$this->now();$data['id']=Db::name('common_meal_list')->insertGetId($data);}return $data;
        });
        if($changed && $this->now()>$date-25200)$this->notifyChange($row,false);return $this->result($this->serialize($row));
    }); }
    public function remove() { return $this->guarded(function(){
        $this->requireMethod('POST');$admin=$this->admin();$id=$this->countValue($this->request->post('id'));$row=Db::name('common_meal_list')->where(['id'=>$id,'owner'=>$admin['id'],'status'=>1])->where('dpt','>',0)->find();if(!$row)$this->fail('记录不存在或不属于本人',403);
        $this->mutate($row['meal_date'],function()use($row,$admin){$fresh=Db::name('common_meal_list')->where(['id'=>$row['id'],'owner'=>$admin['id'],'status'=>1])->where('dpt','>',0)->find();if(!$fresh)$this->fail('记录已变更',409);if($this->now()>$fresh['meal_date']-10800)$this->fail('已截止，不能删除',409);Db::name('common_meal_list')->where('id',$row['id'])->delete();});
        if($this->now()>$row['meal_date']-25200)$this->notifyChange($row,true);return $this->result();
    }); }
    protected function notifyChange($row,$deleted) {
        try {
            $recipients=Db::name('auth')->alias('a')->join('admin_user u','u.id=a.admin_id AND u.username=a.username')->whereIn('a.admin_id',[315,657])->where(['a.status'=>1,'u.status'=>1])->field('a.*')->select()->toArray();$openids=[];
            foreach($recipients as $account){try{$this->assertActive($account);if(trim((string)$account['gzhOpenid']))$openids[]=$account['gzhOpenid'];}catch(\Throwable $error){continue;}}if(!$openids)return;
            $key=$this->cacheKey('meal-pre-report:gzh-token');$token=$this->cache()->get($key);if(!$token){$appid=env('wxoms.gzh_appid','');$secret=env('wxoms.gzh_secret','');if(!$appid || !$secret)throw new \RuntimeException('Missing GZH configuration');$result=$this->postWechat('https://api.weixin.qq.com/cgi-bin/stable_token',['grant_type'=>'client_credential','appid'=>$appid,'secret'=>$secret]);if(empty($result['access_token']))throw new \RuntimeException('Token unavailable');$token=$result['access_token'];$this->cache()->set($key,$token,max(60,(int)$result['expires_in']-120));}
            $owner=Db::name('admin_user')->find($row['owner']);$department=Db::name('admin_org')->where('id',$row['dpt'])->value('title');
            foreach(array_unique($openids) as $openid){$response=$this->postWechat('https://api.weixin.qq.com/cgi-bin/message/template/send?access_token='.rawurlencode($token),['touser'=>$openid,'template_id'=>'er50wOZlg09QrXfQnERblpyz0JfQM-_6_YxsvIRcUKM','miniprogram'=>['appid'=>env('wxoms.appid',''),'pagepath'=>'pages/meal-pre-report/index?tab=summary&date='.$this->formatTime($row['meal_date'])],'data'=>['keyword1'=>['value'=>$department.' - '.($owner['truename'] ?? '')],'keyword2'=>['value'=>$this->formatTime($this->now(),'Y-m-d H:i:s')],'keyword3'=>['value'=>$this->formatTime($row['meal_date']).'员工餐提报'.($deleted?'已删除':'数量或信息有变更')]]]);if((int)($response['errcode'] ?? -1)!==0){if(in_array((int)($response['errcode'] ?? 0),[40001,40014,42001],true))$this->cache()->delete($key);Log::warning('oms meal pre-report notification failed');}}
        }catch(\Throwable $error){Log::warning('oms meal pre-report notification unavailable');}
    }
    protected function postWechat($url,$data) {
        $ch=curl_init($url);curl_setopt_array($ch,[CURLOPT_POST=>true,CURLOPT_POSTFIELDS=>json_encode($data,JSON_UNESCAPED_UNICODE),CURLOPT_HTTPHEADER=>['Content-Type: application/json'],CURLOPT_RETURNTRANSFER=>true,CURLOPT_CONNECTTIMEOUT=>5,CURLOPT_TIMEOUT=>10,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2]);$ca=$this->app->getRootPath().'config/cacert.pem';if(is_file($ca))curl_setopt($ch,CURLOPT_CAINFO,$ca);$body=curl_exec($ch);$status=curl_getinfo($ch,CURLINFO_HTTP_CODE);curl_close($ch);$result=is_string($body)?json_decode($body,true):null;if($status!==200 || !is_array($result))throw new \RuntimeException('Wechat unavailable');return $result;
    }
}
