#!/usr/bin/env python3
"""Unified search gateway for Deep Research agents.

This script is the stable project-owned entrypoint that agents should call
instead of vendor MCP tools. MCP search remains optional, while this gateway
keeps routing behavior reproducible across OpenCode, Codex, and future
adapters.
"""

from __future__ import annotations

import argparse
import json
import sys
from dataclasses import asdict
from pathlib import Path


REPO_ROOT = Path(__file__).resolve().parent.parent
if str(REPO_ROOT) not in sys.path:
    sys.path.insert(0, str(REPO_ROOT))

from scripts.lib.search_client import SearchClient, SearchError, SearchHit
from scripts.lib.zenmux_client import load_secrets


ROUTE_HELP = {
    "general": "Tavily -> Exa -> Brave generic web discovery",
    "evidence": "Exa highlights -> Tavily -> Brave controlled evidence discovery",
    "scholar": "Serper Scholar -> generic fallback",
    "patents": "Serper Google Patents -> site:patents.google.com fallback",
    "news": "Serper News -> generic fallback",
    "fda": "FDA-focused discovery for warning letters, enforcement pages, and meeting materials",
}

PROFILE_ROUTES = {
    "biomed_literature": ["scholar", "evidence", "general"],
    "patent_heavy": ["patents", "evidence", "general"],
    "china_market": ["news", "evidence", "general"],
    "investment": ["news", "evidence", "general"],
}

PROFILE_QUERY_PREFIX = {
    "china_market": "(China OR Chinese OR 中国 OR 国内)",
}


def search_route(client: SearchClient, route: str, query: str, args: argparse.Namespace) -> list[SearchHit]:
    if route == "general":
        return client.search(query, num_results=args.num_results)
    if route == "evidence":
        return client.evidence(query, num_results=args.num_results, category=args.exa_category)
    if route == "scholar":
        return client.scholar(query, num_results=args.num_results, year_low=args.year_low)
    if route == "patents":
        return client.patents(query, num_results=args.num_results)
    if route == "news":
        return client.news(query, num_results=args.num_results, time_range=args.time_range)
    if route == "fda":
        return client.fda(query, num_results=args.num_results)
    raise SystemExit(f"unknown route: {route}")


def emit_markdown(route_hits: list[tuple[str, list[SearchHit]]], query: str) -> None:
    print(f"# Search Results: {query}")
    for route, hits in route_hits:
        print()
        print(f"## Route: {route} ({ROUTE_HELP[route]})")
        if not hits:
            print("No results.")
            continue
        for i, hit in enumerate(hits, start=1):
            print(f"{i}. {hit.title or '(untitled)'}")
            print(f"   - URL: {hit.url}")
            if hit.snippet:
                print(f"   - Snippet: {hit.snippet}")


def emit_json(route_hits: list[tuple[str, list[SearchHit]]], query: str) -> None:
    data = {
        "query": query,
        "routes": [
            {
                "route": route,
                "route_help": ROUTE_HELP[route],
                "results": [asdict(hit) for hit in hits],
            }
            for route, hits in route_hits
        ],
    }
    print(json.dumps(data, ensure_ascii=False, indent=2))


def emit_trace_markdown(route_trace: list[dict[str, str]]) -> None:
    print()
    print("## Route Trace")
    for item in route_trace:
        print(f"- {item['route']}: {item['status']} ({item['detail']})")


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description="Deep Research search gateway")
    parser.add_argument("query", help="Search query")
    parser.add_argument(
        "--route",
        choices=sorted(ROUTE_HELP),
        default="general",
        help="Single search route to run",
    )
    parser.add_argument(
        "--profile",
        choices=sorted(PROFILE_ROUTES),
        help="Run a strategy profile instead of a single route",
    )
    parser.add_argument("--num-results", type=int, default=10)
    parser.add_argument(
        "--exa-category",
        choices=["research paper", "news", "company", "financial report", "github", "tweet", "personal site", "pdf"],
        help="Optional Exa category for the evidence route",
    )
    parser.add_argument("--year-low", type=int, help="Lower year bound for scholar searches")
    parser.add_argument("--time-range", choices=["d", "w", "m", "y"], help="Serper news time range")
    parser.add_argument("--json", action="store_true", help="Emit JSON instead of Markdown")
    parser.add_argument("--dry-run", action="store_true", help="Show planned routes without calling APIs")
    parser.add_argument(
        "--strict-specialized",
        action=argparse.BooleanOptionalAction,
        default=True,
        help="Fail fast if scholar/news/patents cannot use Serper",
    )
    parser.add_argument("--trace", action="store_true", help="Include route execution trace")
    return parser


def main() -> int:
    parser = build_parser()
    args = parser.parse_args()
    load_secrets()

    routes = PROFILE_ROUTES[args.profile] if args.profile else [args.route]
    query = args.query
    if args.profile in PROFILE_QUERY_PREFIX:
        query = f"{PROFILE_QUERY_PREFIX[args.profile]} {query}"

    if args.dry_run:
        for route in routes:
            print(f"{route}: {ROUTE_HELP[route]}")
        if query != args.query:
            print(f"query_rewritten: {query}")
        return 0

    try:
        with SearchClient(strict_specialized=args.strict_specialized) as client:
            route_hits = []
            route_trace: list[dict[str, str]] = []
            for route in routes:
                try:
                    hits = search_route(client, route, query, args)
                    route_hits.append((route, hits))
                    route_trace.append({"route": route, "status": "ok", "detail": f"hits={len(hits)}"})
                except SearchError as exc:
                    route_hits.append((route, []))
                    route_trace.append({"route": route, "status": "failed", "detail": str(exc)})
                    if route != "general":
                        continue
                    raise
    except SearchError as exc:
        raise SystemExit(f"search failed: {exc}") from exc

    if args.json:
        data = {
            "query": query,
            "original_query": args.query,
            "strict_specialized": args.strict_specialized,
            "routes": [
                {
                    "route": route,
                    "route_help": ROUTE_HELP[route],
                    "results": [asdict(hit) for hit in hits],
                }
                for route, hits in route_hits
            ],
            "trace": route_trace if args.trace else [],
        }
        print(json.dumps(data, ensure_ascii=False, indent=2))
    else:
        emit_markdown(route_hits, query)
        if args.trace:
            emit_trace_markdown(route_trace)
    return 0


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