#!/usr/bin/env python3
"""
Perplexity CLI - Simple wrapper for Perplexity API.

Zero external dependencies - uses only Python stdlib.
Provides web-grounded AI answers with automatic citations.

Usage:
    perplexity "What is Claude Code?"
    perplexity -m sonar-reasoning "Complex analysis question"
    cat code.py | perplexity "Review this code"
    perplexity --json "query" > output.json

Environment:
    PERPLEXITY_API_KEY - API key (required)
    PERPLEXITY_VERBOSE - Show token usage when set
"""
import argparse
import json
import os
import sys
import urllib.request
import urllib.error

API_URL = "https://api.perplexity.ai/chat/completions"
MODELS = {
    "sonar": "Fast, cost-effective for quick facts",
    "sonar-pro": "Complex queries, more citations (default)",
    "sonar-reasoning": "Multi-step problem solving",
    "sonar-reasoning-pro": "Deep reasoning (DeepSeek-R1)",
    "sonar-deep-research": "Comprehensive research with agentic search",
}
DEFAULT_MODEL = "sonar-pro"


def get_api_key():
    """Get API key from environment variable."""
    return os.getenv("PERPLEXITY_API_KEY")


def query_perplexity(prompt, model=DEFAULT_MODEL, system_prompt=None, recency=None, domains=None):
    """Send query to Perplexity API and return response."""
    api_key = get_api_key()
    if not api_key:
        sys.exit(
            "Error: PERPLEXITY_API_KEY not set.\n"
            "Set via: export PERPLEXITY_API_KEY='your-key'\n"
            "Get key from: https://www.perplexity.ai/settings/api"
        )

    messages = []
    if system_prompt:
        messages.append({"role": "system", "content": system_prompt})
    messages.append({"role": "user", "content": prompt})

    payload = {
        "model": model,
        "messages": messages,
    }

    # Optional search filters
    if recency:
        payload["search_recency_filter"] = recency
    if domains:
        payload["search_domain_filter"] = domains

    data = json.dumps(payload).encode("utf-8")
    headers = {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
        "Accept": "application/json",
    }

    req = urllib.request.Request(API_URL, data=data, headers=headers)

    try:
        with urllib.request.urlopen(req, timeout=120) as resp:
            result = json.loads(resp.read().decode("utf-8"))
    except urllib.error.HTTPError as e:
        if e.code == 401:
            sys.exit("Error: Invalid API key")
        elif e.code == 429:
            sys.exit("Error: Rate limited. Wait and retry.")
        else:
            body = ""
            try:
                body = e.read().decode("utf-8")
            except Exception:
                pass
            sys.exit(f"Error: HTTP {e.code} - {e.reason}\n{body}")
    except urllib.error.URLError as e:
        sys.exit(f"Error: Network error - {e.reason}")
    except Exception as e:
        sys.exit(f"Error: {e}")

    return result


def safe_print(text):
    """Print text safely, handling encoding issues on Windows."""
    try:
        print(text)
    except UnicodeEncodeError:
        # Fallback: encode with replacement for unsupported chars
        print(text.encode(sys.stdout.encoding, errors='replace').decode(sys.stdout.encoding))


def format_output(result, show_citations=True, json_output=False):
    """Format and print the response."""
    if json_output:
        print(json.dumps(result, indent=2, ensure_ascii=False))
        return

    # Extract content
    try:
        content = result["choices"][0]["message"]["content"]
    except (KeyError, IndexError):
        print("Error: Unexpected response format")
        print(json.dumps(result, indent=2))
        return

    citations = result.get("citations", [])
    usage = result.get("usage", {})

    safe_print(content)

    # Show citations if available and requested
    if show_citations and citations:
        safe_print("\n---")
        safe_print("Sources:")
        for i, cite in enumerate(citations, 1):
            safe_print(f"  [{i}] {cite}")

    # Show usage if verbose
    if os.getenv("PERPLEXITY_VERBOSE"):
        total = usage.get("total_tokens", "N/A")
        print(f"\n[Tokens: {total}]")


def main():
    parser = argparse.ArgumentParser(
        description="Perplexity CLI - Web-grounded AI answers with citations",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Models:
  sonar                Fast, cost-effective for quick facts
  sonar-pro            Complex queries, more citations (default)
  sonar-reasoning      Multi-step problem solving
  sonar-reasoning-pro  Deep reasoning (DeepSeek-R1 based)
  sonar-deep-research  Comprehensive agentic research

Examples:
  perplexity "What's new in TypeScript 5.7?"
  perplexity -m sonar-reasoning "Analyze this security pattern"
  cat code.py | perplexity "Review this code for issues"
  perplexity --json "query" > output.json

  # Filter by recency (day, week, month, year)
  perplexity --recency day "Latest AI news"

  # Restrict to specific domains
  perplexity --domains "github.com,docs.python.org" "Python asyncio best practices"

Environment:
  PERPLEXITY_API_KEY  API key (required)
  PERPLEXITY_VERBOSE  Show token usage when set
""",
    )
    parser.add_argument("prompt", nargs="?", help="Query prompt")
    parser.add_argument(
        "-m",
        "--model",
        default=DEFAULT_MODEL,
        choices=list(MODELS.keys()),
        help=f"Model to use (default: {DEFAULT_MODEL})",
    )
    parser.add_argument("-s", "--system", default=None, help="System prompt")
    parser.add_argument(
        "--no-citations", action="store_true", help="Suppress citation output"
    )
    parser.add_argument(
        "--json", action="store_true", help="Output raw JSON response"
    )
    parser.add_argument(
        "--list-models", action="store_true", help="List available models"
    )
    parser.add_argument(
        "--recency",
        choices=["day", "week", "month", "year"],
        help="Filter search results by recency",
    )
    parser.add_argument(
        "--domains",
        type=str,
        help="Comma-separated domains to include (e.g., 'github.com,stackoverflow.com')",
    )

    args = parser.parse_args()

    # List models
    if args.list_models:
        print("Available models:")
        for name, desc in MODELS.items():
            marker = " (default)" if name == DEFAULT_MODEL else ""
            print(f"  {name}{marker}: {desc}")
        return 0

    # Get prompt from argument and/or stdin
    prompt = args.prompt
    stdin_content = ""

    # Read stdin once if available (piped input)
    if not sys.stdin.isatty():
        try:
            stdin_content = sys.stdin.read().strip()
        except Exception:
            stdin_content = ""

    # Combine stdin and prompt argument
    if stdin_content and prompt:
        # Both stdin and argument: stdin as context, prompt as instruction
        prompt = f"{stdin_content}\n\n{prompt}"
    elif stdin_content:
        # Only stdin content
        prompt = stdin_content
    elif not prompt:
        # No input at all
        parser.print_help()
        return 1

    # Parse domains if provided
    domains = None
    if args.domains:
        domains = [d.strip() for d in args.domains.split(",")]

    # Query and output
    result = query_perplexity(
        prompt,
        model=args.model,
        system_prompt=args.system,
        recency=args.recency,
        domains=domains,
    )
    format_output(result, show_citations=not args.no_citations, json_output=args.json)
    return 0


if __name__ == "__main__":
    sys.exit(main())
