"""ppx - Perplexity AI search from your terminal.

Zero dependencies. Just pip install ppxai and go.
"""

import argparse
import json
import os
import socket
import ssl
import sys
import urllib.error
import urllib.request
from pathlib import Path

CHAT_URL = "https://api.perplexity.ai/chat/completions"
SEARCH_URL = "https://api.perplexity.ai/search"

MODES = {
    "ask":      {"model": "sonar",               "timeout": 120, "label": "Ask",           "cost": "~$0.01"},
    "search":   {"model": None,                  "timeout": 30,  "label": "Search",        "cost": "~$0.01"},
    "research": {"model": "sonar-pro",           "timeout": 120, "label": "Research",      "cost": "~$0.05"},
    "reason":   {"model": "sonar-reasoning-pro", "timeout": 120, "label": "Reason",        "cost": "~$0.10"},
    "deep":     {"model": "sonar-deep-research", "timeout": 300, "label": "Deep Research", "cost": "~$3.00"},
}


class PPXError(Exception):
    """All ppx errors."""


def load_api_key():
    """Load API key from env var or config file.

    Checks in order:
    1. PERPLEXITY_API_KEY environment variable
    2. ~/.config/ppx/config (key=value format)
    3. ~/.claude/.env (legacy, for existing users)
    """
    key = os.environ.get("PERPLEXITY_API_KEY", "")
    if key:
        return key

    for rel in (".config/ppx/config", ".claude/.env"):
        cfg = Path.home() / rel
        if cfg.exists():
            try:
                text = cfg.read_text()
            except (OSError, UnicodeDecodeError):
                continue
            for line in text.splitlines():
                line = line.strip()
                if line.startswith("PERPLEXITY_API_KEY="):
                    return line.split("=", 1)[1].strip().strip("\"'")

    return ""


def api_request(url, payload, api_key, timeout):
    """Make an HTTP POST request to the Perplexity API."""
    data = json.dumps(payload).encode()
    req = urllib.request.Request(
        url,
        data=data,
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
        },
        method="POST",
    )
    ctx = ssl.create_default_context()
    try:
        with urllib.request.urlopen(req, timeout=timeout, context=ctx) as resp:
            return json.loads(resp.read().decode())
    except urllib.error.HTTPError as e:
        try:
            body = e.read().decode() if e.fp else ""
        except Exception:
            body = ""
        raise PPXError(f"API error {e.code}: {body}") from None
    except urllib.error.URLError as e:
        raise PPXError(f"Connection error: {e.reason}") from None
    except (TimeoutError, socket.timeout):
        raise PPXError(f"Request timed out after {timeout}s") from None
    except json.JSONDecodeError:
        raise PPXError("Invalid JSON in API response") from None


def chat(query, model, api_key, timeout):
    """Call /chat/completions and return structured result."""
    payload = {"model": model, "messages": [{"role": "user", "content": query}]}
    raw = api_request(CHAT_URL, payload, api_key, timeout)

    answer = ""
    if "choices" in raw and raw["choices"]:
        msg = raw["choices"][0].get("message", {})
        answer = msg.get("content", "")
    else:
        print("Warning: API response missing choices", file=sys.stderr)

    return {
        "answer": answer,
        "citations": raw.get("citations", []),
        "model": raw.get("model", model),
        "usage": raw.get("usage", {}),
    }


def search(query, api_key, timeout, max_results=10, recency=None, domains=None):
    """Call /search and return structured result."""
    payload = {"query": query, "max_results": max_results}
    if recency:
        payload["search_recency_filter"] = recency
    if domains:
        payload["search_domain_filter"] = domains

    raw = api_request(SEARCH_URL, payload, api_key, timeout)
    return {"results": raw.get("results", []), "id": raw.get("id", "")}


def format_text(result, mode):
    """Format API result as human-readable text."""
    lines = []

    if mode == "search":
        results = result.get("results", [])
        lines.append(f"Found {len(results)} results\n")
        for i, r in enumerate(results, 1):
            title = r.get("title", "No title")
            url = r.get("url", "")
            snippet = r.get("snippet", "")[:200]
            date = r.get("date", "")
            lines.append(f"{i}. {title}")
            lines.append(f"   {url}")
            if date:
                lines.append(f"   Date: {date}")
            if snippet:
                lines.append(f"   {snippet}...")
            lines.append("")
    else:
        if result.get("answer"):
            lines.append(result["answer"])

        citations = result.get("citations", [])
        if citations:
            lines.append("\nSources:")
            for i, cite in enumerate(citations[:10], 1):
                if isinstance(cite, dict):
                    url = cite.get("url", cite.get("title", str(cite)))
                else:
                    url = str(cite)
                lines.append(f"  {i}. {url}")

        usage = result.get("usage", {})
        tokens = usage.get("total_tokens", 0)
        if tokens:
            lines.append(f"\nTokens: {tokens}")

    return "\n".join(lines)


def format_json(result):
    """Format API result as JSON."""
    return json.dumps(result, indent=2)


def build_parser():
    """Build the argument parser."""
    parser = argparse.ArgumentParser(
        prog="ppx",
        description="Perplexity AI search from your terminal",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""\
modes:
  (default)    Research with sonar-pro (~$0.05)
  --ask        Quick question with sonar (~$0.01)
  --search     Direct web results, no AI synthesis (~$0.01)
  --reason     Chain-of-thought reasoning (~$0.10)
  --deep       Deep research (~$3.00, requires --yes-expensive)

examples:
  ppx "best practices for Python async"
  ppx --ask "latest Python version"
  ppx --search "FastAPI middleware" --max-results 5
  ppx --reason "Redis vs PostgreSQL for sessions"
  ppx --json "test query"
  ppx --deep --yes-expensive "comprehensive guide to X" """,
    )

    parser.add_argument("query", nargs="?", help="search query (default mode: research)")

    mode = parser.add_mutually_exclusive_group()
    mode.add_argument("--ask", metavar="Q", help="quick question (sonar)")
    mode.add_argument("--search", metavar="Q", help="direct web results, no AI")
    mode.add_argument("--research", metavar="Q", help="AI research (sonar-pro)")
    mode.add_argument("--reason", metavar="Q", help="chain-of-thought reasoning")
    mode.add_argument("--deep", metavar="Q", help="deep research (~$3, needs --yes-expensive)")

    parser.add_argument("--max-results", type=int, default=10, help="max results for --search (default: 10)")
    parser.add_argument("--recency", choices=["day", "week", "month", "year"], help="recency filter for --search")
    parser.add_argument("--domains", nargs="+", help="limit to specific domains")
    parser.add_argument("--json", action="store_true", dest="json_output", help="output raw JSON")
    parser.add_argument("--yes-expensive", action="store_true", help="confirm expensive --deep queries")
    parser.add_argument("--model", help="override model selection")
    parser.add_argument("--version", action="version", version=f"%(prog)s {_get_version()}")

    return parser


def _get_version():
    from ppx import __version__
    return __version__


def _resolve_mode(args):
    """Determine mode and query from parsed args.

    Returns (mode, query) or exits on error.
    """
    for mode_name in ("ask", "search", "research", "reason", "deep"):
        val = getattr(args, mode_name, None)
        if val:
            return mode_name, val

    if args.query:
        return "research", args.query

    return None, None


def main(argv=None):
    """Entry point."""
    parser = build_parser()
    args = parser.parse_args(argv)

    mode, query = _resolve_mode(args)
    if not query:
        parser.print_help()
        sys.exit(1)

    # Deep mode cost guard
    if mode == "deep" and not args.yes_expensive:
        print("--deep costs ~$3 per query!\n", file=sys.stderr)
        print("Options:", file=sys.stderr)
        print("  1. Add --yes-expensive to confirm", file=sys.stderr)
        print("  2. Use --research instead (~$0.05) - usually sufficient", file=sys.stderr)
        print("  3. Use --reason for complex decisions (~$0.10)", file=sys.stderr)
        print(f'\nExample: ppx --deep --yes-expensive "{query}"', file=sys.stderr)
        sys.exit(1)

    # Load API key
    api_key = load_api_key()
    if not api_key:
        print("Error: PERPLEXITY_API_KEY not found.\n", file=sys.stderr)
        print("Set it via:", file=sys.stderr)
        print("  export PERPLEXITY_API_KEY=pplx-...", file=sys.stderr)
        print("  # or", file=sys.stderr)
        print("  mkdir -p ~/.config/ppx", file=sys.stderr)
        print('  echo "PERPLEXITY_API_KEY=pplx-..." > ~/.config/ppx/config', file=sys.stderr)
        sys.exit(1)

    cfg = MODES[mode]
    model = args.model or cfg["model"]
    timeout = cfg["timeout"]
    label = cfg["label"]

    if not args.json_output:
        if mode == "deep":
            print(f"{label} ({model}): {query}")
            print("  (This may take a minute...)", file=sys.stderr)
        elif mode != "search":
            print(f"{label} ({model}): {query}")
        else:
            print(f"Searching: {query}")

    try:
        if mode == "search":
            result = search(query, api_key, timeout,
                            max_results=args.max_results,
                            recency=args.recency,
                            domains=args.domains)
        else:
            result = chat(query, model, api_key, timeout)
    except PPXError as e:
        print(f"\nError: {e}", file=sys.stderr)
        sys.exit(1)

    if args.json_output:
        print(format_json(result))
    else:
        print(format_text(result, mode))
