from __future__ import annotations

import asyncio
import logging
import time

from fastapi import APIRouter

from woc_bridge.config import (
    _state,
    _require_db_reader, _require_xdotool, _require_send_queue,
    _require_rule_engine, _require_friend_watcher,
)
from woc_bridge.db.coordinator import _check_db_readable, with_db_retry
from woc_bridge.routes.send import _resolve_display_name, _display_name_cache
from woc_bridge.messaging.friend_parser import parse_friend_request
from woc_bridge.ui.drivers.contact import _verify_accept_with_retry
from woc_bridge.models import (
    BridgeError,
    Contact,
    ContactsResponse,
    GroupsResponse,
    GroupMembersResponse,
    SetRemarkRequest,
    SetRemarkResponse,
    AddFriendRequest,
    AddFriendResponse,
    AcceptRuleConfig,
    FriendRequestItem,
    FriendRequestsResponse,
    AcceptFriendRequest,
    AcceptFriendResponse,
    AutoAcceptStatus,
)

logger = logging.getLogger("woc-bridge")
router = APIRouter()


# ---------------------------------------------------------------------------
# 路由：GET /api/contacts
# ---------------------------------------------------------------------------
@router.get("/api/contacts", response_model=ContactsResponse)
@with_db_retry
async def get_contacts(keyword: str = "", limit: int = 50) -> ContactsResponse:
    """联系人列表查询（channels SessionAdapter / WizardAdapter 使用）。

    Args:
        keyword: 模糊匹配 wxid / nickname / remark，空串返回全部
        limit: 1~200，默认 50

    Returns:
        ContactsResponse：含 contacts 列表 / total

    Raises:
        BridgeError(INVALID_PARAMS): limit 越界（HTTP 400）
        BridgeError(DB_NOT_FOUND): 未找到微信消息 DB（HTTP 500）
        BridgeError(DB_ENCRYPTED): DB 已加密（HTTP 503）

    Notes:
        - 查询走 cp 快照 + sqlite3 同步阻塞，asyncio.to_thread 包装
        - keyword 为空时仍走索引，不会全表扫描
    """
    if limit < 1 or limit > 200:
        raise BridgeError(
            code="INVALID_PARAMS",
            message=f"limit 必须在 1~200 之间，收到 {limit}",
        )

    db_reader = _require_db_reader()

    # DB 加密时返回 DB_ENCRYPTED，而非空数据
    await _check_db_readable()

    result = await asyncio.to_thread(db_reader.get_contacts, keyword, limit)
    contacts_list = result.get("contacts", [])
    logger.info(
        "contacts: keyword='%s' limit=%d → 返回 %d 条, total=%d",
        keyword, limit, len(contacts_list), result.get("total", 0),
    )
    return ContactsResponse(
        contacts=[Contact(**c) for c in contacts_list],
        total=result.get("total", 0),
    )


# ---------------------------------------------------------------------------
# 路由：GET /api/contacts/{wxid}
# ---------------------------------------------------------------------------
@router.get("/api/contacts/{wxid}", response_model=Contact)
@with_db_retry
async def get_contact_detail(wxid: str) -> Contact:
    """单条联系人详情查询。

    Args:
        wxid: 路径参数，联系人 wxid（如 wxid_xxx 或 gh_xxx 公众号）

    Returns:
        Contact：单条联系人记录

    Raises:
        BridgeError(DB_NOT_FOUND): 未找到微信消息 DB（HTTP 500）
        BridgeError(DB_ENCRYPTED): DB 已加密（HTTP 503）
        BridgeError(CONTACT_NOT_FOUND): 指定 wxid 不存在（HTTP 404）

    Notes:
        - 不存在的 wxid 返回 CONTACT_NOT_FOUND（404），与 DB 不可达错误区分
        - 群聊 username（xxx@chatroom）也可用本接口查（DbReader 视为联系人）
    """
    db_reader = _require_db_reader()

    # DB 加密时返回 DB_ENCRYPTED，而非空数据
    await _check_db_readable()

    result = await asyncio.to_thread(db_reader.get_contact_detail, wxid)
    if result is None:
        raise BridgeError(
            code="CONTACT_NOT_FOUND",
            message=f"未找到联系人: {wxid}",
        )
    return Contact(**result)


# ---------------------------------------------------------------------------
# 路由：GET /api/groups
# ---------------------------------------------------------------------------
@router.get("/api/groups", response_model=GroupsResponse)
@with_db_retry
async def get_groups(limit: int = 50) -> GroupsResponse:
    """群聊列表查询。

    Args:
        limit: 1~200，默认 50

    Returns:
        GroupsResponse：含 groups 列表（每项为 Contact 结构）/ total

    Raises:
        BridgeError(INVALID_PARAMS): limit 越界（HTTP 400）
        BridgeError(DB_NOT_FOUND): 未找到微信消息 DB（HTTP 500）
        BridgeError(DB_ENCRYPTED): DB 已加密（HTTP 503）

    Notes:
        - 群聊判定条件：username LIKE '%@chatroom'
        - 返回结构复用 Contact（群也存于 contact 表，字段相同）
    """
    if limit < 1 or limit > 200:
        raise BridgeError(
            code="INVALID_PARAMS",
            message=f"limit 必须在 1~200 之间，收到 {limit}",
        )

    db_reader = _require_db_reader()

    # DB 加密时返回 DB_ENCRYPTED，而非空数据
    await _check_db_readable()

    result = await asyncio.to_thread(db_reader.get_groups, limit)
    groups_list = result.get("groups", [])
    logger.info(
        "groups: limit=%d → 返回 %d 个群, total=%d",
        limit, len(groups_list), result.get("total", 0),
    )
    return GroupsResponse(
        groups=[Contact(**g) for g in groups_list],
        total=result.get("total", 0),
    )


# ---------------------------------------------------------------------------
# 路由：GET /api/groups/{wxid}/members
# ---------------------------------------------------------------------------
@router.get("/api/groups/{wxid}/members", response_model=GroupMembersResponse)
@with_db_retry
async def get_group_members(wxid: str) -> GroupMembersResponse:
    """群成员列表查询。

    Args:
        wxid: 路径参数，群聊 wxid（形如 xxxxx@chatroom）

    Returns:
        GroupMembersResponse：含 members 列表 / total / group_wxid

    Raises:
        BridgeError(DB_NOT_FOUND): 未找到微信消息 DB（HTTP 500）
        BridgeError(DB_ENCRYPTED): DB 已加密（HTTP 503）

    Notes:
        - 群成员信息存于 group_members 表，DbReader 联表查询
        - 若群 wxid 不存在或无成员记录，返回空列表而非错误
    """
    db_reader = _require_db_reader()

    # DB 加密时返回 DB_ENCRYPTED，而非空数据
    await _check_db_readable()

    result = await asyncio.to_thread(db_reader.get_group_members, wxid)
    return GroupMembersResponse(**result)


# ---------------------------------------------------------------------------
# 路由：POST /api/contacts/{wxid}/remark (experimental)
# ---------------------------------------------------------------------------
@router.post("/api/contacts/{wxid}/remark", response_model=SetRemarkResponse)
async def set_contact_remark(wxid: str, req: SetRemarkRequest) -> SetRemarkResponse:
    """修改好友备注名（experimental）。

    UI 自动化路径：定位会话 → 点右上角"..."→ 备注 → 输入 → 完成。

    Args:
        wxid: 路径参数，好友 wxid
        req: SetRemarkRequest（wxid 应与路径参数一致，req.wxid 优先）

    Returns:
        SetRemarkResponse

    Raises:
        BridgeError(INVALID_PARAMS): wxid/remark 为空（HTTP 400）
        BridgeError(WECHAT_NOT_LOGGED_IN): 未登录（HTTP 401）
        BridgeError(WINDOW_NOT_FOUND / SEND_FAILED)

    Notes:
        - experimental：菜单项与按钮坐标均为估算值
        - 修改是否真正生效无法校验（无 UI 元素检测）
    """
    # 路径参数 wxid 为权威标识（body 中不再含 wxid 字段）
    if not wxid:
        raise BridgeError(code="INVALID_PARAMS", message="wxid 不能为空")
    if not req.remark:
        raise BridgeError(code="INVALID_PARAMS", message="remark 不能为空")

    xdotool = _require_xdotool()
    send_queue = _require_send_queue()
    db_reader = _require_db_reader()

    login_state = await xdotool.detect_login_state()
    if login_state != "logged_in":
        raise BridgeError(
            code="WECHAT_NOT_LOGGED_IN",
            message=f"当前登录态为 {login_state}，无法修改备注",
        )

    # 解析 display_name（优先 req.display_name，其次 DB 查旧 remark/nickname，回退 wxid）
    display_name = await _resolve_display_name(
        db_reader, wxid, req.display_name
    )

    try:
        await send_queue.enqueue(
            lambda: xdotool.set_contact_remark(wxid, req.remark, display_name)
        )
    except BridgeError:
        raise
    except Exception as e:
        raise BridgeError(
            code="SEND_FAILED",
            message=f"修改备注失败: {e}",
        )

    logger.info(
        "contacts/remark: wxid=%s remark=%s → UI 操作完成",
        wxid, req.remark,
    )

    # Task 11: post_verify 校验备注是否真正写入 DB
    # 先失效 display_name 缓存（remark 已变，旧缓存值不再有效）
    _display_name_cache.pop(wxid, None)
    verified: bool | None = None
    try:
        verified = await xdotool._contact.post_verify_set_contact_remark(
            db_reader, wxid, req.remark
        )
    except Exception as exc:
        logger.warning(
            "contacts/remark: post_verify 异常 (wxid=%s): %s", wxid, exc,
        )
        verified = False
    return SetRemarkResponse(success=True, error=None, verified=verified)


# ---------------------------------------------------------------------------
# 路由：POST /api/friends/add (experimental)
# ---------------------------------------------------------------------------
@router.post("/api/friends/add", response_model=AddFriendResponse)
async def add_friend(req: AddFriendRequest) -> AddFriendResponse:
    """搜索并添加好友（experimental）。

    UI 自动化路径：激活窗口 → Ctrl+F 搜索关键词 → 点"添加联系人" →
    可选填验证消息 → 点"发送"提交好友请求。

    Args:
        req: AddFriendRequest

    Returns:
        AddFriendResponse

    Raises:
        BridgeError(INVALID_PARAMS): keyword 为空（HTTP 400）
        BridgeError(WECHAT_NOT_LOGGED_IN): 未登录（HTTP 401）
        BridgeError(WINDOW_NOT_FOUND / SEND_FAILED)

    Notes:
        - experimental：搜索结果与按钮坐标均为估算值
        - 无法校验对方是否已收到请求
    """
    if not req.keyword:
        raise BridgeError(code="INVALID_PARAMS", message="keyword 不能为空")

    xdotool = _require_xdotool()
    send_queue = _require_send_queue()
    db_reader = _require_db_reader()

    login_state = await xdotool.detect_login_state()
    if login_state != "logged_in":
        raise BridgeError(
            code="WECHAT_NOT_LOGGED_IN",
            message=f"当前登录态为 {login_state}，无法添加好友",
        )

    try:
        await send_queue.enqueue(
            lambda: xdotool.add_friend(req.keyword, req.message)
        )
    except BridgeError:
        raise
    except Exception as e:
        raise BridgeError(
            code="SEND_FAILED",
            message=f"添加好友失败: {e}",
        )

    logger.info(
        "friends/add: keyword=%s message_len=%d → UI 操作完成",
        req.keyword, len(req.message) if req.message else 0,
    )

    # Task 11: post_verify 校验好友申请是否已发送
    verified: bool | None = None
    try:
        verified = await xdotool._contact.post_verify_add_friend(
            db_reader, req.keyword
        )
    except Exception as exc:
        logger.warning(
            "friends/add: post_verify 异常 (keyword=%s): %s", req.keyword, exc,
        )
        verified = False
    return AddFriendResponse(success=True, error=None, verified=verified)


# ---------------------------------------------------------------------------
# 路由：好友申请自动通过
# ---------------------------------------------------------------------------
@router.get("/api/friends/requests", response_model=FriendRequestsResponse)
@with_db_retry
async def list_friend_requests(limit: int = 50) -> FriendRequestsResponse:
    """查询待处理的好友申请列表（从 fmessage 系统消息解析）。

    Args:
        limit: 1~200，默认 50

    Returns:
        FriendRequestsResponse：含 requests 列表 / total

    Raises:
        BridgeError(INVALID_PARAMS): limit 越界（HTTP 400）
        BridgeError(DB_NOT_FOUND): 未找到微信消息 DB（HTTP 500）
        BridgeError(DB_ENCRYPTED): DB 已加密（HTTP 503）
    """
    if limit < 1 or limit > 200:
        raise BridgeError(
            code="INVALID_PARAMS",
            message=f"limit 必须在 1~200 之间，收到 {limit}",
        )
    db_reader = _require_db_reader()
    await _check_db_readable()

    # get_friend_requests_since(cursor_create_time, cursor_local_id, limit)
    # 查询全部待处理申请：cursor_create_time=0, cursor_local_id=0
    result = await asyncio.to_thread(
        db_reader.get_friend_requests_since, 0, 0, limit
    )
    if result is None:
        # DB 不可读（_ensure_decrypted 抛 BridgeError 已被 _check_db_readable 拦截，
        # 此处兜底防御）
        return FriendRequestsResponse(requests=[], total=0)

    # 解析 XML 提取结构化信息
    requests = []
    for item in result.get("requests", []):
        info = parse_friend_request(
            item["content"], item["create_time"], item["local_id"]
        )
        if info is not None:
            requests.append(FriendRequestItem(
                stranger_wxid=info.stranger_wxid,
                nickname=info.nickname,
                verify_message=info.verify_message,
                scene=info.scene,
                create_time=info.create_time,
            ))
    return FriendRequestsResponse(requests=requests, total=len(requests))


@router.post("/api/friends/accept", response_model=AcceptFriendResponse)
async def accept_friend(req: AcceptFriendRequest) -> AcceptFriendResponse:
    """手动通过好友申请（experimental）。

    Args:
        req: AcceptFriendRequest

    Returns:
        AcceptFriendResponse

    Raises:
        BridgeError(INVALID_PARAMS): stranger_wxid 为空（HTTP 400）
        BridgeError(WECHAT_NOT_LOGGED_IN): 未登录（HTTP 401）
        BridgeError(WINDOW_NOT_FOUND / SEND_FAILED / RATE_LIMITED)
    """
    logger.info(
        "friends/accept: 收到手动通过好友申请 wxid=%s nickname=%s",
        req.stranger_wxid, req.nickname,
    )
    if not req.stranger_wxid:
        logger.warning("friends/accept: 参数校验失败 — stranger_wxid 为空")
        raise BridgeError(code="INVALID_PARAMS", message="stranger_wxid 不能为空")

    xdotool = _require_xdotool()
    send_queue = _require_send_queue()
    db_reader = _require_db_reader()

    t0 = time.perf_counter()
    login_state = await xdotool.detect_login_state()
    logger.info(
        "friends/accept: 登录态检测 → %s (%.0fms)",
        login_state, (time.perf_counter() - t0) * 1000,
    )
    if login_state != "logged_in":
        logger.warning("friends/accept: 拒绝通过，登录态=%s", login_state)
        raise BridgeError(
            code="WECHAT_NOT_LOGGED_IN",
            message=f"当前登录态为 {login_state}，无法通过好友申请",
        )

    logger.info(
        "friends/accept: 入队 send_queue (pending=%d)",
        send_queue.pending_count(),
    )
    t0 = time.perf_counter()
    try:
        # lambda 用默认参数捕获 req（SendQueue.enqueue 延迟执行，避免变量被覆盖）
        await send_queue.enqueue(
            lambda req=req: xdotool.accept_friend_request(
                stranger_wxid=req.stranger_wxid,
                nickname=req.nickname or "",
            )
        )
    except BridgeError:
        # 透传 BridgeError（RATE_LIMITED / SEND_FAILED / WINDOW_NOT_FOUND 等）
        logger.warning(
            "friends/accept: 入队/执行失败 BridgeError (%.0fms)",
            (time.perf_counter() - t0) * 1000,
        )
        raise
    except Exception as e:
        logger.warning(
            "friends/accept: 入队/执行失败 %s: %s (%.0fms)",
            type(e).__name__, e, (time.perf_counter() - t0) * 1000,
        )
        raise BridgeError(code="SEND_FAILED", message=f"通过好友申请失败: {e}")

    logger.info(
        "friends/accept: wxid=%s nickname=%s → UI 操作完成 (%.0fms)",
        req.stranger_wxid, req.nickname,
        (time.perf_counter() - t0) * 1000,
    )

    # Task 10: 复用 _verify_accept_with_retry 校验好友是否真正通过
    t0 = time.perf_counter()
    verified: bool | None = None
    try:
        verified = await _verify_accept_with_retry(
            db_reader, req.stranger_wxid
        )
    except Exception as exc:
        logger.warning(
            "friends/accept: post_verify 异常 (wxid=%s): %s (%.0fms)",
            req.stranger_wxid, exc, (time.perf_counter() - t0) * 1000,
        )
        verified = False
    logger.info(
        "friends/accept: ✓ wxid=%s verified=%s post_verify=%.0fms",
        req.stranger_wxid, verified, (time.perf_counter() - t0) * 1000,
    )
    return AcceptFriendResponse(success=True, error=None, verified=verified)


@router.get("/api/friends/auto_accept/config", response_model=AcceptRuleConfig)
async def get_auto_accept_config() -> AcceptRuleConfig:
    """查询自动通过规则配置。"""
    rule_engine = _require_rule_engine()
    return await rule_engine.get_config()


@router.put("/api/friends/auto_accept/config", response_model=AcceptRuleConfig)
async def update_auto_accept_config(config: AcceptRuleConfig) -> AcceptRuleConfig:
    """更新自动通过规则配置（热生效）。"""
    logger.info(
        "friends/auto_accept/config: 更新配置 enabled=%s accept_all=%s",
        config.enabled, config.accept_all,
    )
    rule_engine = _require_rule_engine()
    await rule_engine.update_config(config)
    # 同步更新 watcher 的 enabled 状态
    watcher = _require_friend_watcher()
    watcher.set_enabled(config.enabled)
    # 启用 auto_accept 时确保 watcher 协程已启动
    # （lifespan 仅在初始 auto_accept_enabled=True 时启动 watcher，
    # 用户通过 API 从 False 切到 True 时需补启动；start() 幂等，已运行则跳过）
    if config.enabled and not watcher.is_running:
        await watcher.start()
    # 关闭 auto_accept 时停止 watcher 协程，避免继续轮询
    if not config.enabled and watcher.is_running:
        await watcher.stop()
    return config


@router.get("/api/friends/auto_accept/status", response_model=AutoAcceptStatus)
async def get_auto_accept_status() -> AutoAcceptStatus:
    """查询自动通过运行状态。"""
    watcher = _require_friend_watcher()
    db_reader = _require_db_reader()
    breaker = _state.accept_breaker
    db_readable = await asyncio.to_thread(db_reader.is_db_accessible)
    breaker_state = None
    if breaker is not None:
        try:
            breaker_state = breaker.state.value
        except Exception:
            pass
    logger.info(
        "friends/auto_accept/status: running=%s enabled=%s db_readable=%s breaker_state=%s "
        "processed=%d accepted=%d rejected=%d",
        watcher.is_running, watcher.is_enabled, db_readable, breaker_state,
        watcher.processed_count, watcher.accepted_count, watcher.rejected_count,
    )
    return AutoAcceptStatus(
        running=watcher.is_running,
        enabled=watcher.is_enabled,
        processed_count=watcher.processed_count,
        accepted_count=watcher.accepted_count,
        rejected_count=watcher.rejected_count,
        last_processed_time=watcher.last_processed_time,
        cursor_create_time=watcher.cursor_create_time,
        cursor_local_id=watcher.cursor_local_id,
        db_readable=db_readable,
        breaker_state=breaker_state,
    )
