"""Kafka 用量事件生产者.

发布 AIUsageEvent 到 edu.ai.usage topic（coord-final-decisions A2 + 总裁 §2.6）.
派生数据豁免 Outbox 模式（coord-final-decisions A3）：直接通过 aiokafka 发布。

全并行模式：Kafka 不可用时降级（记录警告，不阻断主流程）。
"""

import json
import uuid
from dataclasses import asdict, dataclass
from datetime import UTC, datetime
from typing import Any

import structlog

logger = structlog.get_logger()


@dataclass
class UsageEvent:
    """AI 用量事件（对应 events.proto AIUsageEvent message）."""

    event_id: str = ""
    aggregate_id: str = ""
    event_type: str = "AIUsageRecorded"
    occurred_at: int = 0  # Unix 毫秒时间戳
    user_id: str = ""
    school_id: str = ""
    request_id: str = ""
    provider: str = ""
    model: str = ""
    operation: str = ""
    prompt_tokens: int = 0
    completion_tokens: int = 0
    total_tokens: int = 0
    latency_ms: int = 0
    success: bool = True
    degraded: bool = False
    metadata: dict[str, str] | None = None

    def to_dict(self) -> dict[str, Any]:
        """转换为 dict（用于 JSON 序列化）."""
        data = asdict(self)
        # 确保 event_id 和 occurred_at 已填充
        if not data["event_id"]:
            data["event_id"] = str(uuid.uuid4())
        if not data["occurred_at"]:
            data["occurred_at"] = int(
                datetime.now(UTC).timestamp() * 1000,
            )
        return data

    @classmethod
    def from_usage_record(
        cls,
        user_id: str,
        school_id: str,
        request_id: str,
        provider: str,
        model: str,
        operation: str,
        prompt_tokens: int,
        completion_tokens: int,
        total_tokens: int,
        latency_ms: int,
        success: bool = True,
        degraded: bool = False,
        aggregate_id: str = "",
        metadata: dict[str, str] | None = None,
    ) -> "UsageEvent":
        """从用量数据构建事件."""
        return cls(
            event_id=str(uuid.uuid4()),
            aggregate_id=aggregate_id or request_id or str(uuid.uuid4()),
            event_type="AIUsageRecorded",
            occurred_at=int(datetime.now(UTC).timestamp() * 1000),
            user_id=user_id,
            school_id=school_id,
            request_id=request_id,
            provider=provider,
            model=model,
            operation=operation,
            prompt_tokens=prompt_tokens,
            completion_tokens=completion_tokens,
            total_tokens=total_tokens,
            latency_ms=latency_ms,
            success=success,
            degraded=degraded,
            metadata=metadata,
        )


class KafkaProducer:
    """Kafka 用量事件生产者.

    全并行模式：Kafka 不可用时降级，仅记录日志。
    """

    def __init__(
        self,
        bootstrap_servers: str = "localhost:9092",
        topic: str = "edu.ai.usage",
        transactional_id: str | None = "ai-service-producer",
    ) -> None:
        self._bootstrap_servers = bootstrap_servers
        self._topic = topic
        self._transactional_id = transactional_id
        self._producer: Any = None  # AIOKafkaProducer
        self._started = False

    async def start(self) -> None:
        """启动 Kafka 生产者（超时 10s 降级，避免事务协调器初始化阻塞启动）."""
        try:
            from aiokafka import AIOKafkaProducer

            kwargs: dict[str, Any] = {
                "bootstrap_servers": self._bootstrap_servers,
                "value_serializer": lambda v: json.dumps(v, ensure_ascii=False).encode(
                    "utf-8",
                ),
                "key_serializer": lambda k: k.encode("utf-8") if k else None,
                "enable_idempotence": True,
            }
            if self._transactional_id:
                kwargs["transactional_id"] = self._transactional_id
            self._producer = AIOKafkaProducer(**kwargs)
            import asyncio

            await asyncio.wait_for(self._producer.start(), timeout=10.0)
            self._started = True
            logger.info(
                "kafka_producer_started",
                bootstrap_servers=self._bootstrap_servers,
                topic=self._topic,
            )
        except Exception as exc:  # noqa: BLE001
            logger.warning(
                "kafka_producer_start_failed_degraded",
                error=str(exc),
            )
            self._producer = None
            self._started = False

    async def stop(self) -> None:
        """停止 Kafka 生产者."""
        if self._producer is not None:
            try:
                await self._producer.stop()
                logger.info("kafka_producer_stopped")
            except Exception as exc:  # noqa: BLE001
                logger.warning("kafka_producer_stop_failed", error=str(exc))
            finally:
                self._producer = None
                self._started = False

    async def publish(self, event: UsageEvent) -> None:
        """发布用量事件.

        全并行模式：Kafka 不可用时降级，仅记录日志。

        Args:
            event: 用量事件
        """
        if not self._started or self._producer is None:
            logger.debug(
                "kafka_publish_skipped_not_started",
                event_id=event.event_id,
                operation=event.operation,
            )
            return

        data = event.to_dict()
        try:
            if self._transactional_id:
                # 事务模式：保证 exactly-once
                async with self._producer.transaction():
                    await self._producer.send_and_wait(
                        self._topic,
                        value=data,
                        key=event.user_id,
                    )
            else:
                # 非事务模式（dev mode）
                await self._producer.send_and_wait(
                    self._topic,
                    value=data,
                    key=event.user_id,
                )
            logger.debug(
                "usage_event_published",
                event_id=event.event_id,
                topic=self._topic,
                user_id=event.user_id,
                operation=event.operation,
            )
        except Exception as exc:  # noqa: BLE001
            logger.warning(
                "kafka_publish_failed_degraded",
                error=str(exc),
                event_id=event.event_id,
            )

    @property
    def is_started(self) -> bool:
        """生产者是否已启动."""
        return self._started
