<?php
namespace app\mobile\service;

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

/** 服务号发送失败保存异常并尝试提醒，不撤销业务；绑定只读新版auth。 */
class WechatTemplateService
{
    protected $isAlert=false;
    protected $lastError='';
    protected $alertError='';
    protected $transport=[];
    protected function monitor() { return app()->make(TemplateFailureMonitor::class); }
    public function lastFailure() { return $this->alertError ?: $this->lastError; }
    protected function recordFailure($context,\Throwable $error) {
        $this->lastError='code='.$error->getCode().', '.TemplateFailureMonitor::clean($error->getMessage());
        if ($this->isAlert) return;
        try {
            $this->monitor()->record($context+$this->transport,$error);
            $this->monitor()->notifyPending($this);
        } catch (\Throwable $failure) { Log::warning('wxoms monitoring unavailable: '.TemplateFailureMonitor::clean($failure->getMessage())); }
    }
    protected function flushFaultAlerts() {
        if ($this->isAlert) return;
        try { $this->monitor()->notifyPending($this); }
        catch (\Throwable $failure) { Log::warning('wxoms fault flush unavailable: '.TemplateFailureMonitor::clean($failure->getMessage())); }
    }
    public function sendAlert($recipients,$values) {
        $alert=clone $this;$alert->isAlert=true;$alert->lastError='';$alert->alertError='';
        $ok=$alert->send('work_order_pending',$recipients,$values,'pages/messages/index');
        $this->alertError=$alert->lastError;return $ok;
    }
    protected function cache() { return Cache::store('redis'); }
    protected function setting($key) { return env('wxoms.'.$key, ''); }
    protected function templates() { return app()->config->get('wxoms_templates', []); }
    protected function tokenKey() {
        $config=app()->config;
        return 'wxoms-v3:'.$config->get('database.connections.'.$config->get('database.default').'.database').':template:gzh-token:'.$this->setting('gzh_appid');
    }
    public static function time($timestamp) {
        return (new \DateTimeImmutable('@'.(int)$timestamp))->setTimezone(new \DateTimeZone('Asia/Shanghai'))->format('Y-m-d H:i:s');
    }
    public function adminName($id) { return Db::name('admin_user')->where('id',(int)$id)->value('truename') ?: '相关负责人'; }
    public function admins($ids) {
        $ids=array_values(array_unique(array_filter(array_map('intval',(array)$ids))));
        if (!$ids) return [];
        return $this->activeRecipients(Db::name('auth')->alias('a')->join('admin_user u','u.id=a.admin_id AND u.username=a.username')
            ->whereIn('a.admin_id',$ids)->where(['a.status'=>1,'u.status'=>1])->field('a.*')->select()->toArray());
    }
    public function employees($eids) {
        if (!$eids) return [];
        return $this->activeRecipients(Db::name('auth')->whereIn('username',array_map('strval',(array)$eids))->where('status',1)->select()->toArray());
    }
    public function employeeLeaders($eid) {
        $member=Db::name('member_list')->where(['eid'=>(string)$eid,'status'=>1])->find();
        if (!$member) return [];
        return $this->admins([Db::name('team_list')->where(['id'=>$member['tid'],'status'=>1])->value('aid')]);
    }
    private function activeRecipients($accounts) {
        $recipients=[];
        foreach ($accounts as $account) {
            // 与 OmsBase 的机动工身份规则一致。
            $worker=Db::name('admin_user')->where(['username'=>$account['username'],'job'=>79])->find();
            if ($worker) $valid=Db::name('member_list')->where(['eid'=>$account['username'],'status'=>1])->find();
            elseif ((int)$account['user_type']===1) $valid=Db::name('member_list')->where(['id'=>$account['user_id'],'eid'=>$account['username'],'status'=>1])->find();
            else $valid=Db::name('admin_user')->where(['id'=>$account['admin_id'],'username'=>$account['username'],'status'=>1])->find();
            if ($valid && trim((string)($account['gzhOpenid'] ?? ''))!=='') $recipients[]=trim($account['gzhOpenid']);
        }
        return array_values(array_unique($recipients));
    }
    public function payload($template,$values,$page) {
        $definition=$this->templates()[$template] ?? null;
        if (!$definition || empty($definition['id'])) throw new \RuntimeException('Template configuration missing: '.$template);
        $data=[];
        foreach ($definition['fields'] as $name=>$field) {
            if (!array_key_exists($name,$values)) throw new \RuntimeException('Template field missing: '.$name);
            $value=trim(preg_replace('/\s+/u',' ',strip_tags(html_entity_decode((string)$values[$name],ENT_QUOTES,'UTF-8'))));
            if (strpos($field,'thing')===0) $value=mb_substr($value===''?'暂无':$value,0,20,'UTF-8');
            elseif (strpos($field,'character_string')===0 && !preg_match('/^[\x21-\x7e]{1,32}$/D',$value)) throw new \RuntimeException('Invalid template number');
            elseif (strpos($field,'time')===0 && !preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/D',$value)) throw new \RuntimeException('Invalid template time');
            $data[$field]=['value'=>$value];
        }
        if ($this->isAlert) {
            $base=rtrim((string)env('wxoms.public_base_url','https://www.cqoms.com'),'/');
            if (!preg_match('#^https://[a-zA-Z0-9.-]+(?::[0-9]+)?$#D',$base)) throw new \RuntimeException('Invalid backend alert URL');
            return ['template_id'=>$definition['id'],'url'=>$base.'/vue/#/user/templateFailures','data'=>$data];
        }
        $appid=$this->setting('appid');
        if (!$appid || !preg_match('#^pages/[a-zA-Z0-9/_?=&.-]+$#D',$page)) throw new \RuntimeException('Invalid miniprogram destination');
        return ['template_id'=>$definition['id'],'miniprogram'=>['appid'=>$appid,'pagepath'=>$page],'data'=>$data];
    }
    private function token($refresh=false) {
        $cache=$this->cache();$key=$this->tokenKey();
        if ($refresh) $cache->delete($key);
        $token=$cache->get($key);
        if ($token) return $token;
        $appid=$this->setting('gzh_appid');$secret=$this->setting('gzh_secret');
        if (!$appid || !$secret) throw new \RuntimeException('GZH configuration missing');
        $response=$this->postWechat('https://api.weixin.qq.com/cgi-bin/stable_token',['grant_type'=>'client_credential','appid'=>$appid,'secret'=>$secret]);
        if (empty($response['access_token'])) throw new \RuntimeException('Token unavailable: '.($response['errmsg'] ?? 'No access_token in response'),(int)($response['errcode'] ?? -1));
        $cache->set($key,$response['access_token'],max(1,(int)$response['expires_in']-120));
        return $response['access_token'];
    }
    /** 返回微信接口受理结果；失败记录及故障提醒均不影响已保存业务。 */
    public function send($template,$recipients,$values,$page) {
        $this->lastError='';$this->transport=[];$stage='recipient';
        parse_str((string)parse_url($page,PHP_URL_QUERY),$query);
        $context=['template'=>$template,'page'=>$page,'business_no'=>$values['number'] ?? ($query['id'] ?? '')];
        try {
            $recipients=array_values(array_unique(array_filter(array_map('trim',(array)$recipients))));
            if (!$recipients) throw new \RuntimeException('No active bound recipient');
            $stage='payload';$payload=$this->payload($template,$values,$page);
            $stage='token';$token=$this->token();$success=true;
            foreach ($recipients as $recipient) {
                try {
                    $stage='send';$this->transport=[];
                    $result=$this->postWechat('https://api.weixin.qq.com/cgi-bin/message/template/send?access_token='.rawurlencode($token),$payload+['touser'=>$recipient]);
                    if (in_array((int)($result['errcode'] ?? -1),[40001,40014,42001],true)) {
                        $stage='token';$token=$this->token(true);$stage='send';
                        $result=$this->postWechat('https://api.weixin.qq.com/cgi-bin/message/template/send?access_token='.rawurlencode($token),$payload+['touser'=>$recipient]);
                    }
                    if ((int)($result['errcode'] ?? -1)!==0) throw new \RuntimeException('Wechat rejected: '.($result['errmsg'] ?? 'No error detail'),(int)($result['errcode'] ?? -1));
                } catch (\Throwable $error) {
                    $success=false;$this->recordFailure($context+['stage'=>$stage,'recipient'=>$stage==='send'?$recipient:''],$error);
                    Log::warning('wxoms template failed: '.$template.', code='.$error->getCode().', '.TemplateFailureMonitor::clean($error->getMessage()));
                }
            }
            if ($success) $this->flushFaultAlerts();
            return $success;
        } catch (\Throwable $error) {
            $this->recordFailure($context+['stage'=>$stage],$error);
            Log::warning('wxoms template unavailable: '.$template.', code='.$error->getCode().', '.TemplateFailureMonitor::clean($error->getMessage()));return false;
        }
    }
    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=>2,CURLOPT_TIMEOUT=>4,CURLOPT_SSL_VERIFYPEER=>true,CURLOPT_SSL_VERIFYHOST=>2]);
        $ca=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);$errno=curl_errno($ch);$error=curl_error($ch);curl_close($ch);
        $this->transport=['http_status'=>$status,'curl_errno'=>$errno];
        $result=is_string($body)?json_decode($body,true):null;
        if ($status!==200 || !is_array($result)) throw new \RuntimeException('Wechat transport unavailable: curl='.$errno.', http='.$status.', '.$error);
        return $result;
    }
}
