from __future__ import annotations

import json
import time
from typing import Annotated, Any, List, Optional, Protocol, Union

from fastapi import Depends, Request
from starlette.responses import PlainTextResponse

from app.schemas.message import MessageClearBefore as _SchemaMessageClearBefore
from app.schemas.message import MessageClearData as _SchemaMessageClearData
from app.schemas.message import MessageClearScope as _SchemaMessageClearScope
from app.schemas.message import MessageHistoryItem as _SchemaMessageHistoryItem
from app.schemas.message import Subscription as _SchemaSubscription
from app.schemas.message import SubscriptionMessage as _SchemaSubscriptionMessage
from app.schemas.message import WebMessageItem as _SchemaWebMessageItem
from app.schemas.response import Response as _SchemaResponse
from app.schemas.token import TokenPayload as _SchemaTokenPayload
from app.api.response import ResponseAPIRouter
from app.chain.message import MessageChain
from app.runtime.config import global_vars
from app.adapters.web.security.access import verify_token, verify_apitoken
from app.api.principal import ApiPrincipal
from app.application.configuration import (
    get_api_runtime_config_snapshot,
    get_configured_system_config,
)
from app.api.dependencies.agent import get_message_query_service
from app.api.dependencies.auth import get_current_active_superuser
from app.application.messaging.message import MessageQueryService
from app.runtime.extensions.service_config import ServiceConfigHelper
from app.runtime.log import logger
from app.adapters.external.wechat_crypt import WXBizMsgCrypt
from app.schemas.types import NotificationChannel, SystemConfigKey
from app.api.context import get_background_task_registry, resolve_background_task_registry
from app.runtime.tasks import TaskRegistry

router = ResponseAPIRouter()

_WNS_DEFAULT_TTL = 86400


class WebPushError(Protocol):
    """Web Push 订阅状态判断所需的最小异常协议。"""

    response: Any  # 推送服务响应，状态码字段由具体 SDK 提供


def is_webpush_subscription_gone(error: WebPushError) -> bool:
    """判断 Web Push 订阅是否已在浏览器或推送服务侧失效。"""
    response: Any = getattr(error, "response", None)
    status_code = getattr(response, "status_code", None) or getattr(
        response,
        "status",
        None,
    )
    return status_code in {404, 410}


def is_wns_endpoint(endpoint: str | None) -> bool:
    """判断是否为 Microsoft WNS 推送端点。"""
    return bool(endpoint and "notify.windows.com" in endpoint)


def webpush_options_for_endpoint(endpoint: str | None) -> dict[str, Any]:
    """返回指定推送端点需要的 pywebpush 附加参数。"""
    if not is_wns_endpoint(endpoint):
        return {}
    return {
        "ttl": _WNS_DEFAULT_TTL,
        "headers": {"X-WNS-Cache-Policy": "cache"},
    }


def _normalize_notification_clear_timestamp(value: Any) -> int:
    """
    规范化通知清理时间戳。
    """
    try:
        normalized_value = int(value or 0)
    except (TypeError, ValueError):
        return 0
    return normalized_value if normalized_value > 0 else 0


def _get_notification_clear_before() -> _SchemaMessageClearBefore:
    """
    读取通知中心清理时间配置。
    """
    value = get_configured_system_config().get(SystemConfigKey.NotificationClearBefore)
    if isinstance(value, dict):
        return _SchemaMessageClearBefore(
            all=_normalize_notification_clear_timestamp(value.get("all")),
            system=_normalize_notification_clear_timestamp(value.get("system")),
            media=_normalize_notification_clear_timestamp(value.get("media")),
        )
    return _SchemaMessageClearBefore(
        all=_normalize_notification_clear_timestamp(value),
    )


def _format_notification_clear_time(value: int) -> Optional[str]:
    """
    将清理时间戳转换为消息表使用的时间字符串。
    """
    if not value:
        return None
    timestamp = value / 1000 if value > 10000000000 else value
    return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(timestamp))


def start_message_chain(body: Any, form: Any, args: Any):
    """
    启动链式任务
    """
    MessageChain().process(body=body, form=form, args=args)


@router.post("/", summary="接收用户消息", response_model=_SchemaResponse[None])
async def user_message(
    task_registry: Annotated[TaskRegistry, Depends(get_background_task_registry)],
    request: Request,
    _: _SchemaTokenPayload = Depends(verify_apitoken),
):
    """
    用户消息响应，配置请求中需要添加参数：token=API_TOKEN&source=消息配置名
    """
    body = await request.body()
    form = await request.form()
    args = request.query_params
    source = args.get("source")
    content_type = request.headers.get("content-type", "")
    body_text = body.decode("utf-8", errors="replace")
    image_markers = [
        marker
        for marker in (
            '"photo"',
            '"document"',
            '"files"',
            '"attachments"',
            '"url_private"',
            '"image/"',
            '"image_url"',
        )
        if marker in body_text
    ]
    logger.info(
        "消息入口收到请求: source=%s, content_type=%s, body_bytes=%s, form_keys=%s, image_markers=%s",
        source,
        content_type,
        len(body),
        list(form.keys()) if form else [],
        image_markers,
    )
    resolve_background_task_registry(task_registry).create_sync(
        start_message_chain, body, form, args, owner="api.message.user"
    )
    return _SchemaResponse(success=True)


@router.post("/web", summary="接收WEB消息", response_model=_SchemaResponse[None])
async def web_message(
    request: Request,
    text: Optional[str] = None,
    current_user: ApiPrincipal = Depends(get_current_active_superuser),
):
    """
    WEB消息响应
    """
    images = None
    content_type = request.headers.get("content-type", "")
    if "application/json" in content_type:
        try:
            payload = await request.json()
        except Exception:
            payload = None
        if isinstance(payload, dict):
            text = payload.get("text", text)
            image = payload.get("image")
            images = payload.get("images")
            if image:
                if isinstance(images, list):
                    images = [*images, image]
                else:
                    images = [image]
            elif isinstance(images, str):
                images = [images]

    MessageChain().handle_message(
        channel=NotificationChannel.Web,
        source=current_user.name,
        userid=current_user.name,
        username=current_user.name,
        text=text or "",
        images=images,
    )
    return _SchemaResponse(success=True)


@router.get("/web", summary="获取WEB消息", response_model=List[_SchemaWebMessageItem])
async def get_web_message(
    _: _SchemaTokenPayload = Depends(verify_token),
    service: MessageQueryService = Depends(get_message_query_service),
    page: Optional[int] = 1,
    count: Optional[int] = 20,
):
    """
    获取WEB消息列表
    """
    return await service.list_web(page=page, count=count)


@router.get("/notification", summary="获取通知消息", response_model=List[_SchemaMessageHistoryItem])
async def get_notification_message(
    _: _SchemaTokenPayload = Depends(verify_token),
    service: MessageQueryService = Depends(get_message_query_service),
    page: Optional[int] = 1,
    count: Optional[int] = 20,
):
    """
    获取系统发送的通知消息列表。
    """
    clear_before = _get_notification_clear_before()
    messages = await service.list_notifications(
        page=page,
        count=count,
        all_clear_before=_format_notification_clear_time(clear_before.all),
        system_clear_before=_format_notification_clear_time(clear_before.system),
        media_clear_before=_format_notification_clear_time(clear_before.media),
    )
    return [_SchemaMessageHistoryItem(**message) for message in messages]


@router.delete(
    "/notification",
    summary="清理通知消息",
    response_model=_SchemaResponse[_SchemaMessageClearData],
)
async def clear_notification_message(
    scope: _SchemaMessageClearScope = _SchemaMessageClearScope.All,
    _: _SchemaTokenPayload = Depends(verify_token),
):
    """
    记录通知中心清理时间，后续通知历史查询会在服务端过滤。
    """
    clear_before = _get_notification_clear_before()
    value = clear_before.model_dump()
    value[scope.value] = int(time.time() * 1000)
    await get_configured_system_config().async_set(SystemConfigKey.NotificationClearBefore, value)
    return _SchemaResponse(success=True, data={"clear_before": value})


def wechat_verify(
    echostr: str,
    msg_signature: str,
    timestamp: Union[str, int],
    nonce: str,
    source: Optional[str] = None,
) -> Any:
    """
    微信验证响应
    """
    # 获取服务配置
    client_configs = ServiceConfigHelper.get_notification_configs()
    if not client_configs:
        return "未找到对应的消息配置"
    client_config = next(
        (
            config
            for config in client_configs
            if config.type == "wechat"
            and config.enabled
            and config.config.get("WECHAT_MODE", "app") != "bot"
            and (not source or config.name == source)
        ),
        None,
    )
    if not client_config:
        return "未找到对应的消息配置"
    try:
        wxcpt = WXBizMsgCrypt(
            sToken=client_config.config.get("WECHAT_TOKEN"),
            sEncodingAESKey=client_config.config.get("WECHAT_ENCODING_AESKEY"),
            sReceiveId=client_config.config.get("WECHAT_CORPID"),
        )
        ret, sEchoStr = wxcpt.VerifyURL(
            sMsgSignature=msg_signature,
            sTimeStamp=timestamp,
            sNonce=nonce,
            sEchoStr=echostr,
        )
        if ret == 0:
            # 验证URL成功，将sEchoStr返回给企业号
            return PlainTextResponse(sEchoStr)
        return "微信验证失败"
    except Exception as err:
        logger.error(f"微信请求验证失败: {str(err)}")
        return str(err)


def vocechat_verify() -> Any:
    """
    VoceChat验证响应
    """
    return {"status": "OK"}


@router.get(
    "/",
    summary="回调请求验证",
    response_model=None,
    responses={
        200: {
            "description": "消息平台原生验证响应",
            "content": {
                "text/plain": {"schema": {"type": "string"}},
                "application/json": {
                    "schema": {
                        "type": "object",
                        "properties": {"status": {"type": "string"}},
                    }
                },
            },
        }
    },
)
def incoming_verify(
    token: Optional[str] = None,
    echostr: Optional[str] = None,
    msg_signature: Optional[str] = None,
    timestamp: Union[str, int] = None,
    nonce: Optional[str] = None,
    source: Optional[str] = None,
    _: _SchemaTokenPayload = Depends(verify_apitoken),
) -> Any:
    """
    微信/VoceChat等验证响应
    """
    logger.info(
        f"收到验证请求: token={token}, echostr={echostr}, "
        f"msg_signature={msg_signature}, timestamp={timestamp}, nonce={nonce}"
    )
    if echostr and msg_signature and timestamp and nonce:
        return wechat_verify(echostr, msg_signature, timestamp, nonce, source)
    return vocechat_verify()


@router.post(
    "/webpush/subscribe",
    summary="客户端webpush通知订阅",
    response_model=_SchemaResponse[None],
)
async def subscribe(
    subscription: _SchemaSubscription, _: _SchemaTokenPayload = Depends(verify_token)
):
    """
    客户端webpush通知订阅
    """
    subinfo = subscription.model_dump()
    global_vars.push_subscription(subinfo)
    logger.debug(f"通知订阅成功: {subinfo}")
    return _SchemaResponse(success=True)


@router.post(
    "/webpush/send", summary="发送webpush通知", response_model=_SchemaResponse[None]
)
def send_notification(
    payload: _SchemaSubscriptionMessage,
    _: _SchemaTokenPayload = Depends(verify_token),
):
    """
    发送webpush通知
    """
    from pywebpush import WebPushException, webpush

    for sub in global_vars.get_subscriptions():
        try:
            webpush(
                subscription_info=sub,
                data=json.dumps(payload.model_dump()),
                vapid_private_key=get_api_runtime_config_snapshot().vapid_private_key,
                vapid_claims={"sub": get_api_runtime_config_snapshot().vapid_subject},
                **webpush_options_for_endpoint(sub.get("endpoint")),
            )
        except WebPushException as err:
            logger.error(f"WebPush发送失败: {str(err)}")
            if is_webpush_subscription_gone(err) and global_vars.remove_subscription(sub):
                logger.info(f"已移除失效WebPush订阅: {sub.get('endpoint')}")
            continue
    return _SchemaResponse(success=True)
