"""AI 网关服务入口.

整合组件（02-architecture-design.md §1.2 完整分层）：
  - HTTP 端点（/v1/ai 前缀，ActionState 信封，10 端点）
  - gRPC server（端口 50058，8 RPC）
  - LLM Provider FailoverChain（4 适配器 + 熔断 + 故障切换）
  - Prompt 模板服务（Jinja2 + YAML）
  - 评估三道防线（RuleValidator + LLMJudge + QualityGate）
  - 用量记录（Redis）+ Kafka 事件发布 + 配额管理
  - 安全层（PII + 输入清洗 + 输出审核）
  - 下游 gRPC 客户端（content/data-ana/iam，全并行用 Mock）
  - 备课工作流（4 步编排 + Redis 状态存储）
  - 限流（Redis 三维度令牌桶）
  - OpenTelemetry + Prometheus
"""

import json
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from typing import Any

import structlog
from fastapi import APIRouter, FastAPI, Path, Request
from fastapi.responses import StreamingResponse
from opentelemetry import trace
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from prometheus_client import make_asgi_app
from redis.asyncio import Redis

from .clients import ContentClientMock, DataAnaClientMock, IamClientMock
from .config import settings
from .grpc_server import create_grpc_server
from .middleware import (
    PermissionGuard,
    RequestIdMiddleware,
    extract_user_context,
    register_error_handlers,
)
from .middleware.permission import (
    PERMISSION_AI_CHAT,
    PERMISSION_AI_EXPRESSION_OPTIMIZE,
    PERMISSION_AI_LESSON_GENERATE,
    PERMISSION_AI_QUESTION_GENERATE,
)
from .models import (
    ChatRequest,
    ChatResponse,
    ConfirmRequest,
    ConfirmResultData,
    ConfirmResultResponse,
    GeneratedQuestionResponse,
    GenerateQuestionRequest,
    LessonPreparationData,
    LessonPreparationRequest,
    LessonPreparationResponse,
    OptimizeExpressionRequest,
    OptimizeExpressionResponse,
    WorkflowStatusData,
    WorkflowStatusResponse,
)
from .prompt_service import PromptTemplateService
from .providers import create_failover_chain
from .rate_limiter import RateLimiter
from .services import ChatService, ExpressionService, QuestionService
from .services.evaluation import QualityGate, RuleValidator
from .usage import KafkaProducer, QuotaEnforcer, UsageRecorder
from .workflow import LessonPlanWorkflowService, WorkflowStateStore

logger = structlog.get_logger()
tracer = trace.get_tracer(__name__)


def init_tracer() -> None:
    """初始化 OpenTelemetry.

    dev_mode=true 时跳过 exporter 初始化，避免本地无 collector 时报错。
    """
    if settings.is_dev:
        logger.info("dev_mode_tracer_skipped", dev_mode=settings.dev_mode)
        return

    provider = TracerProvider()
    endpoint = f"{settings.otel_endpoint.rstrip('/')}/v1/traces"
    exporter = OTLPSpanExporter(endpoint=endpoint)
    provider.add_span_processor(BatchSpanProcessor(exporter))
    trace.set_tracer_provider(provider)
    logger.info("tracer_initialized", otel_endpoint=endpoint)


# ---------------------------------------------------------------------------
# 全局组件（lifespan 中初始化 Redis/Kafka/gRPC 连接）
# ---------------------------------------------------------------------------
_failover_chain = create_failover_chain(settings)
_prompt_service = PromptTemplateService()
_quality_gate = QualityGate(rule_validator=RuleValidator())
_chat_service = ChatService(
    failover_chain=_failover_chain,
    prompt_service=_prompt_service,
    default_model=settings.default_chat_model,
)
_question_service = QuestionService(
    failover_chain=_failover_chain,
    prompt_service=_prompt_service,
    quality_gate=_quality_gate,
    default_model=settings.default_question_model,
)
_expression_service = ExpressionService(
    failover_chain=_failover_chain,
    prompt_service=_prompt_service,
    default_model=settings.default_chat_model,
)

_permission_guard = PermissionGuard(dev_mode=settings.is_dev)

# Redis 依赖组件（lifespan 中注入 redis 连接）
_usage_recorder = UsageRecorder(redis=None)
_kafka_producer = KafkaProducer(
    bootstrap_servers=settings.kafka_bootstrap_servers,
    topic=settings.kafka_ai_usage_topic,
    transactional_id=None if settings.is_dev else settings.kafka_producer_transactional_id,
)
_quota_enforcer = QuotaEnforcer(usage_recorder=_usage_recorder)
_rate_limiter = RateLimiter(
    redis=None,
    user_limit=settings.redis_rate_limit_user_per_min,
    ip_limit=settings.redis_rate_limit_ip_per_min,
    school_limit=settings.redis_rate_limit_school_per_min,
)

# 下游客户端（全并行模式用 Mock，ISSUE-07）
_content_client = ContentClientMock()
_data_ana_client = DataAnaClientMock()
_iam_client = IamClientMock()

_state_store = WorkflowStateStore(
    redis=None,
    ttl_seconds=settings.workflow_ttl_seconds,
)
_workflow_service = LessonPlanWorkflowService(
    state_store=_state_store,
    failover_chain=_failover_chain,
    prompt_service=_prompt_service,
    quality_gate=_quality_gate,
    content_client=_content_client,
    data_ana_client=_data_ana_client,
    default_model=settings.default_question_model,
)

_grpc_server = create_grpc_server(
    port=settings.grpc_port,
    chat_service=_chat_service,
    question_service=_question_service,
    expression_service=_expression_service,
    workflow_service=_workflow_service,
)

_redis: Redis | None = None


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
    """应用生命周期：初始化 → 运行 → 优雅关闭."""
    init_tracer()
    _prompt_service.load()

    # Redis 连接（失败降级，不阻断启动）
    global _redis
    try:
        _redis = Redis.from_url(settings.redis_url, decode_responses=True)
        await _redis.ping()
        logger.info("redis_connected", url=settings.redis_url)
        _usage_recorder._redis = _redis  # noqa: SLF001
        _rate_limiter._redis = _redis  # noqa: SLF001
        _state_store._redis = _redis  # noqa: SLF001
    except Exception as exc:  # noqa: BLE001
        logger.warning("redis_connect_failed_degraded", error=str(exc))
        _redis = None

    await _kafka_producer.start()
    await _grpc_server.start()

    logger.info(
        "ai_service_started",
        http_port=settings.http_port,
        grpc_port=settings.grpc_port,
        dev_mode=settings.is_dev,
        llm_available=settings.llm_available,
    )
    if not settings.llm_available:
        logger.warning("ai_service_llm_degraded_no_api_key")

    yield

    # 优雅关闭（reverse order）
    logger.info("ai_service_stopping")
    await _grpc_server.stop()
    await _kafka_producer.stop()
    if _redis is not None:
        await _redis.aclose()
        logger.info("redis_closed")


app = FastAPI(
    title="AI Gateway Service",
    version="1.0.0",
    lifespan=lifespan,
)

# 中间件 + 可观测性
app.add_middleware(RequestIdMiddleware)
FastAPIInstrumentor.instrument_app(app)
app.mount("/metrics", make_asgi_app())
register_error_handlers(app)

# 业务路由（/v1/ai 前缀，Gateway 代理 /api/v1/ai/* → /v1/ai/*）
router = APIRouter(prefix="/v1/ai")


def _client_ip(request: Request) -> str:
    """提取客户端 IP."""
    return request.client.host if request.client else ""


@app.get("/healthz")
async def healthz() -> dict[str, Any]:
    """健康检查（liveness）."""
    return {"status": "ok", "service": "ai"}


@app.get("/readyz")
async def readyz() -> dict[str, Any]:
    """就绪检查（readiness）."""
    return {
        "status": "ok",
        "service": "ai",
        "llm_configured": settings.llm_available,
        "degraded": not settings.llm_available,
        "grpc_running": _grpc_server.is_running,
        "providers": settings.providers_status,
    }


@router.post("/chat", response_model=ChatResponse)
async def chat(req: ChatRequest, request: Request) -> ChatResponse:
    """非流式聊天."""
    ctx = extract_user_context(request)
    _permission_guard.check(ctx, PERMISSION_AI_CHAT)
    await _rate_limiter.check(
        user_id=ctx.user_id,
        ip=_client_ip(request),
        school_id=ctx.school_id,
    )
    with tracer.start_as_current_span("ai_chat"):
        messages = [{"role": m.role, "content": m.content} for m in req.messages]
        result = await _chat_service.chat(
            messages=messages,
            model=req.model,
            temperature=req.temperature,
            user_id=ctx.user_id,
            session_id=req.session_id,
            data_scope=req.data_scope or ctx.data_scope,
        )
        return ChatResponse(success=True, data=result, error=None)


@router.post("/chat/stream")
async def chat_stream(req: ChatRequest, request: Request) -> StreamingResponse:
    """流式聊天（SSE）."""
    ctx = extract_user_context(request)
    _permission_guard.check(ctx, PERMISSION_AI_CHAT)
    await _rate_limiter.check(
        user_id=ctx.user_id,
        ip=_client_ip(request),
        school_id=ctx.school_id,
    )

    async def generate() -> AsyncGenerator[str, None]:
        with tracer.start_as_current_span("ai_chat_stream"):
            messages = [{"role": m.role, "content": m.content} for m in req.messages]
            async for chunk in _chat_service.stream_chat(
                messages=messages,
                model=req.model,
                temperature=req.temperature,
                user_id=ctx.user_id,
                session_id=req.session_id,
                data_scope=req.data_scope or ctx.data_scope,
            ):
                payload = json.dumps(
                    {"content": chunk.content, "done": chunk.done},
                    ensure_ascii=False,
                )
                yield f"data: {payload}\n\n"

    return StreamingResponse(generate(), media_type="text/event-stream")


@router.post("/generate/question", response_model=GeneratedQuestionResponse)
async def generate_question(
    req: GenerateQuestionRequest,
    request: Request,
) -> GeneratedQuestionResponse:
    """生成题目（非流式）."""
    ctx = extract_user_context(request)
    _permission_guard.check(ctx, PERMISSION_AI_QUESTION_GENERATE)
    await _rate_limiter.check(
        user_id=ctx.user_id,
        ip=_client_ip(request),
        school_id=ctx.school_id,
    )
    with tracer.start_as_current_span("generate_question"):
        result = await _question_service.generate(request=req, user_id=ctx.user_id)
        return GeneratedQuestionResponse(success=True, data=result, error=None)


@router.post("/generate/question/stream")
async def generate_question_stream(
    req: GenerateQuestionRequest,
    request: Request,
) -> StreamingResponse:
    """流式生成题目（SSE）."""
    ctx = extract_user_context(request)
    _permission_guard.check(ctx, PERMISSION_AI_QUESTION_GENERATE)
    await _rate_limiter.check(
        user_id=ctx.user_id,
        ip=_client_ip(request),
        school_id=ctx.school_id,
    )

    async def generate() -> AsyncGenerator[str, None]:
        with tracer.start_as_current_span("generate_question_stream"):
            async for chunk in _question_service.stream_generate(
                request=req,
                user_id=ctx.user_id,
            ):
                if chunk.done and chunk.complete_question is not None:
                    payload = {
                        "done": True,
                        "question": chunk.complete_question.model_dump(),
                    }
                else:
                    payload = {"content": chunk.content, "done": False}
                yield f"data: {json.dumps(payload, ensure_ascii=False)}\n\n"

    return StreamingResponse(generate(), media_type="text/event-stream")


@router.post("/optimize/expression", response_model=OptimizeExpressionResponse)
async def optimize_expression(
    req: OptimizeExpressionRequest,
    request: Request,
) -> OptimizeExpressionResponse:
    """优化表达."""
    ctx = extract_user_context(request)
    _permission_guard.check(ctx, PERMISSION_AI_EXPRESSION_OPTIMIZE)
    await _rate_limiter.check(
        user_id=ctx.user_id,
        ip=_client_ip(request),
        school_id=ctx.school_id,
    )
    with tracer.start_as_current_span("optimize_expression"):
        result = await _expression_service.optimize(
            text=req.text,
            context=req.context,
            user_id=ctx.user_id,
        )
        return OptimizeExpressionResponse(success=True, data=result, error=None)


@router.post("/lesson-plan/generate", response_model=LessonPreparationResponse)
async def generate_lesson_plan(
    req: LessonPreparationRequest,
    request: Request,
) -> LessonPreparationResponse:
    """启动备课工作流."""
    ctx = extract_user_context(request)
    _permission_guard.check(ctx, PERMISSION_AI_LESSON_GENERATE)
    await _rate_limiter.check(
        user_id=ctx.user_id,
        ip=_client_ip(request),
        school_id=ctx.school_id,
    )
    with tracer.start_as_current_span("generate_lesson_plan"):
        state = await _workflow_service.start(
            user_id=ctx.user_id,
            school_id=ctx.school_id,
            class_id=req.class_id,
            subject_id=req.subject_id,
            topic=req.topic,
            target_difficulty=req.target_difficulty,
            question_count=req.question_count,
            request_id=ctx.request_id,
        )
        data = LessonPreparationData(
            workflow_id=state.workflow_id,
            status=state.status,
            estimated_completion_seconds=60,
            degraded=state.status == "failed",
            degraded_reason=state.error or "",
        )
        return LessonPreparationResponse(success=True, data=data, error=None)


@router.get(
    "/lesson-plan/status/{workflow_id}",
    response_model=WorkflowStatusResponse,
)
async def get_lesson_plan_status(
    workflow_id: str = Path(..., description="工作流 ID"),
) -> WorkflowStatusResponse:
    """查询备课工作流状态."""
    state = await _workflow_service.get_status(workflow_id)
    data = WorkflowStatusData(
        workflow_id=state.workflow_id,
        status=state.status,
        questions=state.questions,
        error=state.error,
        degraded=state.status == "failed",
        degraded_reason=state.error or "",
    )
    return WorkflowStatusResponse(success=True, data=data, error=None)


@router.post(
    "/lesson-plan/confirm/{workflow_id}",
    response_model=ConfirmResultResponse,
)
async def confirm_lesson_plan(
    workflow_id: str = Path(..., description="工作流 ID"),
    req: ConfirmRequest | None = None,
) -> ConfirmResultResponse:
    """教师确认备课结果入库."""
    body = req or ConfirmRequest()
    result = await _workflow_service.confirm(
        workflow_id=workflow_id,
        modifications=body.modifications,
    )
    if result.get("error"):
        logger.warning(
            "lesson_plan_confirm_failed",
            workflow_id=workflow_id,
            error=result["error"],
        )
    data = ConfirmResultData(
        success=result.get("success", False),
        persisted_question_ids=result.get("persisted_question_ids", []),
    )
    return ConfirmResultResponse(success=True, data=data, error=None)


app.include_router(router)
