<?php
namespace app\mobile\controller;

use think\facade\Db;
use think\facade\Filesystem;
use think\facade\Log;

class OmsSafetyRisk extends OmsBase
{
    public function messageItems($account,$since,\app\mobile\service\MessageInbox $inbox) {
        if(!$inbox->allowsSource($this))$this->fail('接口不存在',404);
        $ehs=$this->isEhs($account);$aid=(int)$account['admin_id'];$items=[];
        $rows=Db::name('onsite_safe_list')->where('status','>',0)->where(function($q)use($account,$aid,$ehs,$since){
            $q->where(function($own)use($account,$since){$own->where('eid',$account['username'])->where('status',4)->where('update','>=',$since);});
            if($aid)$q->whereOr(function($r)use($aid,$since){$r->where('owner',$aid)->where(function($s)use($since){$s->whereIn('status',[2,3])->whereOr(function($done)use($since){$done->where('status',4)->where('update','>=',$since);});});});
            if($ehs)$q->whereOr('status','in',[1,3]);
        })->select()->toArray();
        foreach($rows as $row){$p=$this->present($row,$account);$fields=['提报人'=>$p['ename'],'班组'=>$p['team'],'区域'=>$p['area'],'风险描述'=>$p['issueDesc']];$route='/pages/safety-risk/detail?id='.$row['id'];
            foreach(['canAssign'=>['assign','安全风险待分配'],'canFeedback'=>['feedback','安全风险待整改'],'canConfirm'=>['confirm','风险整改待EHS确认']] as $right=>$task)if($p[$right])$items[]=\app\mobile\service\MessageInbox::item('safety-risk',$row['id'],$task[0],$task[1],$p['statusName'],$fields,$route,'pending',($row['submit_time'] ?? 0) ?: ($row['update'] ?: ($row['create_date'] ?? 0)),$right==='canAssign'?0:($row['due_date']?((int)$row['due_date']+86399):0));
            if((int)$row['status']===4 && ((string)$row['eid']===(string)$account['username'] || $this->isOwner($account,$row)))$items[]=\app\mobile\service\MessageInbox::item('safety-risk',$row['id'],'result','安全风险整改已完成','已完成',$fields,$route,'notice',$row['update']);
        }return $items;
    }
    private function result($data = null) { return json(['code'=>0, 'message'=>'成功', 'data'=>$data]); }
    private function integer($value, $label) {
        if (!is_scalar($value) || !preg_match('/^[1-9][0-9]{0,9}$/', (string)$value) || (float)$value > 2147483647) $this->fail($label . '不正确');
        return (int)$value;
    }
    private function isEhs($account) {
        return (int)$account['admin_id'] > 0 && Db::name('admin_user')->where(['id'=>$account['admin_id'], 'dpt'=>53, 'status'=>1, 'username'=>$account['username']])->find() !== null;
    }
    private function isOwner($account, $row) {
        return (int)$account['admin_id'] > 0 && (int)$account['admin_id'] === (int)$row['owner'] && Db::name('admin_user')->where(['id'=>$account['admin_id'], 'status'=>1, 'username'=>$account['username']])->find() !== null;
    }
    private function row($account, $id) {
        $query = Db::name('onsite_safe_list')->where('id', $this->integer($id, '风险编号'))->where('status', '>', 0);
        $row = $query->find();
        if (!$row) $this->fail('风险不存在或已删除', 404);
        if ((string)$row['eid'] !== (string)$account['username'] && !$this->isOwner($account, $row) && !$this->isEhs($account)) $this->fail('无权查看或处理该风险', 403);
        return $row;
    }
    private function present($row, $account, $withImages = false) {
        $member = empty($row['ename']) || empty($row['team']) ? Db::name('member_list')->find((int)$row['mid']) : null;
        $team = !empty($row['tid']) ? Db::name('team_list')->find((int)$row['tid']) : null;
        $row['id'] = (int)$row['id']; $row['status'] = (int)$row['status']; $row['owner'] = (int)$row['owner'];
        $row['statusName'] = [1=>'待分配', 2=>'待整改', 3=>'待EHS确认', 4=>'已完成'][$row['status']] ?? '未知状态';
        $row['ename'] = $row['ename'] ?: ($member['ename'] ?? '');
        $row['team'] = $row['team'] ?: ($team['team'] ?? '');
        if (!$row['team'] && $member) $row['team'] = Db::name('team_list')->where('id', $member['tid'])->value('team') ?: '';
        $row['projectName'] = $team ? (Db::name('team_base')->where('id', $team['project'])->value('title') ?: '') : '';
        $row['createdDate'] = $row['create_date'] || $row['update'] ? date('Y-m-d', $row['create_date'] ?: $row['update']) : '';
        $row['dueDate'] = $row['due_date'] ? date('Y-m-d', $row['due_date']) : '';
        $row['ownerName'] = $row['owner'] ? (Db::name('admin_user')->where('id', $row['owner'])->value('truename') ?: '人员已变更') : '未分配';
        $row['area'] = (string)$row['area']; $row['issueDesc'] = (string)$row['issue_desc']; $row['action'] = (string)$row['action'];
        $row['canDelete'] = $row['status'] === 1 && (string)$row['eid'] === (string)$account['username'];
        $ehs = $this->isEhs($account);
        $row['canAssign'] = $row['status'] === 1 && $ehs;
        $row['canFeedback'] = $row['status'] === 2 && $this->isOwner($account, $row);
        $row['canConfirm'] = $row['status'] === 3 && $ehs;
        $row['images'] = []; $row['feedbackImages'] = [];
        if ($withImages) {
            foreach (Db::name('img_list')->where(['mid'=>6, 'mark'=>$row['id']])->whereIn('status', [1,2])->order('id')->select()->toArray() as $image) {
                $path = str_replace('\\', '/', (string)$image['img']);
                if (strpos($path, '/storage/') !== 0 || strpos($path, '..') !== false) continue;
                $key = (int)$image['status'] === 1 ? 'images' : 'feedbackImages';
                $row[$key][] = ['id'=>(int)$image['id'], 'url'=>'https://www.cqoms.com' . $path];
            }
        }
        return $row;
    }
    public function options() {
        return $this->guarded(function () {
            $this->requireMethod('GET'); $account = $this->current(); $ehs = $this->isEhs($account);
            $owners = [];
            if ($ehs) {
                $departments = Db::name('admin_org')->column('title', 'id');
                foreach (Db::name('admin_user')->where('status', 1)->field('id,truename,dpt')->order('dpt,job,id')->select()->toArray() as $owner) {
                    $owners[] = ['id'=>(int)$owner['id'], 'name'=>$owner['truename'], 'departmentId'=>(int)$owner['dpt'], 'departmentName'=>$departments[$owner['dpt']] ?? '未设置部门'];
                }
            }
            return $this->result(['requiresTeam'=>(int)$account['user_id'] > 0, 'canImprove'=>(int)$account['admin_id'] > 0, 'isEhs'=>$ehs, 'owners'=>$owners]);
        });
    }
    public function records() {
        return $this->guarded(function () {
            $this->requireMethod('GET'); $account = $this->current();
            $scope = $this->request->get('scope', 'mine'); $page = $this->integer($this->request->get('page', 1), '页码');
            $query = Db::name('onsite_safe_list')->where('status', '>', 0);
            if ($scope === 'mine') $query->where('eid', $account['username']);
            elseif ($scope === 'improve') {
                if (!(int)$account['admin_id']) $this->fail('当前身份不能查看改善任务', 403);
                $ehs = $this->isEhs($account);
                $query->where(function ($q) use ($account, $ehs) {
                    $q->where('owner', $account['admin_id']);
                    if ($ehs) $q->whereOr('status', 'in', [1,3]);
                });
            } else $this->fail('列表类型不正确');
            $total = (clone $query)->count();
            $rows = $query->order('id desc')->page($page, 10)->select()->toArray();
            return $this->result(['items'=>array_map(function ($row) use ($account) { return $this->present($row, $account); }, $rows), 'total'=>$total, 'page'=>$page]);
        });
    }
    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, true));
        });
    }
    private function text($key, $length) {
        $value = $this->requiredText($key, $length * 4);
        if (mb_strlen($value) > $length) $this->fail('内容过长，请缩短后重试');
        return $value;
    }
    protected function stagingDirectory() { return $this->app->getRuntimePath() . 'safety-risk-uploads/'; }
    public function upload() {
        return $this->guarded(function () {
            $this->requireMethod('POST'); $account = $this->current();
            $kind = $this->request->post('kind'); $recordId = 0;
            if ($kind === 'feedback') {
                $row = $this->row($account, $this->request->post('id'));
                if ((int)$row['status'] !== 2 || !$this->isOwner($account, $row)) $this->fail('当前风险不能上传整改图片', 403);
                $recordId = (int)$row['id'];
            } elseif ($kind !== 'issue') $this->fail('图片类型不正确');
            $file = $this->request->file('image');
            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图片');
            $directory = $this->stagingDirectory();
            if (!is_dir($directory) && !mkdir($directory, 0700, true) && !is_dir($directory)) $this->fail('图片暂存失败', 503);
            // 只清理本模块随机命名、超过一天的暂存文件，不涉及历史附件。
            foreach (glob($directory . '*') ?: [] as $old) {
                if (preg_match('/^[a-f0-9]{48}\.(jpg|png|webp)$/', basename($old)) && is_file($old) && filemtime($old) < time() - 86400) @unlink($old);
            }
            $reference = bin2hex(random_bytes(24)); $name = $reference . '.' . $types[$mime];
            $file->move($directory, $name);
            try { $this->cache()->set($this->cacheKey('safety:upload:' . $reference), ['username'=>$account['username'], 'recordId'=>$recordId, 'kind'=>$kind, 'name'=>$name], 86400); }
            catch (\Throwable $error) { @unlink($directory . $name); throw $error; }
            return $this->result(['reference'=>$reference]);
        });
    }
    private function references($account, $recordId, $kind) {
        $references = $this->request->post('uploads', []);
        if (!is_array($references) || count($references) > 3) $this->fail('每组最多3张图片');
        $uploads = [];
        foreach ($references as $reference) {
            if (!is_string($reference) || !preg_match('/^[a-f0-9]{48}$/', $reference) || isset($uploads[$reference])) $this->fail('图片引用不正确');
            $upload = $this->cache()->get($this->cacheKey('safety:upload:' . $reference));
            if (!$upload || $upload['username'] !== $account['username'] || $upload['recordId'] !== $recordId || $upload['kind'] !== $kind || !is_file($this->stagingDirectory() . $upload['name'])) $this->fail('图片已失效或不属于本次提交，请重新选择');
            $uploads[$reference] = $upload;
        }
        return $uploads;
    }
    private function attach($id, $kind, $uploads, &$savedPaths) {
        foreach ($uploads as $upload) {
            $path = Filesystem::disk('public')->putFileAs('wx_upload/' . date('Ymd') . '/safe_risk', new \think\File($this->stagingDirectory() . $upload['name']), $upload['name']);
            if (!$path) $this->fail('图片保存失败', 503);
            $savedPaths[] = $path;
            Db::name('img_list')->insert(['mid'=>6, 'mark'=>$id, 'status'=>$kind === 'issue' ? 1 : 2, 'img'=>'/storage/' . str_replace('\\', '/', $path)]);
        }
    }
    private function consume($uploads) {
        foreach ($uploads as $reference=>$upload) {
            @unlink($this->stagingDirectory() . $upload['name']);
            try { $this->cache()->delete($this->cacheKey('safety:upload:' . $reference)); }
            catch (\Throwable $error) { Log::warning('oms safety upload cache cleanup failed'); }
        }
    }
    private function withUploadLock($account, $action) {
        // ponytail: 同账号图片提交串行，防止同一暂存引用关联多条记录；低频提交使用现有数据库锁。
        $key = 'safety:' . substr(hash('sha256', $this->cacheKey((string)$account['username'])), 0, 48);
        $lock = Db::query('SELECT GET_LOCK(?, 5) AS acquired', [$key]);
        if ((int)$lock[0]['acquired'] !== 1) $this->fail('提交处理中，请稍后重试', 409);
        try { return $action(); } finally { Db::query('SELECT RELEASE_LOCK(?)', [$key]); }
    }
    private function withWriteLock($action) {
        // 旧业务表保持MyISAM：表锁串行写入，异常用原记录补偿；不能保证进程/数据库崩溃时原子提交。
        $prefix = $this->app->config->get('database.connections.' . $this->app->config->get('database.default') . '.prefix');
        if (!preg_match('/^[a-zA-Z0-9_]*$/', $prefix)) throw new \RuntimeException('Invalid table prefix');
        Db::query('SELECT 1'); $connection = Db::connect()->getConnection(); $pdo = $connection->getPdo();
        $pdo->exec('LOCK TABLES `'.$prefix.'onsite_safe_list` WRITE, `'.$prefix.'img_list` WRITE, `'.$prefix.'admin_user` READ');
        try { return $action(); } finally { $connection->free(); $pdo->exec('UNLOCK TABLES'); }
    }
    public function create() {
        return $this->guarded(function () {
            $this->requireMethod('POST'); $account = $this->current();
            $area = $this->text('area', 50); $description = $this->text('issueDesc', 255);
            $tid = null;
            if ((int)$account['user_id'] > 0) {
                $tid = $this->integer($this->request->post('teamId'), '班组');
                $team = Db::name('team_list')->where(['id'=>$tid, 'status'=>1])->find();
                if (!$team) $this->fail('班组已停用，请重新选择');
                $teamName = $team['team'];
            } else {
                $admin = Db::name('admin_user')->find($account['admin_id']);
                $teamName = (Db::name('admin_org')->where('id', $admin['dpt'])->value('title') ?: '未设置部门') . '-' . (Db::name('admin_org')->where('id', $admin['job'])->value('title') ?: '未设置职位');
            }
            if (mb_strlen($teamName) > 50) $this->fail('班组或部门职位名称超过50字，请联系管理员');
            $name = $this->profileData($account)['name'];
            if (mb_strlen($name) > 50) $this->fail('人员姓名过长，请联系管理员');
            $row = ['mid'=>(int)$account['user_id'] ?: null, 'tid'=>$tid, 'eid'=>$account['username'], 'ename'=>$name, 'team'=>$teamName, 'area'=>$area, 'issue_desc'=>$description, 'create_date'=>strtotime('today'), 'update'=>time(), 'status'=>1, 'owner'=>0];
            $row = $this->withUploadLock($account, function () use ($account, $row) {
                $uploads = $this->references($account, 0, 'issue'); $paths = [];
                try {
                    $row['id'] = $this->withWriteLock(function () use ($row, $uploads, &$paths) {
                        $id = Db::name('onsite_safe_list')->insertGetId($row);
                        try { $this->attach($id, 'issue', $uploads, $paths); }
                        catch (\Throwable $error) {
                            Db::name('img_list')->where(['mid'=>6, 'mark'=>$id])->delete();
                            Db::name('onsite_safe_list')->where('id', $id)->delete();
                            throw $error;
                        }
                        return $id;
                    });
                } catch (\Throwable $error) { foreach ($paths as $path) Filesystem::disk('public')->delete($path); throw $error; }
                $this->consume($uploads); return $row;
            });
            $this->notify($row, '新增风险，请分配责任人', $this->ehsRecipients());
            return $this->result(['id'=>(int)$row['id']]);
        });
    }
    private function mutate($action) {
        $this->requireMethod('POST'); $account = $this->current();
        $row = $this->withWriteLock(function () use ($account, $action) {
            $row = $this->row($account, $this->request->post('id'));
            $images = Db::name('img_list')->where(['mid'=>6, 'mark'=>$row['id']])->select()->toArray();
            try {
                $changes = $action($row, $account);
                Db::name('onsite_safe_list')->where('id', $row['id'])->update($changes);
                return array_merge($row, $changes);
            } catch (\Throwable $error) {
                Db::name('onsite_safe_list')->where('id', $row['id'])->update($row);
                $ids = array_column($images, 'id');
                $added = Db::name('img_list')->where(['mid'=>6, 'mark'=>$row['id']]);
                if ($ids) $added->whereNotIn('id', $ids);
                $added->delete();
                foreach ($images as $image) Db::name('img_list')->where('id', $image['id'])->update(['status'=>$image['status']]);
                throw $error;
            }
        });
        return $row;
    }
    public function remove() {
        return $this->guarded(function () {
            $this->mutate(function ($row, $account) {
                if ((int)$row['status'] !== 1 || (string)$row['eid'] !== (string)$account['username']) $this->fail('只能删除本人待分配的风险', 403);
                return ['status'=>0];
            });
            return $this->result();
        });
    }
    public function assign() {
        return $this->guarded(function () {
            $row = $this->mutate(function ($row, $account) {
                if (!$this->isEhs($account)) $this->fail('只有EHS人员可以分配', 403);
                if ((int)$row['status'] !== 1) $this->fail('风险已分配，请刷新', 409);
                $owner = $this->integer($this->request->post('ownerId'), '责任人');
                if (!Db::name('admin_user')->where(['id'=>$owner, 'status'=>1])->find()) $this->fail('请选择启用的责任人');
                return ['owner'=>$owner, 'status'=>2];
            });
            $this->notify($row, '指定您为风险整改责任人', $this->ownerRecipients($row));
            return $this->result();
        });
    }
    public function feedback() {
        return $this->guarded(function () {
            $this->requireMethod('POST'); $account = $this->current();
            $row = $this->withUploadLock($account, function () use ($account) {
                $paths = []; $uploads = [];
                try {
                    $row = $this->mutate(function ($row, $account) use (&$paths, &$uploads) {
                        if (!$this->isOwner($account, $row)) $this->fail('只有当前责任人可以整改', 403);
                        if ((int)$row['status'] !== 2) $this->fail('风险状态已变化，请刷新', 409);
                        $action = $this->text('action', 255); $dateText = $this->requiredText('dueDate', 10);
                        $date = \DateTime::createFromFormat('!Y-m-d', $dateText);
                        if (!$date || $date->format('Y-m-d') !== $dateText || $date->getTimestamp() < strtotime('today -3 days')) $this->fail('请选择有效完成日期（最早为三天前）');
                        $mode = $this->request->post('mode'); if (!in_array($mode, ['save','submit'], true)) $this->fail('提交方式不正确');
                        $keep = $this->request->post('retainedImageIds', []);
                        if (!is_array($keep)) $this->fail('保留图片不正确');
                        $keep = array_map(function ($id) { return $this->integer($id, '图片编号'); }, $keep);
                        if (count(array_unique($keep)) !== count($keep)) $this->fail('图片重复');
                        $existing = array_map('intval', Db::name('img_list')->where(['mid'=>6, 'mark'=>$row['id'], 'status'=>2])->column('id'));
                        if (array_diff($keep, $existing)) $this->fail('图片不属于当前风险');
                        $uploads = $this->references($account, (int)$row['id'], 'feedback');
                        // 历史超过3张可原样保留，但不能继续添加。
                        if (count($keep) + count($uploads) > 3 && ($uploads || count($keep) !== count($existing))) $this->fail('整改图片最多3张');
                        $removed = array_diff($existing, $keep);
                        if ($removed) Db::name('img_list')->whereIn('id', $removed)->update(['status'=>0]);
                        $this->attach($row['id'], 'feedback', $uploads, $paths);
                        return ['action'=>$action, 'due_date'=>$date->getTimestamp(), 'status'=>$mode === 'submit' ? 3 : 2];
                    });
                } catch (\Throwable $error) { foreach ($paths as $path) Filesystem::disk('public')->delete($path); throw $error; }
                $this->consume($uploads); return $row;
            });
            if ((int)$row['status'] === 3) $this->notify($row, '整改已提交，请确认完成', $this->ehsRecipients());
            return $this->result();
        });
    }
    public function confirm() {
        return $this->guarded(function () {
            $row = $this->mutate(function ($row, $account) {
                if (!$this->isEhs($account)) $this->fail('只有EHS人员可以确认', 403);
                if ((int)$row['status'] !== 3) $this->fail('风险不是待确认状态，请刷新', 409);
                return ['status'=>4];
            });
            $this->notify($row, '风险整改已确认完成', array_merge($this->ownerRecipients($row), $this->reporterRecipients($row)));
            return $this->result();
        });
    }
    private function ehsRecipients() {
        try { return $this->templateService()->admins(Db::name('admin_user')->where(['dpt'=>53,'status'=>1])->column('id')); }
        catch (\Throwable $error) { Log::warning('oms safety EHS recipients unavailable'); return []; }
    }
    private function ownerRecipients($row) {
        try { return $this->templateService()->admins([$row['owner']]); }
        catch (\Throwable $error) { Log::warning('oms safety owner recipients unavailable'); return []; }
    }
    private function reporterRecipients($row) {
        try { return $this->templateService()->employees([$row['eid']]); }
        catch (\Throwable $error) { Log::warning('oms safety reporter recipients unavailable'); return []; }
    }
    protected function notify($row, $title, $recipients) {
        try {
            $service=$this->templateService();$page='pages/safety-risk/detail?id='.$row['id'];
            if ((int)$row['status']===3) return $service->send('workflow_pending',$recipients,[
                'name'=>'安全风险整改审核','initiator'=>$service->adminName($row['owner']),'time'=>$service::time(time()),
                'reason'=>$row['issue_desc'],'step'=>'整改完成审核',
            ],$page);
            if ((int)$row['status']===4) return $service->send('workflow_approved',$recipients,[
                'name'=>'安全风险整改审核','approver'=>$this->profileData($this->current())['name'],'number'=>(string)$row['id'],'time'=>$service::time(time()),
            ],$page);
            return $service->send('work_order_pending',$recipients,[
                'notice'=>$title,'number'=>(string)$row['id'],'time'=>$service::time(time()),'name'=>'安全风险整改',
            ],$page);
        } catch (\Throwable $error) { Log::warning('oms safety notification unavailable: '.$error->getMessage()); return false; }
    }
}
