"""
پایپ‌لاین ورودی RAG: خواندن، چانک، embed و ذخیره در vector store — با پشتیبانی از چند پایگاه دانش

منابع:
۱. لحن هر پایگاه دانش (tone) — sensor_uuid=__global__, kb_name=chat|irrigation|fertilization
۲. پایگاه‌های دانش سه‌گانه — sensor_uuid=__global__, kb_name=chat|irrigation|fertilization
۳. دیتای خاک + هواشناسی هر کاربر از DB — sensor_uuid=uuid, kb_name=__all__
"""
import uuid
from pathlib import Path

from .chunker import chunk_text, chunk_texts
from .config import load_rag_config, RAGConfig
from .embedding import embed_texts
from .observability import classify_exception, log_event, observe_operation, record_metric
from .user_data import load_user_sources, build_user_weather_text
from .vector_store import QdrantVectorStore

TEXT_EXTENSIONS = {".txt", ".md", ".rst", ".json"}

SENSOR_UUID_GLOBAL = "__global__"

KB_NAME_ALL = "__all__"


def _resolve_path(base: Path, p: str) -> Path:
    """تبدیل مسیر نسبی به مطلق نسبت به base پروژه."""
    path = Path(p)
    if not path.is_absolute():
        path = base / path
    return path


def _load_file(path: Path) -> str | None:
    """خواندن یک فایل متنی."""
    if not path.exists() or not path.is_file():
        return None
    try:
        return path.read_text(encoding="utf-8").strip()
    except Exception as exc:
        failure = classify_exception(exc)
        log_event(
            level=40,
            message="rag ingest file load failed",
            source="rag.ingest",
            provider=None,
            operation="load_file",
            result_status="error",
            error_code=failure.error_code,
            path=str(path),
        )
        record_metric("rag.ingest.file_load_failure", error_code=failure.error_code)
        return None


def _load_files_from_dir(dir_path: Path, prefix: str = "kb") -> list[tuple[str, str]]:
    """
    خواندن همه فایل‌های متنی از یک دایرکتوری.
    Returns: [(source_id, content), ...]
    """
    if not dir_path.exists() or not dir_path.is_dir():
        return []
    out: list[tuple[str, str]] = []
    for f in sorted(dir_path.rglob("*")):
        if f.is_file() and f.suffix.lower() in TEXT_EXTENSIONS:
            rel = f.relative_to(dir_path)
            source_id = f"{prefix}:{rel}"
            content = _load_file(f)
            if content:
                out.append((source_id, content))
    return out


def load_sources(
    config: RAGConfig | None = None,
    kb_name: str | None = None,
) -> list[tuple[str, str, str, str]]:
    """
    بارگذاری منابع: لحن‌ها، پایگاه‌های دانش سه‌گانه، دیتای کاربران.
    اگر kb_name مشخص شود، فقط آن پایگاه دانش لود می‌شود.

    Returns:
        [(source_id, content, sensor_uuid, kb_name), ...]
    """
    cfg = config or load_rag_config()
    base = Path(__file__).resolve().parent.parent
    sources: list[tuple[str, str, str, str]] = []

    kbs_to_load = cfg.knowledge_bases.items()
    if kb_name:
        kbs_to_load = [(k, v) for k, v in kbs_to_load if k == kb_name]

    for kbn, kb_cfg in kbs_to_load:
        tone_path = _resolve_path(base, kb_cfg.tone_file)
        content = _load_file(tone_path)
        if content:
            sources.append((f"tone:{kbn}", content, SENSOR_UUID_GLOBAL, kbn))

        kb_path = _resolve_path(base, kb_cfg.path)
        for sid, c in _load_files_from_dir(kb_path, prefix=f"kb:{kbn}"):
            sources.append((sid, c, SENSOR_UUID_GLOBAL, kbn))
        if kb_path.is_file():
            content = _load_file(kb_path)
            if content:
                sources.append((f"kb:{kbn}:{kb_path.name}", content, SENSOR_UUID_GLOBAL, kbn))

    for sid, content in load_user_sources():
        if sid.startswith("user:"):
            sensor_uuid = sid.replace("user:", "")
        elif sid.startswith("weather:"):
            sensor_uuid = sid.replace("weather:", "")
        else:
            sensor_uuid = sid
        sources.append((sid, content, sensor_uuid, KB_NAME_ALL))

    return sources


def ingest(
    recreate: bool = False,
    config: RAGConfig | None = None,
    kb_name: str | None = None,
) -> dict:
    """
    ورودی کامل: منابع را می‌خواند، چانک، embed و به vector store می‌فرستد.
    kb_name اختیاری: اگر مشخص شود فقط آن پایگاه دانش ingest می‌شود.

    Args:
        recreate: اگر True باشد، collection را از نو می‌سازد
        config: تنظیمات RAG
        kb_name: نام پایگاه دانش (chat/irrigation/fertilization) — اختیاری

    Returns:
        آمار ورودی (تعداد چانک، منبع‌ها، خطاها)
    """
    cfg = config or load_rag_config()
    store = QdrantVectorStore(config=cfg)
    with observe_operation(source="rag.ingest", provider=cfg.embedding.provider, operation="ingest"):
        if recreate:
            store.ensure_collection(recreate=True)

        sources = load_sources(config=cfg, kb_name=kb_name)
        if not sources:
            record_metric("rag.ingest.empty_sources", kb_name=kb_name)
            return {"chunks_added": 0, "sources": [], "error": "هیچ منبعی یافت نشد"}

    all_chunks: list[str] = []
    all_metas: list[dict] = []
    all_ids: list[str] = []

    for source_id, content, sensor_uuid, src_kb in sources:
        chunks = chunk_text(content, config=cfg)
        for i, ch in enumerate(chunks):
            uid = str(uuid.uuid4())
            all_ids.append(uid)
            all_chunks.append(ch)
            all_metas.append({
                "source": source_id,
                "chunk_index": i,
                "sensor_uuid": sensor_uuid,
                "kb_name": src_kb,
            })

        if not all_chunks:
            record_metric("rag.ingest.empty_chunks", kb_name=kb_name)
            return {"chunks_added": 0, "sources": [s[0] for s in sources], "error": "هیچ چانکی ساخته نشد"}

        embeddings = embed_texts(all_chunks, config=cfg)
        if len(embeddings) != len(all_chunks):
            record_metric("rag.ingest.embedding_mismatch", kb_name=kb_name)
            return {
                "chunks_added": 0,
                "sources": [s[0] for s in sources],
                "error": f"تعداد embed با چانک‌ها مطابقت ندارد: {len(embeddings)} vs {len(all_chunks)}",
            }

        store.add_documents(
            ids=all_ids,
            embeddings=embeddings,
            documents=all_chunks,
            metadatas=all_metas,
        )
        record_metric("rag.ingest.success", kb_name=kb_name, chunks=len(all_chunks))
        return {
            "chunks_added": len(all_chunks),
            "sources": [s[0] for s in sources],
        }
