"""
索引进度推送服务

通过 Redis Pub/Sub 推送索引进度，前端通过 SSE 订阅。
"""
import json
import logging
from typing import Optional

logger = logging.getLogger(__name__)

# Redis 频道前缀
CHANNEL_PREFIX = "knowledge:indexing:progress:"


class IndexingProgressService:
    """索引进度推送服务"""

    @staticmethod
    def _channel(knowledge_base_id: str) -> str:
        return f"{CHANNEL_PREFIX}{knowledge_base_id}"

    @classmethod
    async def publish(
        cls,
        knowledge_base_id: str,
        document_id: str,
        step: str,
        progress: float,
        message: str = "",
        document_name: str = "",
        error: Optional[str] = None,
    ):
        """
        发布索引进度事件

        Args:
            knowledge_base_id: 知识库 ID
            document_id: 文档 ID
            step: 当前步骤 (extracting/cleaning/chunking/vectorizing/completed/failed)
            progress: 进度 0.0 ~ 1.0
            message: 进度描述
            document_name: 文档名称
            error: 错误信息（仅 failed 步骤）
        """
        try:
            from utils.redis import RedisClient
            client = await RedisClient.get_client()

            event = {
                "document_id": document_id,
                "document_name": document_name,
                "step": step,
                "progress": round(progress, 2),
                "message": message,
            }
            if error:
                event["error"] = error

            await client.publish(
                cls._channel(knowledge_base_id),
                json.dumps(event, ensure_ascii=False),
            )
        except Exception as e:
            logger.warning(f"发布索引进度失败: {e}")

    @classmethod
    async def subscribe(cls, knowledge_base_id: str):
        """
        订阅索引进度事件（异步生成器，用于 SSE）

        Yields:
            dict: 进度事件
        """
        from utils.redis import RedisClient
        client = await RedisClient.get_client()
        pubsub = client.pubsub()
        channel = cls._channel(knowledge_base_id)

        await pubsub.subscribe(channel)
        try:
            async for message in pubsub.listen():
                if message["type"] == "message":
                    try:
                        data = json.loads(message["data"])
                        yield data
                    except (json.JSONDecodeError, TypeError):
                        continue
        finally:
            await pubsub.unsubscribe(channel)
            await pubsub.close()
