<?php

declare(strict_types=1);

namespace app\common\service;

use app\admin\model\split\Number;
use think\Db;

/**
 * 下号比率触线降权（ratio_deferred）规则与降权池清理
 *
 * 降权前提：已有落地页访问（visit_count > last_sync_visit_count），
 * 且自上次同步以来进线未增长时累计 streak，触线才标记降权。
 */
class SplitNumberRatioDeferredService
{
    /**
     * 是否具备计入 streak 的落地页访问基数
     *
     * @param array<string, mixed>|Number $number
     */
    public function hasLandingVisitBaseline($number): bool
    {
        $visitCount = $this->readInt($number, 'visit_count');
        if ($visitCount <= 0) {
            return false;
        }

        $lastVisit = $this->readInt($number, 'last_sync_visit_count');

        return $visitCount > $lastVisit;
    }

    /**
     * 根据访问与进线快照计算新的 streak
     *
     * @param array<string, mixed>|Number $number
     */
    public function computeStreakForVisit($number, int $currentStreak): int
    {
        if (!$this->hasLandingVisitBaseline($number)) {
            return $currentStreak;
        }

        $inboundCount = $this->readInt($number, 'inbound_count');
        $lastInbound = $this->readInt($number, 'last_sync_inbound_count');

        if ($inboundCount <= $lastInbound) {
            return $currentStreak + 1;
        }

        return 0;
    }

    /**
     * 同步侧：根据 visit / inbound 与上次同步检查点重算 streak
     *
     * 访问侧（recordVisitAfterRedirect）已按次 +1 累计 streak，此处不得再叠加 delta，否则首次同步会双倍计次导致提前关号。
     * 取 max(访问侧 streak, 自上次同步以来的点击增量) 作为裁决值，并兜底访问钩子未触发的场景。
     *
     * @param array<string, mixed>|Number $number
     */
    public function computeStreakForSync($number, int $currentStreak): int
    {
        $visitCount = $this->readInt($number, 'visit_count');
        if ($visitCount <= 0) {
            return 0;
        }

        $lastVisit = $this->readInt($number, 'last_sync_visit_count');
        $inboundCount = $this->readInt($number, 'inbound_count');
        $lastInbound = $this->readInt($number, 'last_sync_inbound_count');

        if ($inboundCount > $lastInbound) {
            return 0;
        }

        if ($visitCount > $lastVisit) {
            $deltaSinceSync = $visitCount - $lastVisit;

            return max($currentStreak, $deltaSinceSync);
        }

        return $currentStreak;
    }

    /**
     * 是否应标记 ratio_deferred（访问侧触线降权，不关 status）
     */
    public function shouldMarkDeferred(int $streak, int $assignRatio, bool $hasLandingBaseline): bool
    {
        return $hasLandingBaseline && $assignRatio > 0 && $streak >= $assignRatio;
    }

    /**
     * 是否为应清理的无效降权记录
     *
     * @param array<string, mixed>|Number $number
     */
    public function isInvalidDeferredRecord($number): bool
    {
        if ($this->readInt($number, 'ratio_deferred') !== 1) {
            return false;
        }

        return !$this->hasLandingVisitBaseline($number);
    }

    /**
     * 清理无效降权标记（ratio_deferred=1 但无有效落地页访问基线）
     *
     * @param int[]|null $adminIds 数据权限管理员 ID 列表，null 表示不限制
     * @return int 清理条数
     */
    public function cleanupInvalidDeferred(?array $adminIds = null): int
    {
        $query = Number::where('ratio_deferred', 1);
        if ($adminIds !== null && $adminIds !== []) {
            $query->where('admin_id', 'in', $adminIds);
        }

        $rows = $query->field('id,visit_count,last_sync_visit_count,ratio_deferred,no_inbound_click_streak')->select();
        if ($rows === null || $rows === []) {
            return 0;
        }

        $now = time();
        $count = 0;
        Db::startTrans();
        try {
            foreach ($rows as $row) {
                if (!$this->isInvalidDeferredRecord($row)) {
                    continue;
                }
                $update = [
                    'ratio_deferred'    => 0,
                    'ratio_deferred_at' => null,
                    'updatetime'        => $now,
                ];
                if ($this->readInt($row, 'visit_count') <= 0) {
                    $update['no_inbound_click_streak'] = 0;
                }
                Number::where('id', (int) $row['id'])->update($update);
                $count++;
            }
            Db::commit();
        } catch (\Throwable $e) {
            Db::rollback();
            throw $e;
        }

        return $count;
    }

    /**
     * 统计当前权限范围内无效降权条数（清理前预览）
     *
     * @param int[]|null $adminIds
     */
    public function countInvalidDeferred(?array $adminIds = null): int
    {
        $query = Number::where('ratio_deferred', 1);
        if ($adminIds !== null && $adminIds !== []) {
            $query->where('admin_id', 'in', $adminIds);
        }

        $total = 0;
        foreach ($query->field('id,visit_count,last_sync_visit_count,ratio_deferred')->select() as $row) {
            if ($this->isInvalidDeferredRecord($row)) {
                $total++;
            }
        }

        return $total;
    }

    /**
     * @param array<string, mixed>|Number $number
     */
    private function readInt($number, string $field): int
    {
        if (is_array($number)) {
            return (int) ($number[$field] ?? 0);
        }

        return (int) $number->getAttr($field);
    }
}
