<?php

declare(strict_types=1);

namespace app\admin\controller\split;

use app\admin\model\split\Link as LinkModel;
use app\admin\model\split\Ticket as TicketModel;
use app\common\controller\Backend;
use app\common\service\SplitTicketRuleService;
use app\common\service\SplitTicketSyncLogger;
use app\common\service\SplitTicketSyncService;
use think\Db;
use think\Lang;
use think\Loader;
use think\Exception;
use think\exception\DbException;
use think\exception\PDOException;
use think\exception\ValidateException;
use think\response\Json;

/**
 * 工单管理
 *
 * @icon fa fa-ticket
 * @remark 分流工单管理，关联分流链接
 */
class Ticket extends Backend
{
    /** @var \app\admin\model\split\Ticket */
    protected $model = null;

    protected $searchFields = 'ticket_name,ticket_url,ticket_total';

    protected $dataLimit = 'personal';

    protected $modelValidate = true;

    protected $modelSceneValidate = true;

    /** @var string[] 无需鉴权的方法 */
    protected $noNeedRight = ['script'];

    /** @var string patches 视图目录 */
    private const PATCH_VIEW_DIR = 'patches/application/admin/view/split/ticket/';

    public function _initialize()
    {
        parent::_initialize();

        $lang = $this->request->langset();
        $lang = preg_match('/^([a-zA-Z\-_]{2,10})$/i', $lang) ? $lang : 'zh-cn';
        $this->model = new \app\admin\model\split\Ticket();
        $this->view->assign('ticketTypeList', $this->model->getTicketTypeList());
        $this->view->assign('numberTypeList', $this->model->getNumberTypeList());
        $this->view->assign('statusList', $this->model->getStatusList());
        $this->view->assign('splitLinkList', $this->buildSplitLinkList());
        $this->assignconfig('ticketTypeList', $this->model->getTicketTypeList());
        $this->assignconfig('numberTypeList', $this->model->getNumberTypeList());
        $this->assignconfig('statusList', $this->model->getStatusList());
        $this->assignconfig('splitLinkFilterList', $this->buildTicketSplitLinkFilterList());
        $this->assignconfig([
            'syncConfirmMsg'           => __('Sync confirm'),
            'syncBackgroundStartedMsg' => __('Sync background started'),
            'syncInProgressMsg'        => __('Sync in progress'),
            'syncTicketStartedMsg'     => __('Sync ticket started'),
        ]);

        $this->setupPatchFrontend();
    }

    /**
     * 加载工单语言包（优先 patches）
     */
    protected function loadlang($name): void
    {
        $lang = $this->request->langset();
        $lang = preg_match('/^([a-zA-Z\-_]{2,10})$/i', $lang) ? $lang : 'zh-cn';
        $name = Loader::parseName($name);
        $name = preg_match('/^([a-zA-Z0-9_\.\/]+)$/i', $name) ? $name : 'index';

        $files = [
            APP_PATH . 'admin/lang/' . $lang . '/' . str_replace('.', '/', $name) . '.php',
            ROOT_PATH . 'patches/application/admin/lang/' . $lang . '/split/ticket.php',
        ];
        $loaded = false;
        foreach ($files as $file) {
            if (is_file($file)) {
                Lang::load($file);
                $loaded = true;
            }
        }
        if (!$loaded) {
            parent::loadlang($name);
        }
    }

    /**
     * 未部署 JS 时指向 script 接口
     */
    private function setupPatchFrontend(): void
    {
        $patchJs = ROOT_PATH . 'patches/public/assets/js/backend/split/ticket.js';
        $publicJs = ROOT_PATH . 'public/assets/js/backend/split/ticket.js';
        $usePatchJs = is_file($patchJs) && (
            !is_file($publicJs) || filemtime($patchJs) >= filemtime($publicJs)
        );
        if (!$usePatchJs) {
            return;
        }

        $cfg = is_array($this->view->config ?? null) ? $this->view->config : [];
        $version = (string) \think\Config::get('site.version');
        // 使用站内相对路径，避免生产环境 domain() 与反向代理/HTTPS 不一致导致 JS 跨域丢 Cookie
        $scriptUrl = (string) url('split.ticket/script', ['v' => $version], '', false);
        $cfg['jsname'] = $scriptUrl;
        $this->view->assign('config', $cfg);
        $this->view->config = $cfg;
    }

    /**
     * @return array<int, array<string, string>>
     */
    private function buildSplitLinkList(): array
    {
        $query = (new LinkModel())->where('status', 'normal')->order('id', 'desc');
        if ($this->dataLimit) {
            $adminIds = $this->getDataLimitAdminIds();
            if (is_array($adminIds)) {
                $query->where('admin_id', 'in', $adminIds);
            }
        }
        $list = [];
        foreach ($query->select() as $row) {
            $code = (string) $row['link_code'];
            $desc = (string) $row['description'];
            $label = $desc !== '' ? $code . ' - ' . $desc : $code;
            $list[] = [
                'id'    => (string) $row['id'],
                'label' => $label,
            ];
        }
        return $list;
    }

    /**
     * 已有工单关联的分流链接（供列表筛选下拉）
     *
     * @return array<string, string> id => label
     */
    private function buildTicketSplitLinkFilterList(): array
    {
        $ticketTable = $this->model->getTable();
        $linkTable = (new LinkModel())->getTable();
        $query = Db::table($ticketTable)
            ->alias('t')
            ->join($linkTable . ' l', 't.split_link_id = l.id')
            ->where('t.split_link_id', '>', 0)
            ->group('t.split_link_id')
            ->field('l.id,l.link_code,l.description')
            ->order('l.id', 'desc');
        if ($this->dataLimit) {
            $adminIds = $this->getDataLimitAdminIds();
            if (is_array($adminIds)) {
                $query->where('t.admin_id', 'in', $adminIds);
            }
        }
        $list = [];
        foreach ($query->select() as $row) {
            $code = (string) $row['link_code'];
            $desc = (string) $row['description'];
            $label = $desc !== '' ? $code . ' - ' . $desc : $code;
            $list[(string) $row['id']] = $label;
        }
        return $list;
    }

    /**
     * 按当前筛选条件汇总列表统计列
     *
     * @param callable|array<mixed> $where buildparams() 返回的闭包或条件数组
     * @return array<string, int|float|string>
     */
    private function buildListSummary($where): array
    {
        $stats = $this->model
            ->where($where)
            ->fieldRaw(
                'COALESCE(SUM(ticket_total), 0) AS sum_ticket_total,'
                . ' COALESCE(SUM(complete_count), 0) AS sum_complete_count,'
                . ' COALESCE(SUM(speed_per_hour), 0) AS sum_speed_per_hour,'
                . ' COALESCE(AVG(CASE WHEN ticket_total > 0 THEN complete_count * 100.0 / ticket_total ELSE 0 END), 0) AS avg_ticket_progress'
            )
            ->find();
        if (!$stats) {
            return [
                'ticket_total'         => 0,
                'complete_count'       => 0,
                'speed_per_hour'       => '0.00',
                'ticket_progress_text' => '0.00%',
            ];
        }
        return [
            'ticket_total'         => (int) ($stats['sum_ticket_total'] ?? 0),
            'complete_count'       => (int) ($stats['sum_complete_count'] ?? 0),
            'speed_per_hour'       => number_format((float) ($stats['sum_speed_per_hour'] ?? 0), 2, '.', ''),
            'ticket_progress_text' => number_format((float) ($stats['avg_ticket_progress'] ?? 0), 2, '.', '') . '%',
        ];
    }

    private function fetchPatch(string $template): string
    {
        $patchFile = ROOT_PATH . self::PATCH_VIEW_DIR . $template . '.html';
        $appFile = APP_PATH . 'admin/view/split/ticket/' . $template . '.html';
        if (is_file($patchFile)) {
            $file = $patchFile;
        } elseif (is_file($appFile)) {
            $file = $appFile;
        } else {
            $this->error('模板文件不存在');
        }
        return (string) $this->view->fetch($file);
    }

    /**
     * @return string|Json
     * @throws DbException
     * @throws \think\Exception
     */
    public function index()
    {
        $this->request->filter(['strip_tags', 'trim']);
        if (false === $this->request->isAjax()) {
            return $this->fetchPatch('index');
        }
        if ($this->request->request('keyField')) {
            return $this->selectpage();
        }
        [$where, $sort, $order, $offset, $limit] = $this->buildparams();
        $ticketTable = $this->model->getTable();
        $clickSub = TicketModel::buildClickCountSubQuery();
        // 勿 with(splitLink)：eagerlyType=0 为 JOIN 预载入，会重写 field 导致子查询 SQL 语法错误
        $list = $this->model
            ->field($ticketTable . '.*,' . $clickSub)
            ->where($where)
            ->order($sort, $order)
            ->paginate($limit);
        $result = [
            'total'   => $list->total(),
            'rows'    => $list->items(),
            'summary' => $this->buildListSummary($where),
        ];
        return json($result);
    }

    /**
     * 同步统计字段仅由接口写入，禁止表单提交篡改
     *
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    protected function preExcludeFields($params): array
    {
        $params = parent::preExcludeFields($params);
        unset(
            $params['complete_count'],
            $params['inbound_count'],
            $params['speed_per_hour'],
            $params['number_count'],
            $params['number_offline_count'],
            $params['number_banned_count'],
            $params['online_count'],
            $params['sync_status'],
            $params['sync_time'],
            $params['sync_message'],
            $params['sync_success_time'],
            $params['sync_success_mode'],
            $params['sync_fail_count'],
            $params['speed_snapshot_count'],
            $params['speed_snapshot_time'],
            $params['click_count']
        );
        return $params;
    }

    /**
     * 手动同步选中工单
     */
    public function sync(): void
    {
        if (!$this->request->isPost()) {
            $this->error(__('Invalid parameters'));
        }
        $ids = $this->request->post('ids', '');
        if ($ids === '') {
            $this->error(__('Parameter %s can not be empty', 'ids'));
        }
        $adminIds = $this->getDataLimitAdminIds();
        $pk = $this->model->getPk();
        $list = $this->model->where($pk, 'in', $ids)->select();
        if (!$list || count($list) === 0) {
            $this->error(__('No Results were found'));
        }

        SplitTicketSyncLogger::log('web', 'manual sync request', [
            'ids'      => $ids,
            'appDebug' => SplitTicketSyncLogger::isEnabled(),
        ]);

        $service = new SplitTicketSyncService();
        $ok = 0;
        $fail = 0;
        $messages = [];

        foreach ($list as $row) {
            if (is_array($adminIds) && !in_array((int) $row[$this->dataLimitField], $adminIds, true)) {
                $fail++;
                $messages[] = '#' . $row['id'] . ': 无权限';
                continue;
            }
            $result = $service->syncOne((int) $row['id'], true);
            if ($result['success']) {
                $ok++;
            } else {
                $fail++;
                $messages[] = '#' . $row['id'] . ': ' . ($result['message'] ?? '失败');
            }
        }

        $summary = sprintf('成功 %d 条，失败 %d 条', $ok, $fail);
        if ($fail > 0) {
            $summary .= '；' . implode('；', array_slice($messages, 0, 5));
        }
        $this->success($summary);
    }

    /**
     * 批量更新：工单状态变更时联动号码
     *
     * @param string $ids
     */
    public function multi($ids = '')
    {
        if (!$this->request->isPost()) {
            $this->error(__('Invalid parameters'));
        }
        $ids = $ids ?: $this->request->post('ids', '');
        if ($ids === '') {
            $this->error(__('Parameter %s can not be empty', 'ids'));
        }

        $params = $this->request->post('params', '');
        parse_str((string) $params, $values);
        $ruleService = new SplitTicketRuleService();

        if (isset($values['status'])) {
            $pk = $this->model->getPk();
            $adminIds = $this->getDataLimitAdminIds();
            $query = $this->model->where($pk, 'in', $ids);
            if (is_array($adminIds)) {
                $query->where($this->dataLimitField, 'in', $adminIds);
            }
            $rows = $query->select();
            if (!$rows || count($rows) === 0) {
                $this->error(__('No Results were found'));
            }

            $count = 0;
            Db::startTrans();
            try {
                foreach ($rows as $item) {
                    $count += $item->allowField(true)->isUpdate(true)->save($values);
                }
                Db::commit();
            } catch (ValidateException $e) {
                Db::rollback();
                $this->error($e->getMessage());
            } catch (PDOException|Exception $e) {
                Db::rollback();
                $this->error($e->getMessage());
            }

            foreach ($rows as $row) {
                $fresh = $this->model->get($row['id']);
                if ($fresh) {
                    $ruleService->syncNumbersWithTicketStatus($fresh);
                }
            }

            if ($count > 0) {
                $this->success();
            }
            $this->error(__('No rows were updated'));
        }

        parent::multi($ids);
    }

    /**
     * @return string
     * @throws DbException
     */
    public function add()
    {
        if (false === $this->request->isPost()) {
            $copyRow = $this->buildCopyRowData();
            if ($copyRow !== null) {
                $this->view->assign('row', $copyRow);
            }
            return $this->fetchPatch('add');
        }
        $params = $this->request->post('row/a', []);
        if ($params === []) {
            $this->error(__('Parameter %s can not be empty', ''));
        }
        $params = $this->preExcludeFields($params);
        if (($params['number_type'] ?? '') !== 'custom') {
            $params['number_type_custom'] = '';
        }
        if ($this->dataLimit && $this->dataLimitFieldAutoFill) {
            $params[$this->dataLimitField] = $this->auth->id;
        }
        $result = false;
        Db::startTrans();
        try {
            if ($this->modelValidate) {
                $name = str_replace('\\model\\', '\\validate\\', get_class($this->model));
                $validate = $this->modelSceneValidate ? $name . '.add' : $name;
                $this->model->validateFailException()->validate($validate);
            }
            $result = $this->model->allowField(true)->save($params);
            Db::commit();
        } catch (ValidateException $e) {
            Db::rollback();
            $this->error($e->getMessage());
        } catch (PDOException|Exception $e) {
            Db::rollback();
            $this->error($e->getMessage());
        }
        if ($result === false) {
            $this->error(__('No rows were inserted'));
        }
        $this->success('', null, ['id' => (int) $this->model->id]);
    }

    /**
     * 从 copy_id 构建添加工单表单的预填数据（仅复制业务字段，不含同步统计）
     *
     * @return array<string, mixed>|null
     */
    private function buildCopyRowData(): ?array
    {
        $copyId = (int) $this->request->get('copy_id/d', 0);
        if ($copyId <= 0) {
            return null;
        }

        $row = $this->model->get($copyId);
        if (!$row) {
            return null;
        }

        $adminIds = $this->getDataLimitAdminIds();
        if (is_array($adminIds) && !in_array((int) $row[$this->dataLimitField], $adminIds, true)) {
            return null;
        }

        $data = $row->toArray();
        $copyFields = [
            'ticket_type',
            'ticket_name',
            'ticket_url',
            'ticket_total',
            'split_link_id',
            'number_type',
            'number_type_custom',
            'order_limit',
            'assign_ratio',
            'account',
            'password',
        ];
        $copyRow = [];
        foreach ($copyFields as $field) {
            if (array_key_exists($field, $data)) {
                $copyRow[$field] = $data[$field];
            }
        }

        $copyRow['start_time'] = $data['start_time_text'] ?? '';
        $copyRow['end_time'] = $data['end_time_text'] ?? '';

        $name = trim((string) ($copyRow['ticket_name'] ?? ''));
        if ($name !== '' && mb_strpos($name, ' (副本)') === false) {
            $copyRow['ticket_name'] = $name . ' (副本)';
        }

        return $copyRow;
    }

    /**
     * @param string|null $ids
     * @return string
     * @throws DbException
     */
    public function edit($ids = null)
    {
        $row = $this->model->get($ids);
        if (!$row) {
            $this->error(__('No Results were found'));
        }
        $adminIds = $this->getDataLimitAdminIds();
        if (is_array($adminIds) && !in_array((int) $row[$this->dataLimitField], $adminIds, true)) {
            $this->error(__('You have no permission'));
        }
        if (false === $this->request->isPost()) {
            $rowData = $row->toArray();
            $rowData['start_time'] = $rowData['start_time_text'] ?? '';
            $rowData['end_time'] = $rowData['end_time_text'] ?? '';
            $this->view->assign('row', $rowData);
            return $this->fetchPatch('edit');
        }
        $params = $this->request->post('row/a', []);
        if ($params === []) {
            $this->error(__('Parameter %s can not be empty', ''));
        }
        $params = $this->preExcludeFields($params);
        if (($params['number_type'] ?? '') !== 'custom') {
            $params['number_type_custom'] = '';
        }
        $oldStatus = (string) ($row['status'] ?? 'hidden');
        $result = false;
        Db::startTrans();
        try {
            if ($this->modelValidate) {
                $name = str_replace('\\model\\', '\\validate\\', get_class($this->model));
                $validate = $this->modelSceneValidate ? $name . '.edit' : $name;
                $row->validateFailException()->validate($validate);
            }
            $result = $row->allowField(true)->save($params);
            Db::commit();
        } catch (ValidateException $e) {
            Db::rollback();
            $this->error($e->getMessage());
        } catch (PDOException|Exception $e) {
            Db::rollback();
            $this->error($e->getMessage());
        }
        if ($result === false) {
            $this->error(__('No rows were updated'));
        }
        if (isset($params['status']) && (string) $params['status'] !== $oldStatus) {
            $fresh = $this->model->get($row['id']);
            if ($fresh) {
                (new SplitTicketRuleService())->syncNumbersWithTicketStatus($fresh);
            }
        }
        $this->success();
    }

    /**
     * 输出后台 JS（patches 未部署到 public 时）
     */
    public function script(): void
    {
        $jsFile = ROOT_PATH . 'patches/public/assets/js/backend/split/ticket.js';
        if (!is_file($jsFile)) {
            $jsFile = ROOT_PATH . 'public/assets/js/backend/split/ticket.js';
        }
        if (!is_file($jsFile)) {
            $this->error('脚本文件不存在');
        }
        $content = file_get_contents($jsFile);
        if ($content === false) {
            $this->error('读取脚本失败');
        }
        $response = response($content, 200, [
            'Content-Type' => 'application/javascript; charset=utf-8',
            'Cache-Control' => 'public, max-age=3600',
        ]);
        throw new \think\exception\HttpResponseException($response);
    }
}
