<?php

declare(strict_types=1);

namespace app\common\service;

use app\admin\model\split\Link;
use app\admin\model\split\Number;
use app\admin\model\split\Ticket;
use think\Collection;

/**
 * 分流链接落地页：查链、选号、拼接跳转 URL、访问计数
 *
 * 号码池：同一 split_link_id 下 status=normal，且所属工单未关闭（ticket.status≠hidden）的号码；
 * 优先选用 ratio_deferred=0，全部触线降权时回退至降权池。
 *
 * random_shuffle=0：跨工单合并池，全局严格轮转；
 * random_shuffle=1：先按各工单开启号码数量比例随机选工单，再在该工单内随机选号。
 */
class SplitRedirectService
{
    private SplitRoundRobinStore $roundRobinStore;

    private SplitNumberVisitRuleService $visitRuleService;

    public function __construct(
        ?SplitRoundRobinStore $roundRobinStore = null,
        ?SplitNumberVisitRuleService $visitRuleService = null
    ) {
        $this->roundRobinStore = $roundRobinStore ?? new SplitRoundRobinStore();
        $this->visitRuleService = $visitRuleService ?? new SplitNumberVisitRuleService();
    }

    /**
     * 根据链接码解析本次应跳转的加好友 URL；无效时返回 null
     *
     * @param string $linkCode  9 位链接码
     * @param string $clientIp  访客 IP（IP 防护开启时用于国家校验）
     */
    public function resolveRedirectUrl(string $linkCode, string $clientIp = ''): ?string
    {
        $linkCode = SplitLinkCodeService::normalize($linkCode);
        if (!SplitLinkCodeService::isValidFormat($linkCode)) {
            return null;
        }

        $link = Link::where('link_code', $linkCode)
            ->where('status', 'normal')
            ->find();
        if (!$link) {
            return null;
        }

        $ipProtect = (int) $link->getAttr('ip_protect');
        $countries = (string) $link->getAttr('countries');
        if (!SplitIpProtectService::isAllowed($ipProtect, $countries, $clientIp)) {
            return null;
        }

        $linkId = (int) $link->getAttr('id');
        $list = $this->loadEligibleNumbers($linkId);
        if ($list === []) {
            return null;
        }

        if (SplitNumberWeighService::isRandomShuffleEnabled($linkId)) {
            $picked = $this->pickNumberRowRandomByTicketWeight($linkId, $list);
        } else {
            $picked = $this->pickNumberRow($linkId, $list);
        }
        if ($picked === null) {
            return null;
        }

        $numberType = is_array($picked) ? (string) ($picked['number_type'] ?? '') : (string) $picked->getAttr('number_type');
        $numberValue = is_array($picked) ? (string) ($picked['number'] ?? '') : (string) $picked->getAttr('number');
        $numberCustom = is_array($picked)
            ? (string) ($picked['number_type_custom'] ?? '')
            : (string) $picked->getAttr('number_type_custom');

        $replyText = '';
        if (in_array($numberType, ['whatsapp', 'telegram'], true)) {
            $replyText = SplitAutoReplyService::pickRandomLine((string) $link->getAttr('auto_reply'));
        }

        $redirectUrl = SplitFriendUrlBuilder::build(
            $numberType,
            $numberValue,
            $numberCustom,
            $replyText
        );
        if ($redirectUrl === '') {
            return null;
        }

        $numberId = is_array($picked) ? (int) ($picked['id'] ?? 0) : (int) $picked->getAttr('id');
        if ($numberId > 0) {
            Number::where('id', $numberId)->setInc('visit_count');
            $this->visitRuleService->recordVisitAfterRedirect($numberId);
        }

        return $redirectUrl;
    }

    /**
     * 可参与选号的号码：status=normal，且 ticket_name 不属于已关闭工单
     *
     * @return list<Number|array<string, mixed>>
     */
    private function loadEligibleNumbers(int $linkId): array
    {
        if ($linkId <= 0) {
            return [];
        }

        /** @var list<string> $hiddenTicketNames */
        $hiddenTicketNames = Ticket::where('split_link_id', $linkId)
            ->where('status', 'hidden')
            ->column('ticket_name');
        $hiddenTicketNames = array_values(array_unique(array_filter(array_map(
            static function ($name): string {
                return trim((string) $name);
            },
            $hiddenTicketNames
        ))));

        $query = Number::where('split_link_id', $linkId)
            ->where('status', 'normal')
            ->order('id', 'asc')
            ->field('id,number,number_type,number_type_custom,ratio_deferred,ticket_name');

        if ($hiddenTicketNames !== []) {
            $query->where('ticket_name', 'not in', $hiddenTicketNames);
        }

        $numbers = $query->select();
        return $numbers instanceof Collection ? $numbers->all() : (array) $numbers;
    }

    /**
     * random_shuffle=1：先按工单开启号码数加权随机选工单，再在该工单内选号
     *
     * @param list<Number|array<string, mixed>> $rows
     * @return array<string, mixed>|Number|null
     */
    private function pickNumberRowRandomByTicketWeight(int $linkId, array $rows)
    {
        $groups = $this->groupNumbersByTicket($rows);
        if ($groups === []) {
            return null;
        }

        $ticketKey = $this->pickTicketGroupKeyWeighted($groups);
        if ($ticketKey === null || !isset($groups[$ticketKey])) {
            return null;
        }

        return $this->pickNumberRow($linkId, $groups[$ticketKey]);
    }

    /**
     * @param list<Number|array<string, mixed>> $rows
     * @return array<string, list<Number|array<string, mixed>>>
     */
    private function groupNumbersByTicket(array $rows): array
    {
        /** @var array<string, list<Number|array<string, mixed>>> $groups */
        $groups = [];
        foreach ($rows as $row) {
            $name = is_array($row)
                ? trim((string) ($row['ticket_name'] ?? ''))
                : trim((string) $row->getAttr('ticket_name'));
            $key = $name !== '' ? $name : '__manual__';
            $groups[$key][] = $row;
        }

        return $groups;
    }

    /**
     * 按各分组号码数量加权随机选一个工单（分组 key）
     *
     * @param array<string, list<Number|array<string, mixed>>> $groups
     */
    private function pickTicketGroupKeyWeighted(array $groups): ?string
    {
        $total = 0;
        foreach ($groups as $rows) {
            $total += count($rows);
        }
        if ($total <= 0) {
            return null;
        }

        $keys = array_keys($groups);
        if (count($keys) === 1) {
            return $keys[0];
        }

        $r = random_int(1, $total);
        $acc = 0;
        foreach ($groups as $key => $rows) {
            $acc += count($rows);
            if ($r <= $acc) {
                return $key;
            }
        }

        return $keys[0];
    }

    /**
     * 双层选号：优先 ratio_deferred=0，否则回退至降权池
     *
     * @param list<Number|array<string, mixed>> $rows
     * @return array<string, mixed>|Number|null
     */
    private function pickNumberRow(int $linkId, array $rows)
    {
        $preferred = [];
        $deferred = [];
        foreach ($rows as $row) {
            $deferredFlag = is_array($row)
                ? (int) ($row['ratio_deferred'] ?? 0)
                : (int) $row->getAttr('ratio_deferred');
            if ($deferredFlag === 1) {
                $deferred[] = $row;
            } else {
                $preferred[] = $row;
            }
        }

        $picked = SplitNumberWeighService::pickRowFromList($linkId, $preferred, $this->roundRobinStore, 'preferred');
        if ($picked !== null) {
            return $picked;
        }

        return SplitNumberWeighService::pickRowFromList($linkId, $deferred, $this->roundRobinStore, 'deferred');
    }
}
