"""应用 1：通用对话（SSE 流式）。

每次进入 chat 页面新建 ai_conversations 记录（不复用），
首条消息注入页面上下文，流式返回 AI 回复。

app_id = "app1_chat"
"""

from __future__ import annotations

import json
import logging
from typing import AsyncGenerator

from app.ai.dashscope_client import DashScopeClient
from app.ai.cache_service import AICacheService
from app.ai.conversation_service import ConversationService
from app.ai.data_fetchers import build_page_text
from app.ai.schemas import SSEEvent

logger = logging.getLogger(__name__)

APP_ID = "app1_chat"

# system prompt 总字符数上限
_MAX_SYSTEM_PROMPT_LEN = 4000


async def chat_stream(
    *,
    message: str,
    user_id: int | str,
    nickname: str,
    role: str,
    site_id: int,
    source_page: str | None = None,
    page_context: dict | None = None,
    screen_content: str | None = None,
    client: DashScopeClient,
    conv_svc: ConversationService,
) -> AsyncGenerator[SSEEvent, None]:
    """流式对话入口，返回 SSEEvent 异步生成器。

    流程：
    1. 创建 conversation 记录
    2. 写入 user message
    3. 构建 system prompt（注入页面上下文）
    4. 调用 bailian.chat_stream 流式获取回复
    5. 逐 chunk yield SSEEvent(type="chunk")
    6. 完成后写入 assistant message，yield SSEEvent(type="done")
    7. 异常时 yield SSEEvent(type="error")
    """
    conversation_id: int | None = None

    try:
        # 1. 每次新建 conversation（不复用）
        source_ctx = _build_source_context(
            source_page=source_page,
            page_context=page_context,
            screen_content=screen_content,
        )
        conversation_id = conv_svc.create_conversation(
            user_id=user_id,
            nickname=nickname,
            app_id=APP_ID,
            site_id=site_id,
            source_page=source_page,
            source_context=source_ctx,
        )
        logger.info(
            "App1 新建对话: conversation_id=%s user_id=%s site_id=%s",
            conversation_id, user_id, site_id,
        )

        # 2. 立即写入 user message
        conv_svc.add_message(
            conversation_id=conversation_id,
            role="user",
            content=message,
        )

        # 3. 构建消息列表（system prompt + user message）
        messages = await _build_messages(
            message=message,
            user_id=user_id,
            nickname=nickname,
            role=role,
            site_id=site_id,
            source_page=source_page,
            page_context=page_context,
            screen_content=screen_content,
        )

        # 4-5. 流式调用百炼，逐 chunk yield
        full_reply_parts: list[str] = []
        async for chunk in bailian.chat_stream(messages):
            full_reply_parts.append(chunk)
            yield SSEEvent(type="chunk", content=chunk)

        # 6. 流式完成，拼接完整回复并写入 assistant message
        full_reply = "".join(full_reply_parts)
        # 百炼流式模式不返回 tokens_used，按字符数估算（粗略）
        estimated_tokens = len(full_reply)
        conv_svc.add_message(
            conversation_id=conversation_id,
            role="assistant",
            content=full_reply,
            tokens_used=estimated_tokens,
        )

        yield SSEEvent(
            type="done",
            conversation_id=conversation_id,
            tokens_used=estimated_tokens,
        )

    except Exception as e:
        logger.error(
            "App1 对话异常: conversation_id=%s error=%s",
            conversation_id, e,
            exc_info=True,
        )
        yield SSEEvent(type="error", message=str(e))


async def _build_messages(
    *,
    message: str,
    user_id: int | str,
    nickname: str,
    role: str,
    site_id: int,
    source_page: str | None,
    page_context: dict | None,
    screen_content: str | None,
) -> list[dict]:
    """构建发送给百炼的消息列表。

    首条 system 消息注入页面上下文和用户信息。
    """
    system_content = await _build_system_prompt(
        user_id=user_id,
        nickname=nickname,
        role=role,
        site_id=site_id,
        source_page=source_page,
        page_context=page_context,
        screen_content=screen_content,
    )

    content_str = json.dumps(system_content, ensure_ascii=False, default=str)
    # system prompt 总字符数控制
    if len(content_str) > _MAX_SYSTEM_PROMPT_LEN:
        # 截断 page_context 中的 data_text
        pc = system_content.get("page_context", {})
        dt = pc.get("data_text", "")
        if dt and len(dt) > 500:
            pc["data_text"] = dt[:500] + "…（已截断）"
            content_str = json.dumps(system_content, ensure_ascii=False, default=str)

    return [
        {"role": "system", "content": content_str},
        {"role": "user", "content": message},
    ]


async def _build_system_prompt(
    *,
    user_id: int | str,
    nickname: str,
    role: str,
    site_id: int,
    source_page: str | None,
    page_context: dict | None,
    screen_content: str | None,
) -> dict:
    """构建 system prompt JSON。

    通过 biz_params.user_prompt_params 传入用户信息，
    注入页面上下文供 AI 理解当前场景。
    """
    prompt: dict = {
        "task": (
            "你是台球门店的 AI 助手，根据用户的问题和当前页面上下文提供帮助。"
            "当 page_context 中包含 memberNickname、contextId 或 data_text 时，"
            "你必须直接使用这些信息回答问题，不要再向用户索要已有的信息。"
            "例如用户在客户详情页提问时，直接基于该客户的数据回答，无需要求提供会员 ID。"
        ),
        "biz_params": {
            "user_prompt_params": {
                "User_ID": str(user_id),
                "Role": role,
                "Nickname": nickname,
            },
        },
    }

    # 注入页面上下文（首条消息）
    page_ctx = await _build_page_context(
        source_page=source_page,
        page_context=page_context,
        screen_content=screen_content,
        site_id=site_id,
    )
    if page_ctx:
        prompt["page_context"] = page_ctx

    return prompt


async def _build_page_context(
    *,
    source_page: str | None,
    page_context: dict | None,
    screen_content: str | None,
    site_id: int,
) -> dict:
    """构建页面上下文信息。

    根据 source_page（contextType）调用 build_page_text 获取结构化文本，
    看板类页面从 page_context 提取筛选参数传入 filters。
    contextType 为空或未识别时返回空 dict（跳过注入）。
    """
    ctx: dict = {}

    if source_page:
        ctx["source_page"] = source_page

        # 从 page_context 提取 contextId 和筛选参数
        context_id = None
        filters: dict = {}
        if page_context:
            context_id = page_context.get("contextId")
            # 看板类页面筛选参数透传
            for key in ("timeDimension", "areaFilter", "dimension", "typeFilter", "projectFilter"):
                if key in page_context:
                    filters[key] = page_context[key]

        # 调用 data_fetcher 获取页面数据文本
        try:
            data_text = await build_page_text(
                source_page=source_page,
                context_id=context_id,
                site_id=site_id,
                filters=filters if filters else None,
            )
            if data_text:
                ctx["data_text"] = data_text
        except Exception:
            logger.warning("页面上下文文本化失败: source_page=%s", source_page, exc_info=True)

    if page_context:
        ctx["page_context"] = page_context
    if screen_content:
        ctx["screen_content"] = screen_content

    return ctx


def _build_source_context(
    *,
    source_page: str | None,
    page_context: dict | None,
    screen_content: str | None,
) -> dict | None:
    """构建存入 ai_conversations.source_context 的 JSON。"""
    ctx: dict = {}
    if source_page:
        ctx["source_page"] = source_page
    if page_context:
        ctx["page_context"] = page_context
    if screen_content:
        ctx["screen_content"] = screen_content
    return ctx if ctx else None
