"""
Knowledge Base router — on-premise 기술 문서 검색 + SR 자동 추천.

외부 API 없이 순수 Python 키워드 매칭으로 RAG 유사 기능 구현:
  1. 쿼리/문서 토크나이징
  2. 키워드 히트 수 + 위치 가중치 기반 스코어링
  3. SR 설명 + 작업 로그 에러를 합산해 자동 추천
"""
import re
from typing import List, Optional

from fastapi import APIRouter, Depends, Query
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from core.auth import get_current_user
from database import get_db
from models import KBDocument, KBDocumentOut, KBSearchResult, SRRequest, User, WorkLog

router = APIRouter(prefix="/api/kb", tags=["kb"])

# ── 한국어 + 영문 공통 불용어 ────────────────────────────────────────────────
_STOPWORDS = {
    "이", "가", "을", "를", "의", "에", "에서", "와", "과", "도", "는", "은",
    "이다", "있다", "하다", "되다", "않다", "그", "및", "또는", "또", "등",
    "the", "a", "an", "is", "are", "was", "were", "in", "on", "at", "to",
    "for", "of", "and", "or", "not", "with", "by", "from", "be",
}

_MIN_TOKEN_LEN = 2


def _tokenize(text: str) -> list[str]:
    """공백·특수문자 기준 분리, 불용어 제거, 소문자 정규화."""
    if not text:
        return []
    tokens = re.split(r'[\s,;:.(){}\[\]<>/\\|&!@#$%^*+=~`\-\'\"]+', text.lower())
    return [t for t in tokens if len(t) >= _MIN_TOKEN_LEN and t not in _STOPWORDS]


def _score_doc(query_tokens: list[str], doc: KBDocument) -> tuple[float, list[str]]:
    """
    문서 관련도 스코어 계산.
    title/tags 히트: 가중치 2.0
    symptoms/cause 히트: 가중치 1.5
    solution/commands 히트: 가중치 1.0
    """
    if not query_tokens:
        return 0.0, []

    # 필드별 가중치 텍스트
    weighted_fields = [
        (doc.title   or "", 2.0),
        (doc.tags    or "", 2.0),
        (doc.symptoms or "", 1.5),
        (doc.cause   or "", 1.5),
        (doc.solution or "", 1.0),
        (doc.commands or "", 1.0),
    ]

    matched: set[str] = set()
    raw_score = 0.0

    for field_text, weight in weighted_fields:
        field_lower = field_text.lower()
        for token in query_tokens:
            if token in field_lower:
                raw_score += weight
                matched.add(token)

    # 정규화: 최대 가능 점수 대비 비율
    max_possible = len(query_tokens) * 2.0  # 모든 토큰이 title/tags 히트 시
    normalized = raw_score / max(max_possible, 1.0)
    # 0.0~1.0 범위 클리핑
    return round(min(normalized, 1.0), 4), sorted(matched)


async def _search_docs(
    db: AsyncSession,
    query: str,
    sr_type_hint: Optional[str] = None,
    limit: int = 5,
) -> list[dict]:
    """핵심 검색 함수 — 라우터·추천 양쪽에서 재사용."""
    tokens = _tokenize(query)
    if not tokens:
        return []

    res  = await db.execute(select(KBDocument))
    docs = res.scalars().all()

    results = []
    for doc in docs:
        score, matched = _score_doc(tokens, doc)
        if score <= 0:
            continue
        # SR 유형 힌트 일치 시 보너스
        if sr_type_hint and doc.sr_type == sr_type_hint:
            score = min(score + 0.15, 1.0)
        results.append({"doc": doc, "score": score, "matched": matched})

    results.sort(key=lambda x: -x["score"])
    return results[:limit]


# ── Endpoints ──────────────────────────────────────────────────────────────────

@router.get("", response_model=List[KBSearchResult])
async def search_kb(
    q: str = Query(..., min_length=2, description="검색 키워드"),
    limit: int = Query(5, ge=1, le=20),
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """키워드 검색 — 관련 기술 문서 반환."""
    hits = await _search_docs(db, q, limit=limit)
    return [
        KBSearchResult(
            doc=KBDocumentOut.model_validate(h["doc"]),
            score=h["score"],
            matched_keywords=h["matched"],
        )
        for h in hits
    ]


@router.get("/suggest/{sr_id}", response_model=List[KBSearchResult])
async def suggest_for_sr(
    sr_id: str,
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """
    SR 설명 + 작업 로그 에러 텍스트를 종합해 관련 KB 문서 자동 추천.
    최대 3건 반환.
    """
    # SR 조회
    r = await db.execute(select(SRRequest).where(SRRequest.sr_id == sr_id))
    sr = r.scalars().first()
    if not sr:
        return []

    # 쿼리 소스 합산: 제목 + 설명 + 최근 작업 로그 결과(에러 행)
    query_parts = [sr.title or "", sr.description or ""]

    wl_res = await db.execute(
        select(WorkLog).where(WorkLog.sr_id == sr_id)
                        .order_by(WorkLog.created_at.desc()).limit(10)
    )
    for wlog in wl_res.scalars().all():
        # 에러·경고 라인만 추출
        for line in (wlog.result or "").splitlines():
            if any(kw in line.upper() for kw in ("ERROR", "WARN", "ORA-", "EXCEPTION", "FAILED")):
                query_parts.append(line)

    combined_query = " ".join(query_parts)
    hits = await _search_docs(db, combined_query, sr_type_hint=sr.sr_type, limit=3)

    return [
        KBSearchResult(
            doc=KBDocumentOut.model_validate(h["doc"]),
            score=h["score"],
            matched_keywords=h["matched"],
        )
        for h in hits
    ]


@router.get("/list", response_model=List[KBDocumentOut])
async def list_kb(
    category: Optional[str] = Query(None),
    db: AsyncSession = Depends(get_db),
    _u: User = Depends(get_current_user),
):
    """전체 KB 문서 목록 (카테고리 필터 선택)."""
    q = select(KBDocument).order_by(KBDocument.category, KBDocument.doc_id)
    if category:
        q = q.where(KBDocument.category == category)
    res = await db.execute(q)
    return res.scalars().all()
