import logging
import asyncio
from datetime import datetime
from uuid import uuid4

from nonebot.adapters.onebot.v11 import Bot, Event, GroupMessageEvent, Message, MessageSegment, PrivateMessageEvent

from danding_bot.plugins.danding_qqpush.config import Config as QqPushConfig
from danding_bot.plugins.danding_qqpush.image_render import ImageRenderer
from ..room_store import room_store  # use the singleton managed by __init__.py lifecycle hooks
from ..points_service import PointsService
from ..race_engine import RaceEngine
from ..message_service import MessageService
from ..models import Room, Horse, Bet, HorseState, RoomState, RaceResult

from .. import plugin_config as config

logger = logging.getLogger("horse_racing.commands")
points_service = PointsService(config)
race_engine = RaceEngine(config)
message_service = MessageService(config)
_race_image_renderer: ImageRenderer | None = None


async def _get_user_name(bot: Bot, scope: str, user_id: str) -> str:
    """Get user display name (group card > nickname > user_id)."""
    try:
        if scope.startswith("group_"):
            group_id = int(scope.split("_", 1)[1])
            info = await bot.get_group_member_info(group_id=group_id, user_id=int(user_id))
            return info.get("card") or info.get("nickname") or user_id
    except Exception as exc:
        logger.debug("获取赛马用户昵称失败 scope=%s user_id=%s error=%s", scope, user_id, exc)
    return user_id


async def _build_name_map(bot: Bot, scope: str, user_ids: list[str]) -> dict[str, str]:
    """Build a mapping from user_id to display name."""
    name_map: dict[str, str] = {}
    for uid in user_ids:
        if uid not in name_map:
            name_map[uid] = await _get_user_name(bot, scope, uid)
    return name_map


def _get_race_image_renderer() -> ImageRenderer:
    global _race_image_renderer
    if _race_image_renderer is None:
        qqpush_config = QqPushConfig()
        _race_image_renderer = ImageRenderer(
            width=config.RACE_IMAGE_WIDTH,
            font_size=config.RACE_IMAGE_FONT_SIZE,
            padding=config.RACE_IMAGE_PADDING,
            line_spacing=config.RACE_IMAGE_LINE_SPACING,
            font_paths=qqpush_config.FontPaths,
        )
    return _race_image_renderer


def _build_race_image_message(message: str) -> Message:
    if message.startswith("比赛开始！"):
        title = "🏇 赛马开赛"
        body = message.replace("比赛开始！", "发令枪响，比赛正式开始！", 1)
    elif message.startswith("比赛结束！"):
        title = "🏆 赛马结果"
        body = message
    else:
        title = "📣 赛马进度"
        body = f"🏁 实时播报\n{message}"

    renderer = _get_race_image_renderer()
    image_base64 = renderer.render_to_base64(body, title=title)
    message_obj = Message()
    message_obj.append(MessageSegment.image(image_base64))
    return message_obj


def _normalize_horse_name(horse_name: str) -> str:
    return horse_name.strip().casefold()


def _get_horses_in_order(room: Room) -> list[Horse]:
    return sorted(room.horses.values(), key=lambda horse: horse.index)


def _format_horse_label(horse: Horse) -> str:
    return f"{horse.index:02d}号 {horse.name}"


def _find_user_horse(room: Room, user_id: str) -> Horse | None:
    for horse in _get_horses_in_order(room):
        if horse.owner_id == user_id:
            return horse
    return None


def _find_duplicate_horse(room: Room, horse_name: str) -> Horse | None:
    normalized_name = _normalize_horse_name(horse_name)
    for horse in room.horses.values():
        if _normalize_horse_name(horse.name) == normalized_name:
            return horse
    return None


def _resolve_horse_selector(room: Room, selector: str) -> Horse | None:
    selector = selector.strip()
    if selector.isdigit():
        target_index = int(selector)
        for horse in room.horses.values():
            if horse.index == target_index:
                return horse
        return None

    normalized_selector = _normalize_horse_name(selector)
    for horse in room.horses.values():
        if _normalize_horse_name(horse.name) == normalized_selector:
            return horse
    return None


def _describe_points_delta(delta: int) -> str:
    if delta >= 300:
        return "血赚翻倍"
    if delta >= 150:
        return "大赚特赚"
    if delta > 0:
        return "小有收获"
    if delta == 0:
        return "稳住不亏"
    if delta <= -300:
        return "倾家荡产"
    if delta <= -150:
        return "伤筋动骨"
    return "略有损失"


async def _is_admin_or_owner(bot: Bot, event: Event) -> bool:
    """Check if the event sender is a group admin or owner."""
    if not isinstance(event, GroupMessageEvent):
        return False
    try:
        member_info = await bot.get_group_member_info(
            group_id=event.group_id,
            user_id=int(event.get_user_id()),
        )
        return member_info.get("role", "") in ("admin", "owner")
    except Exception as exc:
        logger.debug("检查赛马管理员权限失败 user_id=%s error=%s", getattr(event, "user_id", ""), exc)
        return False



def _build_point_changes(room: Room, odds: dict[str, float]) -> tuple[dict[str, int], dict[str, str]]:
    point_changes: dict[str, int] = {}

    for horse in room.horses.values():
        point_changes[horse.owner_id] = point_changes.get(horse.owner_id, 0) + config.PARTICIPANT_REWARD

    for bet in room.bets:
        point_changes[bet.user_id] = point_changes.get(bet.user_id, 0) - bet.amount

    champion = room.horses.get(room.champion_name)
    if champion:
        point_changes[champion.owner_id] = point_changes.get(champion.owner_id, 0) + config.CHAMPION_REWARD

    for bet in room.bets:
        if bet.horse_name == room.champion_name:
            payout = int(bet.amount * odds.get(bet.horse_name, config.MIN_ODDS))
            point_changes[bet.user_id] = point_changes.get(bet.user_id, 0) + payout

    point_summaries = {
        user_id: _describe_points_delta(delta)
        for user_id, delta in point_changes.items()
    }
    return point_changes, point_summaries


def _build_participants_snapshot(room: Room) -> list[str]:
    """生成赛果归档所需的参赛马名快照。"""

    return [horse.name for horse in _get_horses_in_order(room)]


def _build_bet_distribution(room: Room) -> dict[str, int]:
    """按马名汇总下注分布，供 xapi 原样归档。"""

    distribution = {horse.name: 0 for horse in _get_horses_in_order(room)}
    for bet in room.bets:
        distribution[bet.horse_name] = distribution.get(bet.horse_name, 0) + bet.amount
    return distribution


async def _send_to_scope(bot: Bot, scope: str, message: str, message_type: str = "race_update", critical: bool = False):
    """Send message to group or private chat based on scope."""
    outbound_message: str | Message = message
    if config.RACE_RENDER_AS_IMAGE:
        try:
            outbound_message = _build_race_image_message(message)
        except Exception as e:
            logger.warning(f"_build_race_image_message failed, using plain text: {e}")
            outbound_message = message

    try:
        await message_service.send_with_recall(bot, scope, message_type, outbound_message)
    except Exception as e:
        if critical:
            logger.warning(f"_send_to_scope failed (critical={critical}) for {scope} [{message_type}]: {e}, retrying...")
            try:
                await message_service.send_with_recall(bot, scope, message_type, message)
            except Exception as e2:
                logger.error(f"_send_to_scope retry also failed for {scope} [{message_type}]: {e2}")
        else:
            logger.warning(f"_send_to_scope failed for {scope} [{message_type}]: {e}")


async def _format_point_change_lines(room: Room, point_changes: dict[str, int], point_summaries: dict[str, str], name_map: dict[str, str]) -> list[str]:
    ordered_user_ids: list[str] = []
    for horse in _get_horses_in_order(room):
        if horse.owner_id not in ordered_user_ids:
            ordered_user_ids.append(horse.owner_id)
    for bet in room.bets:
        if bet.user_id not in ordered_user_ids:
            ordered_user_ids.append(bet.user_id)

    lines = ["积分变化："]
    for user_id in ordered_user_ids:
        delta = point_changes.get(user_id, 0)
        summary = point_summaries.get(user_id, _describe_points_delta(delta))
        display_name = name_map.get(user_id, user_id)
        balance = await points_service.get_balance(user_id)
        lines.append(f"  {display_name} {delta:+d} 积分 · {summary}（余额: {balance}）")
    return lines


def calculate_odds(room: Room) -> dict[str, float]:
    """Calculate odds for each horse based on bet distribution."""
    total_bet = sum(b.amount for b in room.bets)
    odds = {}
    for name in room.horses:
        horse_bet = sum(b.amount for b in room.bets if b.horse_name == name)
        if horse_bet == 0:
            odds[name] = config.MIN_ODDS
        else:
            raw_odds = total_bet / horse_bet
            odds[name] = max(config.MIN_ODDS, round(raw_odds, 2))
    return odds


async def settle_race(room: Room) -> tuple[RaceResult, dict[str, float]] | None:
    """Settle bets and rewards after race finishes. Returns (result, odds) or None."""
    champion = room.horses.get(room.champion_name)
    if not champion:
        return None

    odds = calculate_odds(room)

    # Collect all affected user IDs
    user_ids: set[str] = set()
    for horse in room.horses.values():
        user_ids.add(horse.owner_id)
    for bet in room.bets:
        user_ids.add(bet.user_id)

    # Record pre-balances
    pre_balances: dict[str, int] = {}
    for uid in user_ids:
        pre_balances[uid] = await points_service.get_balance(uid)

    # 1. Reward all participants
    for horse in room.horses.values():
        try:
            await points_service.reward_participant(horse.owner_id)
        except Exception as e:
            logger.warning(f"reward_participant failed for {horse.owner_id}: {e}")

    # 2. Champion bonus
    try:
        await points_service.reward_champion(champion.owner_id)
    except Exception as e:
        logger.warning(f"reward_champion failed for {champion.owner_id}: {e}")

    # 3. Bet payouts for winners
    for bet in room.bets:
        if bet.horse_name == room.champion_name:
            try:
                await points_service.payout_winnings(
                    bet.user_id, bet.amount, odds.get(bet.horse_name, config.MIN_ODDS)
                )
            except Exception as e:
                logger.warning(f"payout_winnings failed for {bet.user_id}: {e}")

    # Record post-balances and compute deltas
    post_balances: dict[str, int] = {}
    for uid in user_ids:
        post_balances[uid] = await points_service.get_balance(uid)

    point_changes: dict[str, int] = {}
    for uid in user_ids:
        delta = post_balances[uid] - pre_balances[uid]
        if delta != 0:
            point_changes[uid] = delta

    # Build human-readable summaries
    _, point_change_summaries = _build_point_changes(room, odds)

    result = RaceResult(
        race_id=str(uuid4()),
        scope=room.scope,
        champion_name=room.champion_name,
        champion_owner=champion.owner_id,
        participants=_build_participants_snapshot(room),
        bet_distribution=_build_bet_distribution(room),
        duration_ticks=room.tick_count,
        completed_at=datetime.now(),
        point_changes=point_changes,
        point_change_summaries=point_change_summaries,
        odds_snapshot=odds,
    )
    return result, odds


async def run_race_with_settlement(bot: Bot, room: Room, scope: str):
    """Run race with live progress updates and settlement."""
    room.state = RoomState.RUNNING

    horse_list = "\n".join(f"  {_format_horse_label(h)}" for h in _get_horses_in_order(room))
    await _send_to_scope(bot, scope, f"比赛开始！\n{horse_list}", "race_update")

    try:
        while room.state == RoomState.RUNNING:
            await asyncio.sleep(config.RACE_TICK_INTERVAL)

            finished = race_engine.tick(room)
            progress = race_engine.format_progress(room)
            await _send_to_scope(bot, scope, progress, "race_update")

            if finished:
                champion = race_engine.determine_champion(finished)
                room.champion_name = champion.name
                room.state = RoomState.FINISHED
                break
    except asyncio.CancelledError:
        room.state = RoomState.INTERRUPTED
        for bet in room.bets:
            await points_service.refund_bet_points(bet.user_id, bet.amount, "比赛中断退还")
        return

    settlement = await settle_race(room)
    result = settlement[0] if settlement else None
    odds = settlement[1] if settlement else {}

    all_user_ids = [h.owner_id for h in room.horses.values()] + [b.user_id for b in room.bets]
    name_map = await _build_name_map(bot, scope, all_user_ids)

    champion = room.horses.get(room.champion_name)
    champion_name_display = name_map.get(champion.owner_id, champion.owner_id) if champion else '?'
    result_lines = [
        f"比赛结束！冠军：{_format_horse_label(champion) if champion else room.champion_name}",
        f"马主 {champion_name_display} 获得 {config.CHAMPION_REWARD} 积分",
    ]
    winning_bets = [b for b in room.bets if b.horse_name == room.champion_name]
    if winning_bets:
        result_lines.append("下注中奖：")
        for b in winning_bets:
            payout = int(b.amount * odds.get(b.horse_name, config.MIN_ODDS))
            bettor_name = name_map.get(b.user_id, b.user_id)
            result_lines.append(f"  {bettor_name} 下注 {b.amount} -> 获得 {payout}")
    if result:
        result_lines.extend(await _format_point_change_lines(room, result.point_changes, result.point_change_summaries, name_map))

    await message_service.recall_previous_of_type(bot, scope, "race_update")
    await _send_to_scope(bot, scope, "\n".join(result_lines), "race_result")

    if result:
        await room_store.save_race_result(result)

    race_engine.stop_race(scope)
    room_store.delete_room(scope)
    message_service.clear_pending_recalls(scope)


# Import and re-export access functions from access.py (canonical source)
from .access import get_event_id, get_scope, check_access
