"""Project-context tools."""

from __future__ import annotations

import json

from ims_mcp.clients.document import DocumentClient
from ims_mcp.constants import XML_DATASET
from ims_mcp.context import CallContext
from ims_mcp.services.bundler import Bundler
from ims_mcp.services.query_builder import QueryBuilder


def _tagged_title(document: str, tags: list[str]) -> str:
    return document


def _unique_docs(docs: list) -> list:
    seen: set[str] = set()
    out = []
    for doc in docs:
        if doc.id not in seen:
            out.append(doc)
            seen.add(doc.id)
    return out


def _extract_tags(doc) -> set[str]:
    meta = getattr(doc, "meta_fields", {}) or {}
    if isinstance(meta, str):
        try:
            meta = json.loads(meta)
        except Exception:
            meta = {}
    if not isinstance(meta, dict) and hasattr(meta, "__dict__"):
        meta = {k: v for k, v in vars(meta).items() if k != "rag"}
    if isinstance(meta, dict):
        tags = meta.get("tags", [])
    else:
        tags = getattr(meta, "tags", [])
    return {str(tag).lower() for tag in tags if str(tag).strip()}


def _filter_docs_by_all_tags(docs: list, tags: list[str] | None) -> list:
    required = {tag.strip().lower() for tag in (tags or []) if tag and tag.strip()}
    if not required:
        return docs
    return [doc for doc in docs if required.issubset(_extract_tags(doc))]


async def query_project_context(
    call_ctx: CallContext,
    document_client: DocumentClient,
    bundler: Bundler,
    query_builder: QueryBuilder,
    repository_name: str,
    query: str | None = None,
    tags: list[str] | None = None,
    topic: str | None = None,
) -> str:
    try:
        dataset_id = call_ctx.dataset_lookup.get_id(repository_name)
    except Exception as exc:
        return f"Error: failed to resolve project dataset '{repository_name}': {exc}"
    if not dataset_id:
        return f"Error: project dataset not found: {repository_name}"

    try:
        dataset = call_ctx.ragflow.get_dataset(name=repository_name)
    except Exception as exc:
        return f"Error: failed to open project dataset '{repository_name}': {exc}"

    if not dataset:
        return f"Error: project dataset not found: {repository_name}"

    docs = []

    # Keyword search: tags via metadata_condition, query via keywords.
    if tags or (query and query.strip()):
        try:
            docs.extend(
                document_client.list_docs(
                    dataset=dataset,
                    page_size=1000,
                    **query_builder.build_list_params(tags=tags, query=query),
                )
            )
        except Exception as exc:
            return f"Error: failed to list project context: {exc}"

    # Semantic expansion via retrieve when topic is provided.
    if topic and topic.strip():
        try:
            semantic = call_ctx.ragflow.retrieve(
                **query_builder.build_retrieve_params(dataset_ids=[dataset_id], query=topic.strip(), tags=tags)
            )
            for chunk in semantic:
                if getattr(chunk, "document_id", ""):
                    docs.extend(document_client.list_docs(dataset=dataset, doc_id=chunk.document_id, page_size=1))
        except Exception:
            # Semantic expansion should not block keyword results.
            pass

    docs = _filter_docs_by_all_tags(_unique_docs(docs), tags)
    if not docs:
        return "No project context found"
    return bundler.bundle(docs, repository_name)


async def store_project_context(
    call_ctx: CallContext,
    document_client: DocumentClient,
    repository_name: str,
    document: str,
    tags: list[str],
    content: str,
    force: bool = False,
) -> str:
    try:
        dataset_id = call_ctx.dataset_lookup.get_id(repository_name)
    except Exception as exc:
        return f"Error: failed to resolve project dataset '{repository_name}': {exc}"
    if not dataset_id:
        if not force:
            return (
                f"Error: dataset '{repository_name}' not found. "
                "Run discover_projects first, then retry with force=true if creation is intended."
            )
        try:
            dataset = call_ctx.ragflow.create_dataset(name=repository_name)
        except Exception as exc:
            return f"Error: failed to create project dataset '{repository_name}': {exc}"
        call_ctx.dataset_lookup.invalidate()
    else:
        try:
            dataset = call_ctx.ragflow.get_dataset(name=repository_name)
        except Exception as exc:
            return f"Error: failed to open project dataset '{repository_name}': {exc}"

    if not dataset:
        return f"Error: project dataset not found: {repository_name}"

    title = _tagged_title(document=document, tags=tags)
    meta_fields = {"tags": tags}
    try:
        upserted = document_client.upsert_doc(
            dataset=dataset,
            name=title,
            content=content.encode("utf-8"),
            meta_fields=meta_fields,
        )
    except Exception as exc:
        return f"Error: failed to store project context document '{document}': {exc}"
    return f"Stored project context document: {upserted.id}"


async def discover_projects(call_ctx: CallContext, query: str | None = None) -> str:
    datasets = call_ctx.ragflow.list_datasets(page=1, page_size=1000)
    lines = []
    for ds in datasets:
        if query and query.lower() not in ds.name.lower():
            continue
        lines.append(XML_DATASET.format(id=ds.id, name=ds.name))
    return "\n".join(lines) if lines else "No projects found"
