"""
대화형 운영 AI — 자연어 명령으로 ITSM 운영 작업 실행.

엔드포인트:
  POST /api/conv-ops/execute   — 자연어 명령 실행
  GET  /api/conv-ops/history   — 실행 이력
  GET  /api/conv-ops/intents   — 지원 인텐트 목록
  POST /api/conv-ops/feedback  — 피드백

핵심 흐름:
  1. 사용자 자연어 입력 수신
  2. Ollama(localhost:11434)로 intent + params 파싱 (JSON 전용)
  3. intent에 따라 내부 httpx로 기존 API 순차 호출
  4. 각 단계 결과를 steps에 기록, 전체 요약 생성
  5. tb_conv_ops_session에 저장
"""
from __future__ import annotations

import json
import logging
from datetime import datetime
from typing import Any, Dict, List, Optional

import httpx
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
from sqlalchemy import desc, select
from sqlalchemy.ext.asyncio import AsyncSession

from core.auth import get_current_user
from database import get_db, SessionLocal
from models import ConvOpsSession, User

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/conv-ops", tags=["conversational-ops"])

# ── 지원 인텐트 ───────────────────────────────────────────────────────────────

INTENTS: Dict[str, str] = {
    "sr_notify":    "SR 알림 — 특정 SR 상태 조회 및 담당자에게 알림 발송",
    "server_check": "서버 점검 — CMDB 서버 목록 조회 및 상태 확인",
    "deploy":       "배포 — 지정 서버에 배포 작업 SR 생성 및 실행",
    "report":       "보고서 — 일간/주간/월간 운영 보고서 생성",
    "bulk_action":  "일괄 처리 — 여러 SR을 한 번에 상태 변경·배정",
}

# ── Pydantic 스키마 ───────────────────────────────────────────────────────────

class ExecuteRequest(BaseModel):
    user_input: str
    dry_run: bool = False        # True이면 실행 없이 파싱 결과만 반환


class FeedbackRequest(BaseModel):
    session_id: int
    helpful: bool
    comment: Optional[str] = None


class StepResult(BaseModel):
    action: str
    result: Any
    status: str                  # success | failed | skipped


class ExecuteResponse(BaseModel):
    session_id: int
    parsed_intent: Optional[str]
    parsed_params: Optional[Dict[str, Any]]
    steps: List[StepResult]
    summary: str
    success: bool


class IntentInfo(BaseModel):
    intent: str
    description: str
    example: str


# ── Ollama 파싱 헬퍼 ──────────────────────────────────────────────────────────

_OLLAMA_URL = "http://localhost:11434/api/generate"
_PARSE_PROMPT_TMPL = """당신은 ITSM 운영 명령 파서입니다.
다음 자연어 입력을 JSON으로 변환하세요.

지원 인텐트: {intents}

출력 JSON 형식 (이것만 출력, 설명 없음):
{{
  "intent": "<인텐트 키>",
  "params": {{
    "target": "<대상 서버/SR ID/기관명>",
    "action": "<세부 작업>",
    "filters": {{}}
  }},
  "confidence": 0.0
}}

인텐트를 알 수 없으면 "intent": "unknown" 으로 응답하세요.

입력: {user_input}
"""


async def _parse_intent(user_input: str) -> Dict[str, Any]:
    """Ollama로 자연어 → intent+params 파싱. 실패 시 unknown 반환."""
    intent_list = ", ".join(INTENTS.keys())
    prompt = _PARSE_PROMPT_TMPL.format(
        intents=intent_list,
        user_input=user_input,
    )
    try:
        async with httpx.AsyncClient(timeout=30.0) as client:
            resp = await client.post(
                _OLLAMA_URL,
                json={"model": "llama3", "prompt": prompt, "stream": False},
            )
        if resp.status_code == 200:
            raw = resp.json().get("response", "")
            # JSON 블록 추출
            start = raw.find("{")
            end   = raw.rfind("}") + 1
            if start >= 0 and end > start:
                parsed = json.loads(raw[start:end])
                return parsed
    except Exception as exc:
        logger.warning("Ollama 파싱 실패: %s", exc)
    return {"intent": "unknown", "params": {}, "confidence": 0.0}


# ── 내부 API 호출 헬퍼 ────────────────────────────────────────────────────────

_BASE = "http://127.0.0.1:9001"


async def _call_internal(
    method: str,
    path: str,
    token: Optional[str] = None,
    **kwargs,
) -> Dict[str, Any]:
    """내부 ITSM API 호출. 에러 시 상태 포함 dict 반환."""
    headers = {}
    if token:
        headers["Authorization"] = f"Bearer {token}"
    try:
        async with httpx.AsyncClient(timeout=20.0, headers=headers) as client:
            fn = getattr(client, method.lower())
            resp = await fn(f"{_BASE}{path}", **kwargs)
        return {"status_code": resp.status_code, "body": resp.json()}
    except Exception as exc:
        return {"status_code": 500, "body": {"detail": str(exc)}}


# ── 인텐트 실행기 ─────────────────────────────────────────────────────────────

async def _execute_sr_notify(
    params: Dict[str, Any],
    token: Optional[str],
) -> List[StepResult]:
    steps = []
    target = params.get("target", "")

    # 1. SR 조회
    result = await _call_internal("GET", f"/api/tasks?search={target}", token=token)
    ok = result["status_code"] == 200
    steps.append(StepResult(
        action=f"SR 조회 (검색어: {target})",
        result=result["body"],
        status="success" if ok else "failed",
    ))
    return steps


async def _execute_server_check(
    params: Dict[str, Any],
    token: Optional[str],
) -> List[StepResult]:
    steps = []

    # 1. CMDB 서버 목록 조회
    result = await _call_internal("GET", "/api/cmdb/servers?limit=20", token=token)
    ok = result["status_code"] == 200
    steps.append(StepResult(
        action="CMDB 서버 목록 조회",
        result=result["body"],
        status="success" if ok else "failed",
    ))
    return steps


async def _execute_deploy(
    params: Dict[str, Any],
    token: Optional[str],
) -> List[StepResult]:
    steps = []
    target = params.get("target", "")

    # 1. 서버 조회
    result = await _call_internal("GET", f"/api/cmdb/servers?search={target}", token=token)
    ok = result["status_code"] == 200
    steps.append(StepResult(
        action=f"배포 대상 서버 조회 (target={target})",
        result=result["body"],
        status="success" if ok else "failed",
    ))
    return steps


async def _execute_report(
    params: Dict[str, Any],
    token: Optional[str],
) -> List[StepResult]:
    steps = []
    action = params.get("action", "daily")

    # 1. 보고서 목록 조회
    result = await _call_internal("GET", f"/api/report/list?type={action}", token=token)
    ok = result["status_code"] == 200
    steps.append(StepResult(
        action=f"보고서 조회 (유형={action})",
        result=result["body"],
        status="success" if ok else "failed",
    ))
    return steps


async def _execute_bulk_action(
    params: Dict[str, Any],
    token: Optional[str],
) -> List[StepResult]:
    steps = []
    filters = params.get("filters", {})

    # 1. SR 목록 조회
    result = await _call_internal("GET", "/api/tasks?status=RECEIVED&limit=50", token=token)
    ok = result["status_code"] == 200
    steps.append(StepResult(
        action="일괄 처리 대상 SR 조회",
        result=result["body"],
        status="success" if ok else "failed",
    ))
    return steps


_EXECUTOR_MAP = {
    "sr_notify":    _execute_sr_notify,
    "server_check": _execute_server_check,
    "deploy":       _execute_deploy,
    "report":       _execute_report,
    "bulk_action":  _execute_bulk_action,
}


# ── 요약 생성 헬퍼 ────────────────────────────────────────────────────────────

def _build_summary(intent: str, steps: List[StepResult], success: bool) -> str:
    ok_count   = sum(1 for s in steps if s.status == "success")
    fail_count = sum(1 for s in steps if s.status == "failed")
    intent_label = INTENTS.get(intent, intent)
    if success:
        return f"[{intent_label}] 완료 — {ok_count}단계 성공"
    return f"[{intent_label}] 부분 완료 — 성공 {ok_count}건, 실패 {fail_count}건"


# ── 엔드포인트 ────────────────────────────────────────────────────────────────

@router.post("/execute", response_model=ExecuteResponse, summary="자연어 명령 실행")
async def execute_command(
    req: ExecuteRequest,
    current_user: User = Depends(get_current_user),
    db: AsyncSession   = Depends(get_db),
):
    """자연어 명령을 Ollama로 파싱하고 해당 인텐트를 실행한다."""
    # 1. intent 파싱
    parsed = await _parse_intent(req.user_input)
    intent = parsed.get("intent", "unknown")
    params = parsed.get("params", {})

    steps: List[StepResult] = []

    if req.dry_run:
        # dry_run: 실행 없이 파싱 결과만 반환
        steps.append(StepResult(
            action="dry_run — 파싱 결과 확인",
            result=parsed,
            status="success",
        ))
        summary = f"[DRY RUN] 인텐트: {intent}"
        success = True
    elif intent == "unknown" or intent not in _EXECUTOR_MAP:
        steps.append(StepResult(
            action="인텐트 매핑 실패",
            result={"parsed": parsed},
            status="failed",
        ))
        summary = f"지원하지 않는 명령입니다. 지원 인텐트: {', '.join(INTENTS.keys())}"
        success = False
    else:
        # 토큰 추출 (request의 Authorization 헤더에서 가져올 수 없으므로 None 전달)
        # 실제 운영에서는 current_user로 내부 서비스 토큰 발급 가능
        executor = _EXECUTOR_MAP[intent]
        steps = await executor(params, token=None)
        success = all(s.status == "success" for s in steps)
        summary = _build_summary(intent, steps, success)

    # 2. 세션 저장
    session = ConvOpsSession(
        user_input=req.user_input,
        parsed_intent=json.dumps(parsed, ensure_ascii=False),
        steps=json.dumps([s.model_dump() for s in steps], ensure_ascii=False),
        summary=summary,
        success=success,
        created_by=current_user.id,
    )
    db.add(session)
    await db.commit()
    await db.refresh(session)

    return ExecuteResponse(
        session_id=session.id,
        parsed_intent=intent,
        parsed_params=params,
        steps=steps,
        summary=summary,
        success=success,
    )


@router.get("/history", summary="실행 이력 조회")
async def get_history(
    skip:  int = Query(0,  ge=0),
    limit: int = Query(20, ge=1, le=100),
    current_user: User = Depends(get_current_user),
    db: AsyncSession   = Depends(get_db),
):
    """현재 사용자의 대화형 운영 명령 실행 이력을 반환한다."""
    stmt = (
        select(ConvOpsSession)
        .where(ConvOpsSession.created_by == current_user.id)
        .order_by(desc(ConvOpsSession.created_at))
        .offset(skip)
        .limit(limit)
    )
    rows = (await db.execute(stmt)).scalars().all()
    result = []
    for row in rows:
        parsed_intent_data = {}
        if row.parsed_intent:
            try:
                parsed_intent_data = json.loads(row.parsed_intent)
            except Exception:
                parsed_intent_data = {}
        steps_data = []
        if row.steps:
            try:
                steps_data = json.loads(row.steps)
            except Exception:
                steps_data = []
        result.append({
            "id":           row.id,
            "user_input":   row.user_input,
            "parsed_intent": parsed_intent_data,
            "steps":        steps_data,
            "summary":      row.summary,
            "success":      row.success,
            "created_at":   row.created_at.isoformat() if row.created_at else None,
        })
    return {"items": result, "total": len(result)}


@router.get("/intents", summary="지원 인텐트 목록")
async def list_intents(
    current_user: User = Depends(get_current_user),
):
    """지원하는 자연어 명령 인텐트 목록과 설명 및 예시를 반환한다."""
    examples = {
        "sr_notify":    "SR-20260101 상태 알려줘",
        "server_check": "서버 목록 조회해줘",
        "deploy":       "web01 서버에 배포해줘",
        "report":       "이번 주 운영 보고서 만들어줘",
        "bulk_action":  "대기중인 SR 전부 처리해줘",
    }
    return {
        "intents": [
            {
                "intent":      k,
                "description": v,
                "example":     examples.get(k, ""),
            }
            for k, v in INTENTS.items()
        ]
    }


@router.post("/feedback", summary="실행 결과 피드백")
async def submit_feedback(
    req: FeedbackRequest,
    current_user: User = Depends(get_current_user),
    db: AsyncSession   = Depends(get_db),
):
    """실행 결과에 대한 도움 여부 피드백을 기록한다."""
    stmt = select(ConvOpsSession).where(ConvOpsSession.id == req.session_id)
    session = (await db.execute(stmt)).scalars().first()
    if not session:
        raise HTTPException(status_code=404, detail="세션을 찾을 수 없습니다")
    if session.created_by != current_user.id:
        raise HTTPException(status_code=403, detail="본인 세션에만 피드백 가능합니다")

    # 피드백을 summary에 메타데이터로 추가 (별도 컬럼 없이 간소 처리)
    feedback_note = f" [피드백: {'도움됨' if req.helpful else '도움안됨'}]"
    if req.comment:
        feedback_note += f" — {req.comment}"
    if session.summary and "[피드백:" not in session.summary:
        session.summary = (session.summary or "") + feedback_note

    await db.commit()
    return {"ok": True, "session_id": req.session_id}
