"""Kafka producer（MasteryEvent / WarningTriggered 派生数据事件发布）.

对齐 02-architecture-design.md §12 Outbox 豁免：
  派生数据事件（掌握度/预警）非业务事实，直接 aiokafka producer 发布，
  豁免 transactional outbox 模式（总裁裁决 §2.10）.

事件路由（复用 topic，总裁裁决 §2.11）：
  - edu.insight.mastery.updated  ← MasteryEvent(action=mastery.updated)
  - edu.insight.mastery.updated  ← MasteryEvent(action=warning.triggered)（复用）
  - edu.insight.ai.usage          ← AIUsageEvent（ai 服务发布，本服务消费）

Producer 配置：
  - idempotent=true（防重复）
  - transactional_id=data-ana-producer（事务幂等）
  - acks=all（强一致性）

降级策略：
  - kafka_brokers 未配置或不可达时，发布操作静默失败（记日志），
    不阻塞主流程（掌握度计算/预警评估继续，下游感知不到事件则视为未更新）.
"""

import json
import time
import uuid
from typing import Any

import structlog

from ..config import settings

logger = structlog.get_logger(__name__)

# producer 惰性初始化（避免未安装 aiokafka 时 import 失败）
_producer: Any | None = None
_producer_initialized: bool = False


async def get_producer() -> Any | None:
    """获取 Kafka producer（惰性初始化，失败返回 None 降级）."""
    global _producer, _producer_initialized

    if not settings.kafka_brokers:
        return None

    if _producer_initialized:
        return _producer

    _producer_initialized = True
    try:
        from aiokafka import AIOKafkaProducer  # type: ignore[import-not-found]

        _producer = AIOKafkaProducer(
            bootstrap_servers=settings.kafka_brokers,
            client_id=settings.service_name,
            transactional_id=settings.kafka_producer_transactional_id,
            enable_idempotence=True,
            acks="all",
            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,
            linger_ms=20,  # 批量等待 20ms（平衡延迟与吞吐）
            retry_backoff_ms=100,
            request_timeout_ms=10_000,
        )
        await _producer.start()
        logger.info(
            "kafka_producer_initialized",
            brokers=settings.kafka_brokers,
            transactional_id=settings.kafka_producer_transactional_id,
        )
        return _producer
    except Exception as exc:  # noqa: BLE001
        logger.warning("kafka_producer_init_failed_degraded", error=str(exc))
        _producer = None
        return None


async def close_producer() -> None:
    """关闭 producer（应用退出时调用）."""
    global _producer, _producer_initialized
    if _producer is not None:
        try:
            await _producer.stop()
        except Exception as exc:  # noqa: BLE001
            logger.warning("kafka_producer_close_failed", error=str(exc))
        finally:
            _producer = None
            _producer_initialized = False


async def _publish(
    topic: str,
    payload: dict[str, Any],
    key: str | None = None,
) -> bool:
    """发布消息（producer 不可用时返回 False 降级）."""
    producer = await get_producer()
    if producer is None:
        return False
    try:
        await producer.send_and_wait(topic, value=payload, key=key)
        logger.info(
            "kafka_event_published",
            topic=topic,
            key=key,
            event_id=payload.get("event_id"),
            action=payload.get("action"),
        )
        return True
    except Exception as exc:  # noqa: BLE001
        logger.warning(
            "kafka_publish_failed_degraded",
            topic=topic,
            key=key,
            error=str(exc),
        )
        return False


def _gen_event_id() -> str:
    """生成事件 ID（消费者幂等去重依据）."""
    return str(uuid.uuid4())


def _now_ms() -> int:
    """当前时间戳（毫秒）."""
    return int(time.time() * 1000)


# ===== 派生数据事件发布（Outbox 豁免，总裁裁决 §2.10） =====


async def publish_mastery_updated(
    student_id: str,
    knowledge_point_id: str,
    mastery_level: float,
    previous_level: float,
    metadata: dict[str, str] | None = None,
) -> bool:
    """发布掌握度更新事件（edu.insight.mastery.updated，action=mastery.updated）.

    总裁裁决 §2.10：派生数据事件豁免 Outbox 模式，直接 producer 发布.
    """
    payload = {
        "event_id": _gen_event_id(),
        "action": "mastery.updated",
        "occurred_at": _now_ms(),
        "student_id": student_id,
        "knowledge_point_id": knowledge_point_id,
        "mastery_level": mastery_level,
        "previous_level": previous_level,
        "metadata": metadata or {},
    }
    # key 用 student_id 保证同学生事件顺序（同一 partition）
    return await _publish(
        settings.kafka_mastery_topic,
        payload,
        key=student_id,
    )


async def publish_warning_triggered(
    target_id: str,
    warning_type: str,
    threshold: float,
    current_value: float,
    severity: str = "WARN",
    student_id: str = "",
    knowledge_point_id: str = "",
    metadata: dict[str, str] | None = None,
) -> bool:
    """发布预警触发事件（复用 edu.insight.mastery.updated，action=warning.triggered）.

    总裁裁决 §2.11：warning.triggered 复用 mastery topic（避免新增 topic）.
    """
    payload = {
        "event_id": _gen_event_id(),
        "action": "warning.triggered",
        "occurred_at": _now_ms(),
        "student_id": student_id or target_id,  # 兼容 student_id 为空的场景
        "knowledge_point_id": knowledge_point_id,
        "warning_type": warning_type,
        "target_id": target_id,
        "threshold": threshold,
        "current_value": current_value,
        "severity": severity,
        "metadata": metadata or {},
    }
    # key 用 target_id 保证同目标预警事件顺序
    return await _publish(
        settings.kafka_warning_topic,  # 复用 mastery topic
        payload,
        key=target_id,
    )


async def ping() -> bool:
    """Kafka producer 连通性检查（供 /readyz 使用）.

    aiokafka 无显式 ping API，已初始化即视为可用.
    """
    producer = await get_producer()
    return producer is not None
