<?php

declare(strict_types=1);

namespace app\admin\controller\split;

use app\admin\model\split\Link as LinkModel;
use app\admin\model\split\Number as NumberModel;
use app\common\controller\Backend;
use think\Db;
use think\Exception;
use think\exception\DbException;
use think\exception\PDOException;
use think\exception\ValidateException;
use think\response\Json;

/**
 * 号码管理
 *
 * @icon fa fa-phone
 * @remark 分流号码管理，关联分流链接
 */
class Number extends Backend
{
    /** @var NumberModel */
    protected $model = null;

    protected $searchFields = 'ticket_name,number';

    protected $dataLimit = 'personal';

    /** 关联 splitLink 预载入时需为筛选/排序字段加表别名，避免 status 等列歧义 */
    protected $relationSearch = true;

    protected $modelValidate = true;

    protected $modelSceneValidate = true;

    /** @var string */
    protected $multiFields = 'status,manual_manage';

    /** @var string[] */
    protected $noNeedRight = ['script'];

    /** @var string */
    private const PATCH_VIEW_DIR = 'patches/application/admin/view/split/number/';

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

        $lang = $this->request->langset();
        $lang = preg_match('/^([a-zA-Z\-_]{2,10})$/i', $lang) ? $lang : 'zh-cn';
        $langFile = ROOT_PATH . 'patches/application/admin/lang/' . $lang . '/split/number.php';
        if (is_file($langFile)) {
            \think\Lang::load($langFile);
        }

        $this->model = new NumberModel();
        $this->view->assign('numberTypeList', $this->model->getNumberTypeList());
        $this->view->assign('statusList', $this->model->getStatusList());
        $this->view->assign('manualManageList', $this->model->getManualManageList());
        $this->view->assign('splitLinkList', $this->buildSplitLinkList());
        $this->assignconfig('numberTypeList', $this->model->getNumberTypeList());
        $this->assignconfig('statusList', $this->model->getStatusList());
        $this->assignconfig('manualManageList', $this->model->getManualManageList());
        $this->assignconfig('platformStatusList', $this->model->getPlatformStatusList());
        $this->assignconfig('splitLinkFilterList', $this->buildNumberSplitLinkFilterList());
        $this->assignconfig('splitLinkSelectList', $this->buildSplitLinkSelectConfig());

        $this->setupPatchFrontend();
    }

    private function setupPatchFrontend(): void
    {
        $patchJs = ROOT_PATH . 'patches/public/assets/js/backend/split/number.js';
        $publicJs = ROOT_PATH . 'public/assets/js/backend/split/number.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.number/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>
     */
    private function buildNumberSplitLinkFilterList(): array
    {
        $numberTable = $this->model->getTable();
        $linkTable = (new LinkModel())->getTable();
        $query = Db::table($numberTable)
            ->alias('n')
            ->join($linkTable . ' l', 'n.split_link_id = l.id')
            ->where('n.split_link_id', '>', 0)
            ->group('n.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('n.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;
    }

    /**
     * 全部分流链接（供批量操作弹窗可搜索下拉）
     *
     * @return array<string, string>
     */
    private function buildSplitLinkSelectConfig(): array
    {
        $list = [];
        foreach ($this->buildSplitLinkList() as $row) {
            $list[(string) $row['id']] = (string) $row['label'];
        }
        return $list;
    }

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

    /**
     * 列表状态开关：手动关闭时标记 manual_manage=1，防止同步自动恢复开启
     *
     * @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);
        if (!isset($values['status'])) {
            parent::multi($ids);
            return;
        }

        if ((string) $values['status'] === 'hidden') {
            $values['manual_manage'] = 1;
        } elseif ((string) $values['status'] === 'normal') {
            $values['manual_manage'] = 0;
        }

        $adminIds = $this->getDataLimitAdminIds();
        if (is_array($adminIds)) {
            $this->model->where($this->dataLimitField, 'in', $adminIds);
        }
        $count = 0;
        Db::startTrans();
        try {
            $list = $this->model->where($this->model->getPk(), 'in', $ids)->select();
            foreach ($list 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());
        }
        if ($count > 0) {
            $this->success();
        }
        $this->error(__('No rows were updated'));
    }

    /**
     * @return string|Json
     * @throws DbException
     */
    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();
        $list = $this->model
            ->with(['splitLink'])
            ->where($where)
            ->order($sort, $order)
            ->paginate($limit);
        $result = ['total' => $list->total(), 'rows' => $list->items()];
        return json($result);
    }

    /**
     * @return string
     * @throws DbException
     */
    public function add()
    {
        if (false === $this->request->isPost()) {
            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'] = '';
        }

        $numbersText = (string) ($params['numbers'] ?? '');
        unset($params['numbers']);
        $numberList = NumberModel::parseNumbersText($numbersText);
        if ($numberList === []) {
            $this->error(__('Please fill at least one number'));
        }

        $validateData = array_merge($params, ['numbers' => $numbersText]);
        $adminId = $this->dataLimit && $this->dataLimitFieldAutoFill ? (int) $this->auth->id : 0;

        $inserted = 0;
        $skipped = 0;
        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, $validateData);
            }
            $splitLinkId = (int) ($params['split_link_id'] ?? 0);
            $baseRow = [
                'split_link_id'       => $splitLinkId,
                'ticket_name'         => (string) ($params['ticket_name'] ?? ''),
                'number_type'         => (string) ($params['number_type'] ?? ''),
                'number_type_custom'  => (string) ($params['number_type_custom'] ?? ''),
                'status'              => (string) ($params['status'] ?? 'normal'),
                'visit_count'         => 0,
                'inbound_count'       => 0,
                'manual_manage'       => 0,
            ];
            if ($adminId > 0) {
                $baseRow['admin_id'] = $adminId;
            }
            foreach ($numberList as $num) {
                $exists = $this->model
                    ->where('split_link_id', $splitLinkId)
                    ->where('number', $num)
                    ->find();
                if ($exists) {
                    $skipped++;
                    continue;
                }
                $row = $baseRow;
                $row['number'] = $num;
                $item = new NumberModel();
                $item->allowField(true)->isUpdate(false)->save($row);
                $inserted++;
            }
            if ($inserted === 0) {
                throw new Exception(__('All numbers already exist for this link'));
            }
            Db::commit();
        } catch (ValidateException $e) {
            Db::rollback();
            $this->error($e->getMessage());
        } catch (PDOException|Exception $e) {
            Db::rollback();
            $this->error($e->getMessage());
        }

        $msg = __('Inserted %d number(s)', $inserted);
        if ($skipped > 0) {
            $msg .= '，' . __('Skipped %d duplicate(s)', $skipped);
        }
        $this->success($msg);
    }

    /**
     * @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()) {
            $this->view->assign('row', $row);
            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'] = '';
        }

        $newNumber = trim((string) ($params['number'] ?? ''));
        $splitLinkId = (int) ($params['split_link_id'] ?? $row['split_link_id']);
        $exists = $this->model
            ->where('split_link_id', $splitLinkId)
            ->where('number', $newNumber)
            ->where('id', '<>', (int) $row['id'])
            ->find();
        if ($exists) {
            $this->error(__('Number already exists for this link'));
        }

        $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, $params);
            }
            $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'));
        }
        $this->success();
    }

    /**
     * 批量更新号码状态与手动管理
     */
    public function batchupdate(): void
    {
        if (false === $this->request->isPost()) {
            $this->error(__('Invalid parameters'));
        }
        $ids = $this->request->post('ids', '');
        if ($ids === '' || $ids === null) {
            $this->error(__('Parameter %s can not be empty', 'ids'));
        }
        if (!is_array($ids)) {
            $ids = explode(',', (string) $ids);
        }
        $ids = array_filter(array_map('intval', $ids));
        if ($ids === []) {
            $this->error(__('Parameter %s can not be empty', 'ids'));
        }

        $status = (string) $this->request->post('status', '');
        $manualManage = $this->request->post('manual_manage', '');
        $statusList = $this->model->getStatusList();
        if (!isset($statusList[$status])) {
            $this->error(__('Invalid status'));
        }
        if (!in_array((string) $manualManage, ['0', '1'], true)) {
            $this->error(__('Invalid manual manage'));
        }

        $query = $this->model->where('id', 'in', $ids);
        if ($this->dataLimit) {
            $adminIds = $this->getDataLimitAdminIds();
            if (is_array($adminIds)) {
                $query->where('admin_id', 'in', $adminIds);
            }
        }
        $count = $query->update([
            'status'         => $status,
            'manual_manage'  => (int) $manualManage,
        ]);
        if ($count === false || $count === 0) {
            $this->error(__('No rows were updated'));
        }
        $this->success(__('Batch update success'));
    }

    /**
     * 按分流链接 + 号码文本批量开启/关闭/删除
     */
    public function batchoperate(): void
    {
        if (false === $this->request->isPost()) {
            $this->error(__('Invalid parameters'));
        }

        $splitLinkId = (int) $this->request->post('split_link_id', 0);
        $numbersText = (string) $this->request->post('numbers', '');
        $action = (string) $this->request->post('action', '');

        if ($splitLinkId <= 0) {
            $this->error(__('Please select split link'));
        }

        $numberList = NumberModel::parseNumbersText($numbersText);
        if ($numberList === []) {
            $this->error(__('Please fill at least one number'));
        }

        $allowedActions = ['enable', 'disable', 'delete'];
        if (!in_array($action, $allowedActions, true)) {
            $this->error(__('Invalid batch operate action'));
        }

        $linkQuery = (new LinkModel())->where('id', $splitLinkId)->where('status', 'normal');
        if ($this->dataLimit) {
            $adminIds = $this->getDataLimitAdminIds();
            if (is_array($adminIds)) {
                $linkQuery->where('admin_id', 'in', $adminIds);
            }
        }
        if (!$linkQuery->find()) {
            $this->error(__('No Results were found'));
        }

        $query = $this->model
            ->where('split_link_id', $splitLinkId)
            ->where('number', 'in', $numberList);
        if ($this->dataLimit) {
            $adminIds = $this->getDataLimitAdminIds();
            if (is_array($adminIds)) {
                $query->where('admin_id', 'in', $adminIds);
            }
        }

        $count = 0;
        Db::startTrans();
        try {
            if ($action === 'delete') {
                $rows = $query->select();
                foreach ($rows as $row) {
                    if ($row->delete()) {
                        $count++;
                    }
                }
            } else {
                $status = $action === 'enable' ? 'normal' : 'hidden';
                $manualManage = $action === 'enable' ? 0 : 1;
                $count = (int) $query->update([
                    'status'        => $status,
                    'manual_manage' => $manualManage,
                ]);
            }
            Db::commit();
        } catch (PDOException|Exception $e) {
            Db::rollback();
            $this->error($e->getMessage());
        }

        if ($count <= 0) {
            $this->error(__('No matching numbers found'));
        }

        $this->success(sprintf(__('Batch operate success'), $count));
    }

    /**
     * 排除不可由表单提交的字段
     *
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    protected function preExcludeFields($params): array
    {
        $params = parent::preExcludeFields($params);
        unset(
            $params['visit_count'],
            $params['inbound_count'],
            $params['manual_manage'],
            $params['id']
        );
        return $params;
    }

    public function script(): void
    {
        $jsFile = ROOT_PATH . 'patches/public/assets/js/backend/split/number.js';
        if (!is_file($jsFile)) {
            $jsFile = ROOT_PATH . 'public/assets/js/backend/split/number.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);
    }
}
