-- 百战成神(跨服 CS)
--
-- 职责:
--   1) 活动周期: Timer.oneMin/onHour -> timedStageHandle 开/关轮、发奖
--   2) 玩家数据: 积分、排行、匹配、REGISTER/UPDATE_SHOW
--   3) 协议: 处理普通服 LW_* , 回 WL_* 到对应 logic fd
--   4) 断连补偿: 逻辑服 LW_HELLO -> onLogicServerConnect 补 ACT_START/待发奖
--
-- 对外导出:
--   oneMin, onHour, onLogicServerConnect, syncActStateToAllConnected
--   N2C_* (InnerHandler 注册)
local InnerMsg = require("core.InnerMsg")
local Util = require("common.Util")
local Timer = require("core.Timer")
local MiddleManager = require("middle.MiddleManager")
local CommonDB = require("common.CommonDB")
local BaiZhanChengShenDB = require("baiZhanChengShen.BaiZhanChengShenDB")
local BaiZhanChengShenDefine = require("baiZhanChengShen.BaiZhanChengShenDefine")
local BzcsLog = require("baiZhanChengShen.BaiZhanChengShenLog")

------------------------------------ 活动周期调度 ------------------------------------

local wDay
-- 本轮发奖 Timer 已调度(进程内防重; rewardIssued 在调度前落库防 oneMin 重复触发)
local rewardIssueInProgress = false
-- 上一 tick 是否处于可挑战窗口(用于 0:10 到点补广播, 跨服重启后首次 tick 也会触发一次)
local prevIsRunning = false

-- 刷新缓存的当前星期
local function updateWDay()
    wDay = Util.getWeekDay()
end

-- 今日 0 点时间戳
local function getTodayStartTime()
    return Util.getDayStartTime(os.time())
end

-- 将 baseTime 对齐到所在周开放首日 0:10 (getWeekDay: 1=周日, 7=周六)
local function alignRoundStart(baseTime)
    local startW = BaiZhanChengShenDefine.BZCS_OPEN_WDAY_AREA[1]
    local endW = BaiZhanChengShenDefine.BZCS_OPEN_WDAY_AREA[2]
    local w = Util.getWeekDay(baseTime)
    local sub
    if startW <= endW then
        if w >= startW and w <= endW then
            sub = w - startW
        elseif w < startW then
            sub = w + 7 - startW
        else
            sub = w - startW
        end
    elseif w >= startW then
        sub = w - startW
    else
        sub = w + 7 - startW
    end
    local dayStart = Util.getDayStartTime(baseTime)
    return dayStart - sub * 86400 + BaiZhanChengShenDefine.BZCS_START_SEC
end

-- 由开局时间推算结束日(周日) 23:00 时间戳
local function calcRoundEnd(startTime)
    local openStart = alignRoundStart(startTime)
    local endDayOffset = BaiZhanChengShenDefine.GetOpenEndDayOffset()
    local endDayStart = Util.getDayStartTime(openStart) + endDayOffset * 86400
    return endDayStart + BaiZhanChengShenDefine.BZCS_END_SEC
end

-- 当前是否在周六~周日开放日
local function isInOpenWday()
    if not wDay then updateWDay() end
    local startW = BaiZhanChengShenDefine.BZCS_OPEN_WDAY_AREA[1]
    local endW = BaiZhanChengShenDefine.BZCS_OPEN_WDAY_AREA[2]
    if startW <= endW then
        return wDay >= startW and wDay <= endW
    end
    return wDay >= startW or wDay <= endW
end

-- 本轮在 DB 中仍有效(未过 endTime 且处于开放周), 含 startTime 前待开启时段
local function hasActiveRound(now)
    now = now or os.time()
    local startTime, endTime = BaiZhanChengShenDB.GetActivityTimes()
    if startTime <= 0 or endTime <= 0 then
        return false
    end
    if now >= endTime then
        return false
    end
    return isInOpenWday()
end

-- 活动是否处于可挑战窗口(已过本轮 startTime)
local function isRunning()
    local now = os.time()
    local startTime, endTime = BaiZhanChengShenDB.GetActivityTimes()
    if startTime == 0 or endTime == 0 then
        return false
    end
    if now < startTime or now > endTime then
        return false
    end
    return isInOpenWday()
end

-- 首轮开启前置: 跨服基准开服天数达到阈值(最老服)
local function isFirstRoundOpenEligible()
    local openDay = CommonDB.getServerOpenDay() or 0
    return openDay >= BaiZhanChengShenDefine.BZCS_OPEN_SVR_DAY
end

-- 严格判断当前是否可开启新一轮:
-- 周末开放日 + 已过本轮周六 0:10 + 未超过本轮周日 23:00
-- 首轮还需满足跨服开服天数>=45; 非首轮需满足周期间隔且上轮已结束
local function canOpenNewRoundNow(now, lastReset, startTime, endTime)
    now = now or os.time()
    if not isInOpenWday() then
        return false
    end
    local roundStart = alignRoundStart(now)
    if now < roundStart then
        return false
    end
    local roundEnd = calcRoundEnd(roundStart)
    if now > roundEnd then
        return false
    end
    if lastReset == 0 then
        if not isFirstRoundOpenEligible() then
            return false
        end
        return true
    end
    if Util.diffDay(lastReset) < BaiZhanChengShenDefine.BZCS_CYCLE_DAYS then
        return false
    end
    if startTime > 0 and now < endTime then
        return false
    end
    return true
end

-- 汇总本次 WL 广播已通知的逻辑服 serverId(MiddleManager_GetAllFD 返回 SVRINDEX_2_FD, key 即 serverId)
local function formatNotifyServerIds(svrIndex2Fd)
    local seen, serverIds = {}, {}
    for serverId, _ in pairs(svrIndex2Fd or {}) do
        if serverId and not seen[serverId] then
            seen[serverId] = true
            serverIds[#serverIds + 1] = serverId
        end
    end
    -- 合服扩展连接(仅 MERGE 表有登记时补充)
    for serverId, _ in pairs(SVRINDEX_2_FD_MERGE or {}) do
        if serverId and not seen[serverId] then
            seen[serverId] = true
            serverIds[#serverIds + 1] = serverId
        end
    end
    table.sort(serverIds)
    return table.concat(serverIds, ",")
end

-- 向单个逻辑服 fd 推送当前活动状态(重连/跨服重启后补偿, 避免错过广播)
local function sendActStateToFd(fd)
    if not fd then return end
    local serverId = MiddleManager.FD_2_SVRINDEX[fd] or 0
    local startTime, endTime = BaiZhanChengShenDB.GetActivityTimes()
    if hasActiveRound() then
        local msgData = InnerMsg.wl.WL_BZCS_ACT_START
        msgData.startTime = startTime
        msgData.endTime = endTime
        InnerMsg.sendMsg(fd, msgData)
        BzcsLog.logAction("act_open", string.format(
            "tag=sync_fd serverId=%s fd=%s start=%s end=%s running=%s",
            serverId, fd, startTime, endTime, isRunning() and 1 or 0
        ))
    else
        local msgData = InnerMsg.wl.WL_BZCS_ACT_END
        InnerMsg.sendMsg(fd, msgData)
        BzcsLog.logAction("act_close", string.format(
            "tag=sync_fd serverId=%s fd=%s start=%s end=%s running=0",
            serverId, fd, startTime, endTime
        ))
    end
end

-- 逻辑服连上跨服时: 同步活动状态 + 补发断连期间缓存的周期奖励
function onLogicServerConnect(fd)
    if _G.is_middle ~= true or not fd then return end
    sendActStateToFd(fd)
    local serverId = MiddleManager.FD_2_SVRINDEX[fd]
    if not serverId then return end
    local pending = BaiZhanChengShenDB.TakePendingRewards(serverId)
    if #pending > 0 then
        local msgData = InnerMsg.wl.WL_BZCS_ISSUE_REWARD
        msgData.rewardList = pending
        InnerMsg.sendMsg(fd, msgData)
        BzcsLog.logAction("reward_reissue", string.format("serverId=%s cnt=%s", serverId, #pending))
    end
end

-- 跨服启动后向已连接逻辑服同步活动状态
function syncActStateToAllConnected()
    if _G.is_middle ~= true then return end
    local fdList = MiddleManager.MiddleManager_GetAllFD()
    for _, fd in pairs(fdList) do
        sendActStateToFd(fd)
    end
end

-- 广播活动开启, 普通服写入 KEY_BZCS_START_TIME; tag 用于区分开轮/时间修正等场景
function ActOpen(startTime, tag)
    tag = tag or "broadcast"
    local msgData = InnerMsg.wl.WL_BZCS_ACT_START
    msgData.startTime = startTime or os.time()
    local _, endTime = BaiZhanChengShenDB.GetActivityTimes()
    msgData.endTime = endTime
    local svrIndex2Fd = MiddleManager.MiddleManager_GetAllFD()
    local fdCnt = 0
    for serverId, fd in pairs(svrIndex2Fd) do
        InnerMsg.sendMsg(fd, msgData)
        fdCnt = fdCnt + 1
    end
    BzcsLog.logAction("act_open", string.format(
        "tag=%s start=%s end=%s fdCnt=%s running=%s servers=%s",
        tag, msgData.startTime, msgData.endTime or 0, fdCnt, isRunning() and 1 or 0,
        formatNotifyServerIds(svrIndex2Fd)
    ))
end

-- 广播活动结束; tag 用于区分正常结算/放弃上轮等场景
function ActEnd(tag)
    tag = tag or "broadcast"
    local startTime, endTime = BaiZhanChengShenDB.GetActivityTimes()
    local msgData = InnerMsg.wl.WL_BZCS_ACT_END
    local svrIndex2Fd = MiddleManager.MiddleManager_GetAllFD()
    local fdCnt = 0
    for serverId, fd in pairs(svrIndex2Fd) do
        InnerMsg.sendMsg(fd, msgData)
        fdCnt = fdCnt + 1
    end
    BzcsLog.logAction("act_close", string.format(
        "tag=%s start=%s end=%s fdCnt=%s rewardIssued=%s servers=%s",
        tag, startTime or 0, endTime or 0, fdCnt, BaiZhanChengShenDB.IsRewardIssued() and 1 or 0,
        formatNotifyServerIds(svrIndex2Fd)
    ))
end

-- 按 serverId 汇总待发奖 {{uuid,rank},...}
local function groupRewardsByServer(playerList)
    local byServer = {}
    for _, info in ipairs(playerList) do
        local uuid, rank, serverId = info[1], info[2], info[3]
        if serverId then
            local list = byServer[serverId]
            if not list then
                list = {}
                byServer[serverId] = list
            end
            list[#list + 1] = {uuid, rank}
        end
    end
    return byServer
end

-- 向指定逻辑服批量下发排名奖励(每服一次 WL); 未连接则整批入 pending
local function issueRewardBatch(serverId, rewardList)
    if not rewardList or #rewardList == 0 then return end
    local fd = MiddleManager.getFDBySvrIndex(serverId)
    if fd then
        local msgData = InnerMsg.wl.WL_BZCS_ISSUE_REWARD
        msgData.rewardList = rewardList
        InnerMsg.sendMsg(fd, msgData)
        BzcsLog.logAction("reward_issue_send", string.format("serverId=%s cnt=%s", serverId, #rewardList))
    else
        BaiZhanChengShenDB.AddPendingRewards(serverId, rewardList)
        BzcsLog.logAction("reward_issue_offline", string.format("serverId=%s cnt=%s", serverId, #rewardList))
    end
end

-- 活动结束发奖: 按逻辑服批量 WL_BZCS_ISSUE_REWARD(服与服之间 2s 节流)
function IssueRewardManager()
    if BaiZhanChengShenDB.IsRewardIssued() or rewardIssueInProgress then
        return
    end
    local rewardPlayers = BaiZhanChengShenDB.GetAllPlayersForReward()
    local byServer = groupRewardsByServer(rewardPlayers)
    local serverList = {}
    for serverId, rewardList in pairs(byServer) do
        serverList[#serverList + 1] = {serverId, rewardList}
    end
    if #serverList == 0 then
        BaiZhanChengShenDB.SetRewardIssued(true)
        return
    end
    rewardIssueInProgress = true
    -- 先落库标记, 避免异步 Timer 完成前 timedStageHandle/oneMin 重复调度
    BaiZhanChengShenDB.SetRewardIssued(true)
    BzcsLog.logAction("reward_issue", string.format("playerCnt=%s serverCnt=%s", #rewardPlayers, #serverList))
    local delay = 0
    local onlineCnt, pendingCnt = 0, 0
    for _, entry in ipairs(serverList) do
        local serverId, rewardList = entry[1], entry[2]
        local fd = MiddleManager.getFDBySvrIndex(serverId)
        if not fd then
            BaiZhanChengShenDB.AddPendingRewards(serverId, rewardList)
            pendingCnt = pendingCnt + 1
        else
            delay = delay + 2
            onlineCnt = onlineCnt + 1
            Timer.addLater(delay, issueRewardBatch, serverId, rewardList)
        end
    end
    rewardIssueInProgress = false
    BzcsLog.logAction("reward_issue_scheduled", string.format(
        "onlineCnt=%s pendingCnt=%s delaySec=%s", onlineCnt, pendingCnt, delay
    ))
end

-- 开启新周期: 重置 DB 并 ActOpen
local function newRoundHandle(now)
    now = now or os.time()
    local startTime = alignRoundStart(now)
    local endTime = calcRoundEnd(startTime)
    BaiZhanChengShenDB.ResetForNewRound(startTime, endTime)
    ActOpen(startTime, "new_round")
end

-- 结束当前周期: 发奖 + ActEnd
local function endRoundHandle()
    if BaiZhanChengShenDB.IsRewardIssued() or rewardIssueInProgress then
        return
    end
    BzcsLog.logAction("act_settle", "begin_issue_reward")
    IssueRewardManager()
    ActEnd("end_round")
end

-- 放弃上轮未完成的周期发奖(满21天开新轮时不再补发)
local function abandonUnissuedRewards()
    if BaiZhanChengShenDB.IsRewardIssued() then
        return
    end
    BzcsLog.logAction("reward_abandon", "new_round_skip")
    BaiZhanChengShenDB.SetRewardIssued(true)
    BaiZhanChengShenDB.ClearAllPendingRewards()
    ActEnd("abandon_new_round")
end

-- 是否应开启新轮; 条件满足则执行 newRoundHandle 并广播
local function tryOpenNewRound(now, lastReset, startTime, endTime)
    if not canOpenNewRoundNow(now, lastReset, startTime, endTime) then
        return false
    end
    if lastReset > 0 and not BaiZhanChengShenDB.IsRewardIssued() then
        abandonUnissuedRewards()
    end
    newRoundHandle(now)
    return true
end

-- 周期阶段机 (oneMin/onHour 调用):
-- 1) 严格满足开新轮条件(含首轮45天门槛、周末窗口 0:10~23:00、间隔/结束检查) -> newRoundHandle + ActOpen
-- 2) 已过 endTime 且未发奖 -> endRoundHandle
-- 3) 刚进入可挑战窗口(如周六0:10) -> ActOpen 补偿逻辑服
local function timedStageHandle()
    local now = os.time()
    local lastReset = BaiZhanChengShenDB.GetLastResetTime()
    local startTime, endTime = BaiZhanChengShenDB.GetActivityTimes()
    local running = isRunning()

    if tryOpenNewRound(now, lastReset, startTime, endTime) then
        prevIsRunning = isRunning()
        return
    end

    if startTime > 0 and now >= endTime and not BaiZhanChengShenDB.IsRewardIssued() then
        endRoundHandle()
        prevIsRunning = false
        return
    end

    if running and not prevIsRunning then
        ActOpen(startTime, "window_open")
    end
    prevIsRunning = running
end

-- Timer 每分钟(跳过整点)检查阶段
function oneMin()
    if _G.is_middle ~= true then return end
    if Util.getMin() == 0 then return end
    timedStageHandle()
end

-- Timer 每小时检查阶段, 0 点刷新星期
function onHour(hour)
    if _G.is_middle ~= true then return end
    if hour == 0 or not wDay then
        updateWDay()
    end
    timedStageHandle()
end

------------------------------------ N2C (普通服 LW -> 本模块 -> WL 回包) ------------------------------------
-- msg 中带 sourceServerId/playerUuid, 通过 MiddleManager.getFDBySvrIndex 回包

-- 向逻辑服 fd 发送 WL 协议
local function sendWL(fd, msgData)
    if not fd then return false end
    InnerMsg.sendMsg(fd, msgData)
    return true
end

-- 业务失败时回 WL_BZCS_TIPS
local function errTips(sourceServerId, playerUuid, errCode)
    BzcsLog.logAction("err_tips", string.format("serverId=%s uuid=%s err=%s", sourceServerId or 0, playerUuid or "", errCode or 0))
    local msgData = InnerMsg.wl.WL_BZCS_TIPS
    msgData.playerUuid = playerUuid
    msgData.errCode = errCode or 0
    local fd = MiddleManager.getFDBySvrIndex(sourceServerId)
    if not fd then return end
    sendWL(fd, msgData)
end

-- LW_BZCS_MATCH -> WL_BZCS_MATCH (±500步进扩大, 窗口内随机匹配最多3人; refreshRanks 非空时仅刷新展示)
function N2C_Match(msg)
    local fd = MiddleManager.getFDBySvrIndex(msg.sourceServerId)
    if not isRunning() then
        return errTips(msg.sourceServerId, msg.playerUuid, BaiZhanChengShenDefine.BZCS_ERR_NOT_OPEN)
    end
    local pinfo = BaiZhanChengShenDB.GetPlayer(msg.playerUuid)
    local myScore = pinfo and pinfo.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE
    local refreshRanks = msg.refreshRanks
    local opponents
    if refreshRanks and #refreshRanks > 0 then
        opponents = BaiZhanChengShenDB.GetMatchOpponentsByRanks(refreshRanks)
        if #opponents < #refreshRanks then
            opponents = BaiZhanChengShenDB.GetMatchOpponents(msg.playerUuid, myScore, {})
        end
    else
        opponents = BaiZhanChengShenDB.GetMatchOpponents(msg.playerUuid, myScore, {})
    end
    local msgData = InnerMsg.wl.WL_BZCS_MATCH
    msgData.playerUuid = msg.playerUuid
    msgData.myScore = myScore
    msgData.myRank = BaiZhanChengShenDB.GetRankByUuid(msg.playerUuid)
    msgData.opponentList = opponents
    sendWL(fd, msgData)
end

-- LW_BZCS_RANK_LIST -> WL_BZCS_RANK_LIST
function N2C_RankList(msg)
    local fd = MiddleManager.getFDBySvrIndex(msg.sourceServerId)
    local msgData = InnerMsg.wl.WL_BZCS_RANK_LIST
    msgData.playerUuid = msg.playerUuid
    msgData.rankList = BaiZhanChengShenDB.GetRankList(BaiZhanChengShenDefine.BZCS_RANK_MAX)
    msgData.myRankInfo = BaiZhanChengShenDB.BuildPlayerRankInfo(msg.playerUuid)
    sendWL(fd, msgData)
end

-- LW_BZCS_OPPONENT_INFO -> WL_BZCS_OPPONENT_INFO
function N2C_OpponentInfo(msg)
    local fd = MiddleManager.getFDBySvrIndex(msg.sourceServerId)
    local target = BaiZhanChengShenDB.GetPlayerByRank(msg.targetRank)
    local msgData = InnerMsg.wl.WL_BZCS_OPPONENT_INFO
    msgData.playerUuid = msg.playerUuid
    msgData.res = target and 0 or -1
    msgData.targetInfo = BaiZhanChengShenDB.BuildOpponentInfoSnapshot(target) or {}
    sendWL(fd, msgData)
end

-- LW_BZCS_OPPONENT_LINEUP -> WL_BZCS_OPPONENT_LINEUP (含机器人 showInfo)
function N2C_OpponentLineup(msg)
    local fd = MiddleManager.getFDBySvrIndex(msg.sourceServerId)
    local target = BaiZhanChengShenDB.GetPlayerByRank(msg.targetRank)
    local msgData = InnerMsg.wl.WL_BZCS_OPPONENT_LINEUP
    msgData.playerUuid = msg.playerUuid
    msgData.targetRank = msg.targetRank or 0
    msgData.showInfo = target and target.showInfo or {}
    msgData.isRobot = target and target.isRobot or 0
    sendWL(fd, msgData)
end

-- LW_BZCS_CAN_FIGHT -> WL_BZCS_CAN_FIGHT (按开战时全服名次锁定对手, 扣次在 NS C2N_CanFight)
function N2C_CanFight(msg)
    local fd = MiddleManager.getFDBySvrIndex(msg.sourceServerId)
    if not isRunning() then
        return errTips(msg.sourceServerId, msg.playerUuid, BaiZhanChengShenDefine.BZCS_ERR_NOT_OPEN)
    end
    local targetRank = msg.targetRank
    if not targetRank or targetRank < 1 then
        return errTips(msg.sourceServerId, msg.playerUuid, BaiZhanChengShenDefine.BZCS_ERR_TARGET_INVALID)
    end
    local target = BaiZhanChengShenDB.GetPlayerByRank(targetRank)
    if not target then
        return errTips(msg.sourceServerId, msg.playerUuid, BaiZhanChengShenDefine.BZCS_ERR_TARGET_INVALID)
    end
    if target.uuid == msg.playerUuid then
        return errTips(msg.sourceServerId, msg.playerUuid, BaiZhanChengShenDefine.BZCS_ERR_TARGET_INVALID)
    end
    local msgData = InnerMsg.wl.WL_BZCS_CAN_FIGHT
    msgData.playerUuid = msg.playerUuid
    msgData.targetRank = targetRank
    msgData.defUuid = target.uuid
    msgData.defServerId = target.serverId or 0
    local si = target.showInfo or {}
    msgData.defName = si.name or ""
    msgData.defScore = target.score or BaiZhanChengShenDefine.BZCS_INIT_SCORE
    msgData.isRobot = target.isRobot or 0
    msgData.res = 0
    sendWL(fd, msgData)
end

-- LW_BZCS_REGISTER 首次挑战注册跨服玩家(保留已有积分/firstJoinTime)
function N2C_Register(msg)
    local pinfo = msg.playerInfo
    if not pinfo or not pinfo.uuid then return end
    local old = BaiZhanChengShenDB.GetPlayer(pinfo.uuid)
    if old then
        pinfo.score = old.score
        pinfo.scoreTime = old.scoreTime
        if (old.firstJoinTime or 0) > 0 then
            pinfo.firstJoinTime = old.firstJoinTime
        end
    else
        pinfo.score = BaiZhanChengShenDefine.BZCS_INIT_SCORE
        pinfo.firstJoinTime = pinfo.firstJoinTime or os.time()
        pinfo.scoreTime = os.time()
    end
    pinfo.isRobot = 0
    BaiZhanChengShenDB.UpsertPlayer(pinfo.uuid, pinfo)
    BzcsLog.logAction("register", string.format("uuid=%s serverId=%s score=%s firstJoin=%s", pinfo.uuid, pinfo.serverId or 0, pinfo.score or 0, pinfo.firstJoinTime or 0))
end

-- LW_BZCS_UPDATE_SHOW 增量合并展示数据
function N2C_UpdateShow(msg)
    local pinfo = BaiZhanChengShenDB.GetPlayer(msg.playerUuid)
    if not pinfo or not msg.showInfo then return end
    pinfo.showInfo = pinfo.showInfo or {}
    BaiZhanChengShenDefine.MergeShowInfo(pinfo.showInfo, msg.showInfo)
    BaiZhanChengShenDB.UpsertPlayer(msg.playerUuid, pinfo)
    BzcsLog.logAction("update_show", string.format("uuid=%s type=%s race=%s", msg.playerUuid, msg.updateType or 0, msg.race or 0))
end

-- REGISTER 尚未落库时, 用 FIGHT_END 包字段建最小攻方记录(兜底乱序/丢包)
local function ensureAtkRegistered(msg)
    local atkUuid = msg and msg.atkUuid
    if not atkUuid or atkUuid == "" then return end
    if BaiZhanChengShenDB.GetPlayer(atkUuid) then return end
    local pinfo = {
        uuid = atkUuid,
        serverId = msg.atkServerId or 0,
        score = BaiZhanChengShenDefine.BZCS_INIT_SCORE,
        firstJoinTime = os.time(),
        scoreTime = os.time(),
        isRobot = 0,
        showInfo = { name = msg.atkName or "" },
    }
    BaiZhanChengShenDB.UpsertPlayer(atkUuid, pinfo)
    BzcsLog.logAction("register_fallback", string.format("uuid=%s serverId=%s", atkUuid, pinfo.serverId))
end

-- LW_BZCS_FIGHT_END 整场结算: 攻守加减分, WL 通知攻方; 真人守方另发 WL_BZCS_DEF_NOTIFY
function N2C_FightEnd(msg)
    local atkUuid = msg.atkUuid
    local defUuid = msg.defUuid
    local atkWin = msg.atkWin == 1
    local atkDelta = atkWin and BaiZhanChengShenDefine.BZCS_ATK_WIN_SCORE or BaiZhanChengShenDefine.BZCS_ATK_LOSE_SCORE
    local defDelta = atkWin and BaiZhanChengShenDefine.BZCS_DEF_LOSE_SCORE or BaiZhanChengShenDefine.BZCS_DEF_WIN_SCORE

    ensureAtkRegistered(msg)
    local atkScore = BaiZhanChengShenDB.UpdateScore(atkUuid, atkDelta)
    local defScore = BaiZhanChengShenDB.UpdateScore(defUuid, defDelta)
    if not atkScore then
        BzcsLog.logAction("fight_end_atk_score_fail", string.format(
            "atk=%s atkSvr=%s delta=%s", atkUuid or "", msg.atkServerId or 0, atkDelta
        ))
        atkScore = BaiZhanChengShenDefine.BZCS_INIT_SCORE + atkDelta
    end
    BzcsLog.logAction("fight_end", string.format(
        "atk=%s def=%s atkWin=%s atkDelta=%s defDelta=%s atkScore=%s defScore=%s atkSvr=%s defSvr=%s",
        atkUuid or "", defUuid or "", msg.atkWin or 0, atkDelta, defDelta, atkScore, defScore or -1,
        msg.atkServerId or 0, msg.defServerId or 0
    ))

    local atkFd = MiddleManager.getFDBySvrIndex(msg.atkServerId)
    local wlAtk = InnerMsg.wl.WL_BZCS_FIGHT_END
    wlAtk.playerUuid = atkUuid
    wlAtk.atkWin = msg.atkWin or 0
    wlAtk.scoreChange = atkDelta
    wlAtk.myScore = atkScore
    wlAtk.defName = msg.defName or ""
    wlAtk.defServerId = msg.defServerId or 0
    wlAtk.raceResults = msg.raceResults or {}
    if not sendWL(atkFd, wlAtk) then
        BzcsLog.logAction("fight_end_wl_fail", string.format("atk=%s atkSvr=%s", atkUuid or "", msg.atkServerId or 0))
    end

    local defInfo = BaiZhanChengShenDB.GetPlayer(defUuid)
    if defInfo and defInfo.isRobot ~= 1 and defInfo.serverId and defScore then
        local defFd = MiddleManager.getFDBySvrIndex(defInfo.serverId)
        if defFd then
            local wlDef = InnerMsg.wl.WL_BZCS_DEF_NOTIFY
            wlDef.playerUuid = defUuid
            wlDef.atkName = msg.atkName or ""
            wlDef.atkServerId = msg.atkServerId or 0
            wlDef.atkWin = msg.atkWin == 1 and 0 or 1
            wlDef.scoreChange = defDelta
            wlDef.myScore = defScore
            wlDef.raceResults = msg.raceResults or {}
            sendWL(defFd, wlDef)
        end
    elseif defInfo and defInfo.isRobot ~= 1 and not defScore then
        BzcsLog.logAction("fight_end_def_score_fail", string.format(
            "def=%s defSvr=%s delta=%s", defUuid or "", defInfo.serverId or 0, defDelta
        ))
    end
end
