from __future__ import annotations

import argparse
import json
from dataclasses import dataclass
from typing import Any, Callable, Dict, List, Optional, Sequence

from autotouch_cli.exceptions import AutotouchInputError


@dataclass(frozen=True)
class SearchCommandRuntime:
    resolve_token: Callable[[Optional[str], bool], Optional[str]]
    request_api: Callable[..., Any]
    print_json: Callable[[Any, bool], None]
    load_json_input: Callable[..., Any]


def cmd_search_recipe(
    args: argparse.Namespace,
    *,
    runtime: SearchCommandRuntime,
    recipes: Dict[str, Any],
) -> None:
    selected = str(args.type or "all")
    if selected == "all":
        output = {"recipes": recipes}
        if args.out_file:
            with open(args.out_file, "w", encoding="utf-8") as f:
                json.dump(output, f, indent=2)
        runtime.print_json(output, args.compact)
        return

    recipe = recipes.get(selected)
    if not recipe:
        raise AutotouchInputError(f"unknown recipe type '{selected}'")
    output = {"type": selected, **recipe}
    if args.out_file and recipe.get("payload") is not None:
        with open(args.out_file, "w", encoding="utf-8") as f:
            json.dump(recipe["payload"], f, indent=2)
    runtime.print_json(output, args.compact)


def _load_search_payload(
    args: argparse.Namespace,
    *,
    runtime: SearchCommandRuntime,
    default_payload: Dict[str, Any],
) -> Dict[str, Any]:
    explicit = runtime.load_json_input(
        inline_json=getattr(args, "data_json", None),
        file_path=getattr(args, "data_file", None),
        context="data",
        default=None,
    )
    if explicit is None:
        return default_payload
    if not isinstance(explicit, dict):
        raise AutotouchInputError("search payload must be a JSON object")
    return explicit


def cmd_search_companies(args: argparse.Namespace, *, runtime: SearchCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    payload = _load_search_payload(
        args,
        runtime=runtime,
        default_payload={
            "query": str(getattr(args, "query", "") or "").strip(),
            "limit": max(1, min(int(getattr(args, "limit", 25) or 25), 100)),
        },
    )
    data = runtime.request_api(
        "POST",
        "/api/search/companies",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        payload=payload,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def cmd_search_people(args: argparse.Namespace, *, runtime: SearchCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    payload: Dict[str, Any] = {
        "limit": max(1, min(int(getattr(args, "limit", 25) or 25), 100)),
    }
    query = str(getattr(args, "query", "") or "").strip()
    company = str(getattr(args, "company", "") or "").strip()
    location = str(getattr(args, "location", "") or "").strip()
    if query:
        payload["query"] = query
    if company:
        payload["company"] = company
    if location:
        payload["location"] = location
    raw_skills = getattr(args, "skill", None) or []
    skills: List[str] = []
    for raw in raw_skills:
        skills.extend([part.strip() for part in str(raw).split(",") if part.strip()])
    if skills:
        payload["skills"] = skills
    payload = _load_search_payload(args, runtime=runtime, default_payload=payload)
    data = runtime.request_api(
        "POST",
        "/api/search/people",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        payload=payload,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def cmd_search_similar_companies(args: argparse.Namespace, *, runtime: SearchCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    payload: Dict[str, Any] = {
        "company_name": str(getattr(args, "company_name", "") or "").strip(),
        "limit": max(1, min(int(getattr(args, "limit", 25) or 25), 100)),
    }
    company_domain = str(getattr(args, "company_domain", "") or "").strip()
    company_description = str(getattr(args, "company_description", "") or "").strip()
    if company_domain:
        payload["company_domain"] = company_domain
    if company_description:
        payload["company_description"] = company_description
    payload = _load_search_payload(
        args,
        runtime=runtime,
        default_payload=payload,
    )
    data = runtime.request_api(
        "POST",
        "/api/search/similar-companies",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        payload=payload,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def cmd_search_news(args: argparse.Namespace, *, runtime: SearchCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    payload = _load_search_payload(
        args,
        runtime=runtime,
        default_payload={
            "query": str(getattr(args, "query", "") or "").strip(),
            "days_back": max(1, min(int(getattr(args, "days_back", 7) or 7), 365)),
            "limit": max(1, min(int(getattr(args, "limit", 25) or 25), 100)),
        },
    )
    data = runtime.request_api(
        "POST",
        "/api/search/news",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        payload=payload,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def cmd_search_local_businesses(args: argparse.Namespace, *, runtime: SearchCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    payload = _load_search_payload(
        args,
        runtime=runtime,
        default_payload={
            "query": str(getattr(args, "query", "") or "").strip(),
            "limit": max(1, min(int(getattr(args, "limit", 25) or 25), 100)),
            "country": str(getattr(args, "country", "us") or "us").strip(),
            "language": str(getattr(args, "language", "en") or "en").strip(),
            "page": max(1, int(getattr(args, "page", 1) or 1)),
        },
    )
    data = runtime.request_api(
        "POST",
        "/api/search/local-businesses",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        payload=payload,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def cmd_search_reviews(args: argparse.Namespace, *, runtime: SearchCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    payload: Dict[str, Any] = {
        "sort_by": str(getattr(args, "sort_by", "newest") or "newest").strip(),
        "limit": max(1, min(int(getattr(args, "limit", 25) or 25), 100)),
        "country": str(getattr(args, "country", "us") or "us").strip(),
        "language": str(getattr(args, "language", "en") or "en").strip(),
        "page": max(1, int(getattr(args, "page", 1) or 1)),
    }
    for field_name in ("cid", "place_id", "fid", "next_page_token"):
        value = str(getattr(args, field_name, "") or "").strip()
        if value:
            payload[field_name] = value
    payload = _load_search_payload(args, runtime=runtime, default_payload=payload)
    data = runtime.request_api(
        "POST",
        "/api/search/reviews",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        payload=payload,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def cmd_search_filings(args: argparse.Namespace, *, runtime: SearchCommandRuntime) -> None:
    token = runtime.resolve_token(args.token, required=True)
    filing_types: List[str] = []
    for raw in getattr(args, "filing_type", None) or []:
        filing_types.extend([part.strip() for part in str(raw).split(",") if part.strip()])
    payload: Dict[str, Any] = {
        "company_name": str(getattr(args, "company_name", "") or "").strip(),
    }
    if filing_types:
        payload["filing_types"] = filing_types
    payload = _load_search_payload(args, runtime=runtime, default_payload=payload)
    data = runtime.request_api(
        "POST",
        "/api/search/filings",
        base_url=args.base_url,
        token=token,
        use_x_api_key=args.use_x_api_key,
        payload=payload,
        timeout=args.timeout,
        verbose=args.verbose,
    )
    runtime.print_json(data, args.compact)


def register_search_subcommands(
    subparsers: argparse._SubParsersAction[argparse.ArgumentParser],
    *,
    add_api_common_arguments: Callable[[argparse.ArgumentParser], None],
    handlers: Dict[str, Callable[[argparse.Namespace], None]],
    recipe_types: Sequence[str],
) -> None:
    parser = subparsers.add_parser("search", help="Provider-hidden search and list-building operations")
    search_sub = parser.add_subparsers(dest="search_cmd", required=True)

    recipe_parser = search_sub.add_parser("recipe", help="Print search payload recipes")
    recipe_parser.add_argument("--type", choices=["all", *recipe_types], default="all")
    recipe_parser.add_argument("--out-file", help="Optional path to save the selected payload JSON")
    add_api_common_arguments(recipe_parser)
    recipe_parser.set_defaults(func=handlers["recipe"])

    companies_parser = search_sub.add_parser("companies", help="Find companies with provider-hidden neural search")
    companies_parser.add_argument("--query", help="Free-text company query")
    companies_parser.add_argument("--limit", type=int, default=25, help="Max results, 1-100 (default: 25)")
    companies_parser.add_argument("--data-json", help="Explicit request payload JSON")
    companies_parser.add_argument("--data-file", help="Path to request payload JSON file")
    add_api_common_arguments(companies_parser)
    companies_parser.set_defaults(func=handlers["companies"])

    people_parser = search_sub.add_parser("people", help="Find people with provider-hidden neural search")
    people_parser.add_argument("--query", help="Free-text role/persona query")
    people_parser.add_argument("--company", help="Optional company name")
    people_parser.add_argument("--location", help="Optional location")
    people_parser.add_argument("--skill", action="append", help="Optional skill (repeatable; comma-separated also supported)")
    people_parser.add_argument("--limit", type=int, default=25, help="Max results, 1-100 (default: 25)")
    people_parser.add_argument("--data-json", help="Explicit request payload JSON")
    people_parser.add_argument("--data-file", help="Path to request payload JSON file")
    add_api_common_arguments(people_parser)
    people_parser.set_defaults(func=handlers["people"])

    similar_parser = search_sub.add_parser("similar-companies", help="Find companies similar to a seed company")
    similar_parser.add_argument("--company-name", help="Seed company name")
    similar_parser.add_argument("--company-domain", help="Official company domain to ground similarity search")
    similar_parser.add_argument("--company-description", help="Short semantic company description to improve peer matching")
    similar_parser.add_argument("--limit", type=int, default=25, help="Max results, 1-100 (default: 25)")
    similar_parser.add_argument("--data-json", help="Explicit request payload JSON")
    similar_parser.add_argument("--data-file", help="Path to request payload JSON file")
    add_api_common_arguments(similar_parser)
    similar_parser.set_defaults(func=handlers["similar_companies"])

    news_parser = search_sub.add_parser("news", help="Search recent news")
    news_parser.add_argument("--query", help="News query")
    news_parser.add_argument("--days-back", type=int, default=7, help="Lookback window in days (default: 7)")
    news_parser.add_argument("--limit", type=int, default=25, help="Max results, 1-100 (default: 25)")
    news_parser.add_argument("--data-json", help="Explicit request payload JSON")
    news_parser.add_argument("--data-file", help="Path to request payload JSON file")
    add_api_common_arguments(news_parser)
    news_parser.set_defaults(func=handlers["news"])

    local_businesses_parser = search_sub.add_parser("local-businesses", help="Search local businesses")
    local_businesses_parser.add_argument("--query", help="Local business query")
    local_businesses_parser.add_argument("--limit", type=int, default=25, help="Max results, 1-100 (default: 25)")
    local_businesses_parser.add_argument("--country", default="us", help="Country code (default: us)")
    local_businesses_parser.add_argument("--language", default="en", help="Language code (default: en)")
    local_businesses_parser.add_argument("--page", type=int, default=1, help="Page number (default: 1)")
    local_businesses_parser.add_argument("--data-json", help="Explicit request payload JSON")
    local_businesses_parser.add_argument("--data-file", help="Path to request payload JSON file")
    add_api_common_arguments(local_businesses_parser)
    local_businesses_parser.set_defaults(func=handlers["local_businesses"])

    reviews_parser = search_sub.add_parser("reviews", help="Fetch reviews for a known listing")
    reviews_parser.add_argument("--cid", help="Listing CID")
    reviews_parser.add_argument("--place-id", help="Listing place_id")
    reviews_parser.add_argument("--fid", help="Listing fid")
    reviews_parser.add_argument("--sort-by", default="newest", help="Sort order (default: newest)")
    reviews_parser.add_argument("--limit", type=int, default=25, help="Max results, 1-100 (default: 25)")
    reviews_parser.add_argument("--country", default="us", help="Country code (default: us)")
    reviews_parser.add_argument("--language", default="en", help="Language code (default: en)")
    reviews_parser.add_argument("--next-page-token", help="Pagination token")
    reviews_parser.add_argument("--page", type=int, default=1, help="Page number (default: 1)")
    reviews_parser.add_argument("--data-json", help="Explicit request payload JSON")
    reviews_parser.add_argument("--data-file", help="Path to request payload JSON file")
    add_api_common_arguments(reviews_parser)
    reviews_parser.set_defaults(func=handlers["reviews"])

    filings_parser = search_sub.add_parser("filings", help="Research public company filings")
    filings_parser.add_argument("--company-name", help="Public company name")
    filings_parser.add_argument("--filing-type", action="append", help="Filing type (repeatable; comma-separated also supported)")
    filings_parser.add_argument("--data-json", help="Explicit request payload JSON")
    filings_parser.add_argument("--data-file", help="Path to request payload JSON file")
    add_api_common_arguments(filings_parser)
    filings_parser.set_defaults(func=handlers["filings"])
