<?php
declare(strict_types=1);

/**
 * 洗车数据业务模型
 * 通过注入的 HttpClient 与 ypAPI 通信，不直接依赖具体 HTTP 实现
 *
 * ypAPI 响应统一是 {"status":bool, "data":..., "code":...}，跟 PDC 自己的 {code,data,msg} 不是一套，
 * 本类负责拆包：status=false 抛 MvcException，成功时只把 data 交出去。
 *
 * 号码清洗两条链路（接口实测确认，不是照文档抄的）：
 *   TCD  tcd/cleanData        POST 表单  number → 产品/号码/车型的匹配结果
 *   EPC  tcd/checkNumberData  POST 表单  number → 拿 etk_id/pro_id/grp2_id
 *        etk/cleanData        GET        用上一步的三个 ID → 零件组/位置/图片
 * 两个 tcd/* 接口只认表单编码的 POST（$_POST），GET 和 JSON body 都返回空，所以走 postForm()。
 */
class Wash
{
    /** 竞品品牌优先级，tcd/cleanData 的 facseq 参数默认值 */
    private const CLEAN_FACSEQ = 'TRW,BREMBO,REMSA';

    /** 号码确认相同率，tcd/cleanData 的 rate_number 参数默认值 */
    private const CLEAN_RATE_NUMBER = 70;

    /** 车型确认相同率，tcd/cleanData 的 rate_model 参数默认值 */
    private const CLEAN_RATE_MODEL = 50;

    /** 没写厂商的号码按 OE 号处理，与 WashSheetItem::DEFAULT_VENDOR_NAME 一致 */
    private const DEFAULT_FACTORY = 'OE';

    public function __construct(private HttpClient $http) {}

    /**
     * 从配置构造，适合 Action 层一行初始化
     *   $wash = Wash::fromConfig();
     */
    public static function fromConfig(): self
    {
        $cfg = Mvc::$cfg['ypAPI'] ?? [];
        return new self(new HttpClient(
            $cfg['url']       ?? '',
            $cfg['params']    ?? [],
            120,
            $cfg['sslVerify'] ?? true,
        ));
    }

    // ── 基础请求 ──────────────────────────────────────────────────────────────

    public function get(string $method, array $params): array
    {
        return $this->takeData($this->http->get($method, $params), $method, $params);
    }

    /**
     * 表单编码 POST，ypAPI 的 POST 接口都读 $_POST，只能用这个
     */
    public function postForm(string $method, array $params): array
    {
        return $this->takeData($this->http->postForm($method, $params), $method, $params);
    }

    /**
     * 拆 ypAPI 的响应包：status=false 抛异常，成功返回 data 部分
     * @throws MvcException
     */
    private function takeData(array $response, string $method, array $params): array
    {
        if (empty($response['status'])) {
            $reason = is_string($response['data'] ?? null) ? $response['data'] : (string) ($response['code'] ?? '');
            throw new MvcException("ypAPI {$method} 调用失败: {$reason}");
        }

        return is_array($response['data'] ?? null) ? $response['data'] : [];
    }

    // ── 号码清洗 ──────────────────────────────────────────────────────────────

    /**
     * 清洗一个号码，一次拿到 TCD 和 EPC 两边的结果
     * checkNumberData 两边都要用（TCD 的分类列表、EPC 的零件组定位），所以只调一次再分给两个摘要
     *
     * @param string $factory 厂商名称，空则按 OE 处理
     * @return array{tcd:array,epc:array} 两个摘要分别 json_encode 存进 wash_sheet_item_tcd / _epc
     */
    public function cleanNumber(string $factory, string $number): array
    {
        $checked = $this->checkNumber($factory, $number);

        return [
            'tcd' => $this->buildTcdResult($checked, $number),
            'epc' => $this->buildEpcResult($checked),
        ];
    }

    /**
     * TCD 结果：checkNumberData 的分类列表（界面上按分类分组显示号码）+ tcd/cleanData 的匹配统计
     */
    private function buildTcdResult(array $checked, string $number): array
    {
        $groups = $this->buildTcdGroups($checked);
        if (!$groups) {
            return ['matched' => false];
        }

        $clean = $this->postForm('tcd/cleanData', [
            'number'      => $number,
            'facseq'      => $this->cleanOption('facseq', self::CLEAN_FACSEQ),
            'rate_number' => $this->cleanOption('rate_number', self::CLEAN_RATE_NUMBER),
            'rate_model'  => $this->cleanOption('rate_model', self::CLEAN_RATE_MODEL),
        ]);

        return [
            'matched' => true,
            'groups'  => $groups,
            'clean'   => $this->summariseClean($clean),
        ];
    }

    /**
     * EPC 结果：先从 checkNumberData 定位零件组，再取该零件组的清洗信息
     */
    private function buildEpcResult(array $checked): array
    {
        $pro = $this->firstEpcProduct($checked);
        if ($pro === null) {
            return ['matched' => false];
        }

        $data = $this->get('etk/cleanData', [
            'etk_id'  => $pro['etk_id']  ?? '',
            'pro_id'  => $pro['pro_id']  ?? '',
            'grp2_id' => $pro['grp2_id'] ?? '',
        ]);

        return $this->summariseEpc($data, $pro);
    }

    /**
     * 号码基本检测（tcd + etk），EPC 清洗的第一步
     * 注意：传多个号码时接口是把结果合并返回的，无法按号码拆回去，所以这里只支持单个号码
     */
    public function checkNumber(string $factory, string $number): array
    {
        return $this->postForm('tcd/checkNumberData', [
            'number' => json_encode([[
                'factory' => $factory !== '' ? $factory : self::DEFAULT_FACTORY,
                'number'  => $number,
            ]], JSON_UNESCAPED_UNICODE),
        ]);
    }

    /**
     * 号码匹配全车件模式车型，返回宜配车型ID列表
     */
    public function getModelByNumber(string $number): array
    {
        return $this->get('number/getModelByNumber', ['number' => $number]);
    }

    // ── 摘要提取 ──────────────────────────────────────────────────────────────

    /**
     * checkNumberData 的 data 里挑出第一条 EPC 产品（etk_id/pro_id/grp2_id 三件套）
     */
    private function firstEpcProduct(array $checked): ?array
    {
        $pros = $checked['data']['etk']['pros'] ?? [];

        return is_array($pros) && isset($pros[0]) && is_array($pros[0]) ? $pros[0] : null;
    }

    /**
     * checkNumberData 的 tcd.ga_chk → 界面上的分类分组
     * 中英文名称都存下来，前端按当前语言选一个显示；每组的号码就是该分类下的厂商件
     * 注意 count 是该分类的号码总数，接口只回前 10 条明细，数量多时 nums 会少于 count
     */
    private function buildTcdGroups(array $checked): array
    {
        $groups = [];

        foreach ($checked['data']['tcd']['ga_chk'] ?? [] as $group) {
            if (!is_array($group)) {
                continue;
            }

            $nums = [];
            foreach ($group['pros'] ?? [] as $pro) {
                if (!empty($pro['pro_number'])) {
                    $nums[] = [
                        'pro_id'  => (string) ($pro['pro_id'] ?? ''),
                        'factory' => (string) ($pro['pro_factory'] ?? ''),
                        'number'  => (string) $pro['pro_number'],
                        'pro_url' => (string) ($pro['pro_url'] ?? ''),
                    ];
                }
            }

            $groups[] = [
                'ga_id'   => (string) ($group['ga_ids'] ?? ''),
                'name_cn' => (string) ($group['ga_name_cn'] ?? ''),
                'name_en' => (string) ($group['ga_name'] ?? ''),
                'count'   => (int) ($group['pro_count'] ?? count($nums)),
                'nums'    => $nums,
            ];
        }

        return $groups;
    }

    /**
     * tcd/cleanData 的响应有 60KB 上下，整包落库会把明细表撑爆也拖慢 grid 查询，
     * 这里只留匹配到的主产品和号码/车型统计，需要完整数据时按 art_id 重新调接口
     */
    private function summariseClean(array $data): array
    {
        $pro = $data['pro'] ?? null;
        if (!is_array($pro) || empty($pro['art_id'])) {
            return ['matched' => false];
        }

        return [
            'matched'     => true,
            'art_id'      => (string) $pro['art_id'],
            'brand'       => (string) ($pro['sup_brand'] ?? ''),
            'number'      => (string) ($pro['art_number'] ?? ''),
            'ga_id'       => (string) ($pro['ga_id'] ?? ''),
            'ga_name'     => (string) ($pro['ga_name_cn'] ?? $pro['ga_name'] ?? ''),
            'pro_url'     => (string) ($pro['pro_url'] ?? ''),
            'num_count'   => (int) ($data['num_count_ok'] ?? 0),
            'model_count' => (int) ($data['model_count'] ?? 0),
            'model_ok'    => (int) ($data['model_oknum_count_ok'] ?? 0),
        ];
    }

    /**
     * etk/cleanData 的响应保留零件组、位置、图片这些能在表格里看的字段
     *
     * renum 是替换号，接口按新→旧排序，所以 renum[0] 就是最新号（界面上打「新」标记）。
     * isnew=1 表示被查的这个号码本身就是最新号（实测：查 5Q0199868J 得 1，查它的旧号 5Q0199868F 得 -1），
     * 跟 renum[0] 的判断是两回事，两个都留着。
     */
    private function summariseEpc(array $data, array $pro): array
    {
        if (empty($data['pro_id'])) {
            return ['matched' => false];
        }

        $oe = [];
        foreach ($pro['num'] ?? [] as $num) {
            if (!empty($num['display'])) {
                $oe[] = [
                    'factory' => (string) ($num['factory'] ?? ''),
                    'number'  => (string) $num['display'],
                ];
            }
        }

        return [
            'matched'    => true,
            'etk_id'     => (string) ($data['etk_id'] ?? $pro['etk_id'] ?? ''),
            'pro_id'     => (string) $data['pro_id'],
            'grp2_id'    => (string) ($data['grp2_id'] ?? ''),
            'grp1_name'  => (string) ($data['grp1_name'] ?? ''),
            'grp2_name'  => (string) ($data['grp2_name'] ?? ''),
            'pro_name'   => (string) ($data['pro_name'] ?? ''),
            'oe'         => $oe,
            // etk/cleanData 不带链接，产品页地址取自 checkNumberData 的 etk 产品；OE 号都指向这一个产品页
            'pro_url'    => (string) ($pro['pro_url'] ?? ''),
            'renum'      => array_values(array_filter((array) ($pro['renum'] ?? []), 'is_string')),
            'isnew'      => (int) ($pro['isnew'] ?? -1),
            'weizhi'     => (string) ($data['weizhi_name'] ?? $data['weizhi'] ?? ''),
            'pic'        => (string) ($data['pic'] ?? ''),
        ];
    }

    /**
     * 清洗参数支持在 config/common.php 的 ypAPI.clean 里覆盖，没配就用类常量默认值
     */
    private function cleanOption(string $name, string|int $default): string|int
    {
        return Mvc::$cfg['ypAPI']['clean'][$name] ?? $default;
    }
}
