<?php

namespace app\services\chat;

use app\models\chat\ChatSession;
use app\models\chat\ChatAdminStatus;
use think\facade\Db;
use think\facade\Cache;

/**
 * 会话分配服务
 * Class AssignService
 * @package app\services\chat
 */
class AssignService
{
    // Redis Key 前缀 (specs.md Section 3)
    private const LOCK_PREFIX = 'cs:lock:assign:';
    private const ONLINE_PREFIX = 'cs:online:agent:';
    private const LOAD_PREFIX = 'cs:agent:load:';
    private const QUEUE_KEY = 'cs:queue:pending';
    private const SESSION_OWNER_PREFIX = 'cs:session:owner:';

    // 约束参数 (specs.md CC-01~CC-04)
    private const LOCK_TTL = 3000;        // 分配锁超时 3秒
    private const MAX_SESSIONS = 10;      // 单客服最大会话数

    /**
     * 分配会话给客服
     * @param int $userId 用户ID
     * @param int $sessionId 会话ID
     * @return int|null 分配的客服ID，null表示无可用客服
     */
    public function assignSession($userId, $sessionId): ?int
    {
        $redis = $this->getRedis();
        $lockKey = self::LOCK_PREFIX . $userId;

        // 1. 获取分配锁 (防止并发双分配)
        $lockValue = uniqid('', true);
        $acquired = $redis->set($lockKey, $lockValue, ['NX', 'PX' => self::LOCK_TTL]);

        if (!$acquired) {
            // 锁被占用，检查是否已有活跃会话
            return $this->getExistingSessionAgent($userId);
        }

        try {
            // 2. 检查用户是否已有活跃会话
            $existingAgent = $this->getExistingSessionAgent($userId);
            if ($existingAgent !== null) {
                return $existingAgent;
            }

            // 3. 获取所有在线客服
            $onlineAgents = $this->getOnlineAgents();
            if (empty($onlineAgents)) {
                // 无在线客服，进入留言队列
                $this->addToOfflineQueue($userId, $sessionId);
                return null;
            }

            // 4. 选择会话数最少的客服
            $selectedAgent = $this->selectLeastLoadAgent($onlineAgents);
            if ($selectedAgent === null) {
                // 所有客服已满载
                $this->addToOfflineQueue($userId, $sessionId);
                return null;
            }

            // 5. 更新会话归属
            $this->bindSessionToAgent($sessionId, $selectedAgent);

            // 6. 增加客服负载计数
            $redis->incr(self::LOAD_PREFIX . $selectedAgent);

            return $selectedAgent;

        } finally {
            // 释放锁 (仅释放自己持有的锁)
            $this->releaseLock($lockKey, $lockValue);
        }
    }

    /**
     * 获取在线客服列表
     */
    public function getOnlineAgents()
    {
        $redis = $this->getRedis();
        $enabledAdmins = ChatAdminStatus::getEnabledAdminIds();
        $onlineAgents = [];

        foreach ($enabledAdmins as $adminId) {
            if ($redis->exists(self::ONLINE_PREFIX . $adminId)) {
                $onlineAgents[] = $adminId;
            }
        }

        return $onlineAgents;
    }

    /**
     * 选择会话数最少的客服
     */
    public function selectLeastLoadAgent($onlineAgents): ?int
    {
        $redis = $this->getRedis();
        $loads = [];

        foreach ($onlineAgents as $agentId) {
            $load = (int)$redis->get(self::LOAD_PREFIX . $agentId) ?: 0;
            $maxSessions = $this->getAgentMaxSessions($agentId);

            if ($load < $maxSessions) {
                $loads[$agentId] = $load;
            }
        }

        if (empty($loads)) {
            return null;
        }

        // 返回负载最小的客服
        asort($loads);
        return array_key_first($loads);
    }

    /**
     * 添加到离线队列 (按余额降序)
     */
    public function addToOfflineQueue($userId, $sessionId)
    {
        $redis = $this->getRedis();

        // 获取用户余额
        $user = Db::name('user')->where('id', $userId)->find();
        $balance = $user['money'] ?? 0;

        // ZSET score 使用负余额实现降序 (余额高的先处理)
        $score = -$balance;
        $member = json_encode([
            'userId' => $userId,
            'sessionId' => $sessionId,
            'time' => time()
        ]);

        $redis->zAdd(self::QUEUE_KEY, $score, $member);
    }

    /**
     * 客服上线时处理队列
     */
    public function processOfflineQueue($adminId)
    {
        $redis = $this->getRedis();
        $processed = [];

        while (true) {
            // 获取队列中优先级最高的会话 (score最小 = 余额最高)
            $items = $redis->zRange(self::QUEUE_KEY, 0, 0);

            if (empty($items)) {
                break;
            }

            $item = json_decode($items[0], true);

            // 检查客服是否还能接单
            $currentLoad = (int)$redis->get(self::LOAD_PREFIX . $adminId) ?: 0;
            $maxSessions = $this->getAgentMaxSessions($adminId);

            if ($currentLoad >= $maxSessions) {
                break;
            }

            // 从队列移除
            $redis->zRem(self::QUEUE_KEY, $items[0]);

            // 分配会话
            $this->bindSessionToAgent($item['sessionId'], $adminId);
            $redis->incr(self::LOAD_PREFIX . $adminId);

            $processed[] = $item;
        }

        return $processed;
    }

    /**
     * 释放会话 (会话结束时调用)
     */
    public function releaseSession($sessionId, $adminId)
    {
        $redis = $this->getRedis();

        // 删除会话归属
        $redis->del(self::SESSION_OWNER_PREFIX . $sessionId);

        // 减少客服负载
        $load = (int)$redis->get(self::LOAD_PREFIX . $adminId) ?: 0;
        if ($load > 0) {
            $redis->decr(self::LOAD_PREFIX . $adminId);
        }
    }

    /**
     * 设置客服在线状态
     */
    public function setAgentOnline($adminId)
    {
        $redis = $this->getRedis();
        $redis->setex(self::ONLINE_PREFIX . $adminId, 60, '1');
        ChatAdminStatus::updateLastOnlineTime($adminId);
    }

    /**
     * 刷新客服在线状态 (心跳续期)
     */
    public function refreshAgentOnline($adminId)
    {
        $redis = $this->getRedis();
        $redis->expire(self::ONLINE_PREFIX . $adminId, 60);
    }

    /**
     * 设置客服离线
     */
    public function setAgentOffline($adminId)
    {
        $redis = $this->getRedis();
        $redis->del(self::ONLINE_PREFIX . $adminId);
        $redis->del(self::LOAD_PREFIX . $adminId);
    }

    /**
     * 获取客服最大会话数
     */
    private function getAgentMaxSessions($adminId)
    {
        $status = ChatAdminStatus::getByAdminId($adminId);
        return $status['max_sessions'] ?? self::MAX_SESSIONS;
    }

    /**
     * 获取用户已有会话的客服ID
     */
    private function getExistingSessionAgent($userId): ?int
    {
        $session = ChatSession::getActiveByUserId($userId);
        return $session['admin_id'] ?? null;
    }

    /**
     * 绑定会话到客服
     */
    private function bindSessionToAgent($sessionId, $adminId)
    {
        $redis = $this->getRedis();

        // 更新数据库
        ChatSession::where('id', $sessionId)->update([
            'admin_id' => $adminId,
            'status' => ChatSession::STATUS_ACTIVE,
            'update_time' => time(),
        ]);

        // 设置Redis映射
        $redis->set(self::SESSION_OWNER_PREFIX . $sessionId, $adminId);
    }

    /**
     * 释放分配锁
     */
    private function releaseLock($key, $value)
    {
        $redis = $this->getRedis();

        // Lua脚本保证原子性：仅当值匹配时才删除
        $script = <<<LUA
            if redis.call('get', KEYS[1]) == ARGV[1] then
                return redis.call('del', KEYS[1])
            else
                return 0
            end
        LUA;
        $redis->eval($script, [$key, $value], 1);
    }

    /**
     * 获取Redis实例
     */
    private function getRedis()
    {
        return Cache::store('redis')->handler();
    }
}
