from __future__ import annotations

import argparse
import json
from typing import Any

from embed_1c_code_vectors import code_embedding_model_label, embed_pending_code_vectors
from embed_1c_semantic_cache import DEFAULT_ADAPTER_URL, adapter_call
from rag_embedding_providers import LOCAL_HASHING_MODEL, LOCAL_HASHING_PROVIDER, embed_texts, provider_metadata


DEFAULT_QWEN3_CODE_RETRIEVAL_INSTRUCTION = (
    "Given a natural-language software task, retrieve the relevant 1C Enterprise "
    "BSL source-code fragment that implements or explains it"
)


def prepare_query_embedding_text(
    query: str,
    *,
    embedding_model: str,
    query_instruction: str | None,
) -> tuple[str, str]:
    instruction = query_instruction
    if instruction is None and "qwen3-embedding" in str(embedding_model or "").strip().lower():
        instruction = DEFAULT_QWEN3_CODE_RETRIEVAL_INSTRUCTION
    clean_instruction = str(instruction or "").strip()
    if not clean_instruction:
        return query, ""
    return f"Instruct: {clean_instruction}\nQuery:{query}", clean_instruction


def search_code_vectors(
    *,
    adapter_url: str,
    base_id: str,
    query: str,
    limit: int = 10,
    scan_limit: int = 2000,
    embedding_provider: str = LOCAL_HASHING_PROVIDER,
    embedding_model: str = LOCAL_HASHING_MODEL,
    dimensions: int = 384,
    embedding_base_url: str = "",
    embedding_api_key_env: str = "OPENAI_API_KEY",
    query_instruction: str | None = None,
    embed_pending: bool = False,
    embed_limit: int = 100,
    embed_batch_size: int = 16,
    timeout_seconds: int = 180,
) -> dict[str, Any]:
    refresh = None
    if embed_pending:
        refresh = embed_pending_code_vectors(
            adapter_url=adapter_url,
            base_id=base_id,
            limit=embed_limit,
            batch_size=embed_batch_size,
            embedding_provider=embedding_provider,
            embedding_model=embedding_model,
            dimensions=dimensions,
            embedding_base_url=embedding_base_url,
            embedding_api_key_env=embedding_api_key_env,
            timeout_seconds=timeout_seconds,
        )
    query_embedding_text, applied_instruction = prepare_query_embedding_text(
        query,
        embedding_model=embedding_model,
        query_instruction=query_instruction,
    )
    query_embedding = embed_texts(
        [query_embedding_text],
        provider=embedding_provider,
        model=embedding_model,
        dimensions=dimensions,
        base_url=embedding_base_url,
        api_key_env=embedding_api_key_env,
        timeout_seconds=timeout_seconds,
    )[0]
    stored_model = code_embedding_model_label(
        provider=embedding_provider,
        model=embedding_model,
        dimensions=len(query_embedding),
    )
    result = adapter_call(
        adapter_url,
        "metadata.code_vector.search",
        {
            "base_id": base_id,
            "query": query,
            "query_embedding": query_embedding,
            "embedding_model": stored_model,
            "limit": limit,
            "scan_limit": scan_limit,
            "verify": True,
            "strict": True,
        },
        timeout_seconds=timeout_seconds,
    )
    provider = provider_metadata(
        provider=embedding_provider,
        model=embedding_model,
        dimensions=len(query_embedding),
        base_url=embedding_base_url,
    )
    result["client_embedding"] = {
        "provider": provider.get("embedding_provider"),
        "model": embedding_model,
        "stored_embedding_model": stored_model,
        "dimensions": len(query_embedding),
        "query_instruction": applied_instruction or None,
    }
    if refresh is not None:
        result["embedding_refresh"] = {
            "status": refresh.get("status"),
            "counts": refresh.get("counts") or {},
        }
    return result


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Search BSL code with a local or OpenAI-compatible embedding model."
    )
    parser.add_argument("query")
    parser.add_argument("--adapter-url", default=DEFAULT_ADAPTER_URL)
    parser.add_argument("--base-id", required=True)
    parser.add_argument("--limit", type=int, default=10)
    parser.add_argument("--scan-limit", type=int, default=2000)
    parser.add_argument(
        "--embedding-provider",
        default=LOCAL_HASHING_PROVIDER,
        choices=[LOCAL_HASHING_PROVIDER, "openai-compatible"],
    )
    parser.add_argument("--embedding-model", default=LOCAL_HASHING_MODEL)
    parser.add_argument("--dimensions", type=int, default=384)
    parser.add_argument("--embedding-base-url", default="")
    parser.add_argument("--embedding-api-key-env", default="OPENAI_API_KEY")
    parser.add_argument(
        "--query-instruction",
        default=None,
        help=(
            "Instruction prepended only to the query embedding. "
            "Qwen3 Embedding gets a 1C-code retrieval instruction automatically; "
            "pass an empty value to disable it."
        ),
    )
    parser.add_argument("--embed-pending", action="store_true")
    parser.add_argument("--embed-limit", type=int, default=100)
    parser.add_argument("--embed-batch-size", type=int, default=16)
    parser.add_argument("--timeout-seconds", type=int, default=180)
    parser.add_argument("--json", action="store_true")
    args = parser.parse_args()

    result = search_code_vectors(
        adapter_url=args.adapter_url,
        base_id=args.base_id,
        query=args.query,
        limit=args.limit,
        scan_limit=args.scan_limit,
        embedding_provider=args.embedding_provider,
        embedding_model=args.embedding_model,
        dimensions=args.dimensions,
        embedding_base_url=args.embedding_base_url,
        embedding_api_key_env=args.embedding_api_key_env,
        query_instruction=args.query_instruction,
        embed_pending=args.embed_pending,
        embed_limit=args.embed_limit,
        embed_batch_size=args.embed_batch_size,
        timeout_seconds=args.timeout_seconds,
    )
    if args.json:
        print(json.dumps(result, ensure_ascii=False, indent=2))
    else:
        print(
            f"status={result.get('status')} matches={len(result.get('matches') or [])} "
            f"model={(result.get('client_embedding') or {}).get('stored_embedding_model')}"
        )
        for position, match in enumerate(result.get("matches") or [], start=1):
            print(
                f"{position}. score={float(match.get('score') or 0):.4f} "
                f"object={match.get('object_ref')} routine={(match.get('chunk') or {}).get('routine_name')}"
            )
    return 0 if result.get("status") in {"ok", "not_found"} else 1


if __name__ == "__main__":
    raise SystemExit(main())
