from uuid import uuid4 from fastapi import APIRouter, Depends, HTTPException, Request, Response from sqlalchemy.ext.asyncio import AsyncSession from app.core.logging import get_logger from app.core.security import get_current_user from app.db.session import get_db from app.models.user import User from app.schemas.ai import ( AIProviderStatusResponse, AlertBriefRequest, AlertBriefResponse, BGPBriefRequest, BGPBriefRecordResponse, BGPBriefRecordSummary, PlaygroundMessageActionResponse, PlaygroundMessageCreateRequest, PlaygroundMessageEditRequest, PlaygroundMessageResendRequest, PlaygroundMessageStopRequest, PlaygroundSessionResponse, PlaygroundSessionUpsertRequest, PlaygroundThreadResponse, SituationalAlertBriefRequest, SituationalAlertBriefResponse, SituationalAnalysisRequest, SituationalAnalysisResponse, ) from app.services.alert_ai_brief import build_alert_brief_request from app.services.ai_client import AIProviderClient, get_ai_provider_client from app.services.bgp_ai_brief import build_bgp_brief_request from app.services.bgp_ai_brief_store import ( get_bgp_brief_record, get_latest_bgp_brief_record, list_bgp_brief_records, save_bgp_brief_record, ) from app.services.playground_session_store import ( get_playground_session, upsert_playground_session, ) from app.services.playground_chat_service import ( create_turn, edit_user_message, get_thread, resend_turn, stop_message, ) from app.services.situational_alert_ai_brief import build_situational_alert_brief_request from app.services.business_logs import emit_business_log, exception_context router = APIRouter() logger = get_logger(__name__, service="api") @router.get("/provider/status", response_model=AIProviderStatusResponse) async def get_ai_provider_status( request: Request, response: Response, current_user: User = Depends(get_current_user), provider_client: AIProviderClient = Depends(get_ai_provider_client), ): request_id = request.headers.get("X-Request-ID") or str(uuid4()) response.headers["X-Request-ID"] = request_id return await provider_client.get_status(request_id=request_id) @router.post("/situational-awareness/analyze", response_model=SituationalAnalysisResponse) async def analyze_situational_awareness( payload: SituationalAnalysisRequest, request: Request, response: Response, current_user: User = Depends(get_current_user), provider_client: AIProviderClient = Depends(get_ai_provider_client), ): request_id = request.headers.get("X-Request-ID") or str(uuid4()) response.headers["X-Request-ID"] = request_id return await provider_client.analyze(payload, request_id=request_id) @router.get("/playground/thread", response_model=PlaygroundThreadResponse | None) async def get_playground_thread( session_key: str = "default", current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): return await get_thread( db, user_id=current_user.id, session_key=session_key, ) @router.get("/playground/session", response_model=PlaygroundSessionResponse | None) async def get_saved_playground_session( session_key: str = "default", current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): return await get_playground_session( db, user_id=current_user.id, session_key=session_key, ) @router.put("/playground/session", response_model=PlaygroundSessionResponse) async def save_playground_session( payload: PlaygroundSessionUpsertRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): return await upsert_playground_session( db, user_id=current_user.id, payload=payload, ) @router.post("/playground/messages", response_model=PlaygroundMessageActionResponse) async def create_playground_message( payload: PlaygroundMessageCreateRequest, current_user: User = Depends(get_current_user), provider_client: AIProviderClient = Depends(get_ai_provider_client), db: AsyncSession = Depends(get_db), ): await emit_business_log( logger, event="ai.playground.message.create", message="Playground message creation requested", category="ai", service="api", module=__name__, user_id=current_user.id, context={"session_key": payload.session_key, "preset": payload.selected_preset_key}, ) return await create_turn( db, user_id=current_user.id, payload=payload, provider_client=provider_client, ) @router.post("/playground/messages/stop", response_model=PlaygroundMessageActionResponse) async def stop_playground_message( payload: PlaygroundMessageStopRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): await emit_business_log( logger, event="ai.playground.message.stop", message="Playground message stop requested", category="ai", service="api", module=__name__, user_id=current_user.id, context={"session_key": payload.session_key, "message_id": payload.message_id}, ) return await stop_message( db, user_id=current_user.id, payload=payload, ) @router.post("/playground/messages/resend", response_model=PlaygroundMessageActionResponse) async def resend_playground_message( payload: PlaygroundMessageResendRequest, current_user: User = Depends(get_current_user), provider_client: AIProviderClient = Depends(get_ai_provider_client), db: AsyncSession = Depends(get_db), ): await emit_business_log( logger, event="ai.playground.message.resend", message="Playground message resend requested", category="ai", service="api", module=__name__, user_id=current_user.id, context={"session_key": payload.session_key, "user_message_id": payload.user_message_id}, ) return await resend_turn( db, user_id=current_user.id, payload=payload, provider_client=provider_client, ) @router.post("/playground/messages/edit", response_model=PlaygroundMessageActionResponse) async def edit_playground_message( payload: PlaygroundMessageEditRequest, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): return await edit_user_message( db, user_id=current_user.id, payload=payload, ) @router.get("/bgp/briefs", response_model=list[BGPBriefRecordSummary]) async def list_saved_bgp_briefs( current_user: User = Depends(get_current_user), ): return list_bgp_brief_records() @router.get("/bgp/briefs/latest", response_model=BGPBriefRecordResponse | None) async def get_latest_saved_bgp_brief( current_user: User = Depends(get_current_user), ): return get_latest_bgp_brief_record() @router.get("/bgp/briefs/{brief_id}", response_model=BGPBriefRecordResponse) async def get_saved_bgp_brief( brief_id: str, current_user: User = Depends(get_current_user), ): record = get_bgp_brief_record(brief_id) if record is None: raise HTTPException(status_code=404, detail="BGP brief not found") return record @router.post("/bgp/brief", response_model=BGPBriefRecordResponse) async def analyze_bgp_brief( payload: BGPBriefRequest, request: Request, response: Response, current_user: User = Depends(get_current_user), provider_client: AIProviderClient = Depends(get_ai_provider_client), db: AsyncSession = Depends(get_db), ): request_id = request.headers.get("X-Request-ID") or str(uuid4()) response.headers["X-Request-ID"] = request_id brief_request, facts, context = await build_bgp_brief_request( db, incident_limit=payload.incident_limit, anomaly_limit=payload.anomaly_limit, collector_limit=payload.collector_limit, ) await emit_business_log( logger, event="ai.brief.bgp.facts_collected", message="BGP brief facts collected", category="ai", service="api", module=__name__, request_id=request_id, user_id=current_user.id, context={ "incident_limit": payload.incident_limit, "anomaly_limit": payload.anomaly_limit, "collector_limit": payload.collector_limit, "fact_count": len(facts or []), }, ) brief_request.preferred_model = payload.preferred_model brief_request.thinking = payload.thinking await emit_business_log( logger, event="ai.brief.bgp.start", message="BGP brief AI analysis started", category="ai", service="api", module=__name__, request_id=request_id, user_id=current_user.id, context={"preferred_model": payload.preferred_model}, ) try: analysis = await provider_client.analyze(brief_request, request_id=request_id) record = save_bgp_brief_record( analysis, request_id=request_id, facts=facts, context=context, ) await emit_business_log( logger, event="ai.brief.bgp.completed", message="BGP brief AI analysis saved", category="ai", service="api", module=__name__, request_id=request_id, user_id=current_user.id, context={"provider": analysis.provider, "model": analysis.model, "brief_id": record.id}, ) return record except Exception as exc: await emit_business_log( logger, event="ai.brief.bgp.failed", message="BGP brief AI analysis failed", category="ai", level="error", service="api", module=__name__, request_id=request_id, user_id=current_user.id, context=exception_context(exc, {"preferred_model": payload.preferred_model}), ) raise @router.post("/alerts/brief", response_model=AlertBriefResponse) async def analyze_alert_brief( payload: AlertBriefRequest, request: Request, response: Response, current_user: User = Depends(get_current_user), provider_client: AIProviderClient = Depends(get_ai_provider_client), db: AsyncSession = Depends(get_db), ): request_id = request.headers.get("X-Request-ID") or str(uuid4()) response.headers["X-Request-ID"] = request_id brief_request, facts, context = await build_alert_brief_request( db, alert_limit=payload.alert_limit, ) await emit_business_log( logger, event="ai.brief.alerts.facts_collected", message="Alert brief facts collected", category="ai", service="api", module=__name__, request_id=request_id, user_id=current_user.id, context={"alert_limit": payload.alert_limit, "fact_count": len(facts or [])}, ) brief_request.preferred_model = payload.preferred_model brief_request.thinking = payload.thinking await emit_business_log( logger, event="ai.brief.alerts.start", message="Alert brief AI analysis started", category="ai", service="api", module=__name__, request_id=request_id, user_id=current_user.id, context={"preferred_model": payload.preferred_model}, ) try: analysis = await provider_client.analyze(brief_request, request_id=request_id) await emit_business_log( logger, event="ai.brief.alerts.completed", message="Alert brief AI analysis completed", category="ai", service="api", module=__name__, request_id=request_id, user_id=current_user.id, context={"provider": analysis.provider, "model": analysis.model}, ) return AlertBriefResponse( **analysis.model_dump(), title=brief_request.title, objective=brief_request.objective, facts=facts, context=context, ) except Exception as exc: await emit_business_log( logger, event="ai.brief.alerts.failed", message="Alert brief AI analysis failed", category="ai", level="error", service="api", module=__name__, request_id=request_id, user_id=current_user.id, context=exception_context(exc, {"preferred_model": payload.preferred_model}), ) raise @router.post("/situational-alerts/brief", response_model=SituationalAlertBriefResponse) async def analyze_situational_alert_brief( payload: SituationalAlertBriefRequest, request: Request, response: Response, current_user: User = Depends(get_current_user), provider_client: AIProviderClient = Depends(get_ai_provider_client), db: AsyncSession = Depends(get_db), ): request_id = request.headers.get("X-Request-ID") or str(uuid4()) response.headers["X-Request-ID"] = request_id brief_request, facts, context = await build_situational_alert_brief_request(db) await emit_business_log( logger, event="ai.brief.situational_alerts.facts_collected", message="Situational alert brief facts collected", category="ai", service="api", module=__name__, request_id=request_id, user_id=current_user.id, context={"fact_count": len(facts or [])}, ) brief_request.preferred_model = payload.preferred_model brief_request.thinking = payload.thinking await emit_business_log( logger, event="ai.brief.situational_alerts.start", message="Situational alert brief AI analysis started", category="ai", service="api", module=__name__, request_id=request_id, user_id=current_user.id, context={"preferred_model": payload.preferred_model}, ) try: analysis = await provider_client.analyze(brief_request, request_id=request_id) await emit_business_log( logger, event="ai.brief.situational_alerts.completed", message="Situational alert brief AI analysis completed", category="ai", service="api", module=__name__, request_id=request_id, user_id=current_user.id, context={"provider": analysis.provider, "model": analysis.model}, ) return SituationalAlertBriefResponse( **analysis.model_dump(), title=brief_request.title, objective=brief_request.objective, facts=facts, context=context, ) except Exception as exc: await emit_business_log( logger, event="ai.brief.situational_alerts.failed", message="Situational alert brief AI analysis failed", category="ai", level="error", service="api", module=__name__, request_id=request_id, user_id=current_user.id, context=exception_context(exc, {"preferred_model": payload.preferred_model}), ) raise