"""cli.py — CLI entry point for agent-friend."""

import sys
import os
import argparse

# ANSI colors (disabled if not a TTY)
_TTY = sys.stderr.isatty()
CYAN = "\033[36m" if _TTY else ""
GREEN = "\033[32m" if _TTY else ""
YELLOW = "\033[33m" if _TTY else ""
GRAY = "\033[90m" if _TTY else ""
BOLD = "\033[1m" if _TTY else ""
RESET = "\033[0m" if _TTY else ""


def _tool_callback(name: str, args: dict, result) -> None:
    if result is None:
        args_short = str(args)[:80]
        print(f"{CYAN}→ [{name}]{RESET} {GRAY}{args_short}{RESET}", file=sys.stderr, flush=True)
    else:
        result_short = str(result)[:100].replace("\n", " ")
        print(f"{GREEN}← {result_short}{RESET}", file=sys.stderr, flush=True)


def _auto_model(api_key, requested: str) -> str:
    """Pick a sensible default model based on available API key."""
    if api_key is None:
        return requested
    if api_key.startswith("sk-ant-"):
        return "claude-haiku-4-5-20251001"
    if api_key.startswith("sk-or-"):
        return "google/gemini-2.0-flash-exp:free"
    return requested  # OpenAI or unknown


def _get_api_key():
    return (
        os.environ.get("ANTHROPIC_API_KEY")
        or os.environ.get("OPENROUTER_API_KEY")
        or os.environ.get("OPENAI_API_KEY")
    )


def _resolve_file_or_example(args) -> str:
    """Resolve the input source from --example, URL, or file argument.

    If --example is provided, writes the example data to a temp file and
    returns the path. If the file argument looks like a URL (http/https),
    fetches it and writes to a temp file. Otherwise returns the file
    argument as-is.

    Returns the file path string to pass to run_* functions.
    """
    example_name = getattr(args, "example", None)
    if example_name:
        import json
        import tempfile
        from .examples import get_example

        try:
            data = get_example(example_name)
        except ValueError as e:
            print("Error: {err}".format(err=e), file=sys.stderr)
            sys.exit(1)

        fd, path = tempfile.mkstemp(suffix=".json", prefix="agent-friend-example-")
        with os.fdopen(fd, "w") as f:
            json.dump(data, f)
        return path

    file_arg = getattr(args, "file", "-")
    if isinstance(file_arg, str) and file_arg.startswith(("http://", "https://")):
        import tempfile
        import urllib.request
        import urllib.error

        print("Fetching {url} ...".format(url=file_arg), file=sys.stderr)
        try:
            req = urllib.request.Request(
                file_arg,
                headers={"User-Agent": "agent-friend/0.1 (schema grader)"},
            )
            with urllib.request.urlopen(req, timeout=15) as resp:
                raw = resp.read()
        except urllib.error.HTTPError as e:
            print("Error: HTTP {code} fetching URL".format(code=e.code), file=sys.stderr)
            sys.exit(1)
        except urllib.error.URLError as e:
            print("Error: could not fetch URL — {reason}".format(reason=e.reason), file=sys.stderr)
            sys.exit(1)
        except Exception as e:
            print("Error: {err}".format(err=e), file=sys.stderr)
            sys.exit(1)

        fd, path = tempfile.mkstemp(suffix=".json", prefix="agent-friend-url-")
        with os.fdopen(fd, "wb") as f:
            f.write(raw)
        return path

    return file_arg


def _add_example_flag(parser: argparse.ArgumentParser) -> None:
    """Add --example flag to a subcommand parser."""
    parser.add_argument(
        "--example",
        metavar="NAME",
        default=None,
        help="Use a bundled example schema instead of a file (e.g. notion, github, filesystem). Run 'agent-friend examples' to list all.",
    )


def main() -> None:
    """Main entry point for the agent-friend CLI."""
    # Auto-detect MCP server mode: if called with no args and stdin is piped
    # (not a TTY), behave as an MCP stdio server. This lets `agent-friend`
    # work as an MCP server when MCP clients call it instead of `agent-friend-mcp`.
    if len(sys.argv) == 1 and not sys.stdin.isatty():
        from .mcp_server import main as _mcp_main
        _mcp_main()
        return

    # Route subcommands before argparse (which uses a flat positional arg)
    if len(sys.argv) > 1 and sys.argv[1] == "audit":
        _run_audit_command(sys.argv[2:])
        return

    if len(sys.argv) > 1 and sys.argv[1] == "optimize":
        _run_optimize_command(sys.argv[2:])
        return

    if len(sys.argv) > 1 and sys.argv[1] == "validate":
        _run_validate_command(sys.argv[2:])
        return

    if len(sys.argv) > 1 and sys.argv[1] == "fix":
        _run_fix_command(sys.argv[2:])
        return

    if len(sys.argv) > 1 and sys.argv[1] == "grade":
        _run_grade_command(sys.argv[2:])
        return

    if len(sys.argv) > 1 and sys.argv[1] == "examples":
        _run_examples_command(sys.argv[2:])
        return

    parser = argparse.ArgumentParser(
        prog="agent-friend",
        description=(
            "agent-friend — universal AI tool adapter. Write once, export everywhere.\n\n"
            "Quick start:\n"
            "  agent-friend --demo                   # see @tool exports (no API key)\n"
            "  agent-friend --version                # show version\n"
            "  agent-friend -i                       # interactive chat (needs API key)\n"
            "  agent-friend audit <file.json>        # token cost report for tool defs\n"
            "  agent-friend optimize <file.json>     # suggest token-saving rewrites\n"
            "  agent-friend validate <file.json>     # check schemas for correctness\n"
            "  agent-friend fix <file.json>          # auto-fix schema issues\n"
            "  agent-friend grade <file.json>        # combined quality report card\n"
            "  agent-friend grade https://...        # grade a remote schema URL\n"
            "  agent-friend grade --example notion    # grade a bundled example schema\n"
            "  agent-friend examples                 # list available example schemas"
        ),
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )

    parser.add_argument(
        "message",
        nargs="?",
        help="Send a single message and exit",
    )
    parser.add_argument(
        "--interactive", "-i",
        action="store_true",
        help="Start an interactive multi-turn chat session",
    )
    parser.add_argument(
        "--seed",
        default="You are a helpful personal AI assistant.",
        help="System prompt (default: helpful assistant)",
    )
    parser.add_argument(
        "--model",
        default=None,
        help=(
            "Model to use. Auto-detected from API key if not set.\n"
            "  Anthropic key → claude-haiku-4-5-20251001\n"
            "  OpenRouter key → google/gemini-2.0-flash-exp:free (free!)\n"
            "  OpenAI key → gpt-4o-mini"
        ),
    )
    parser.add_argument(
        "--tools",
        default="",
        help="Comma-separated tools: search,code,memory,browser,email (default: none)",
    )
    parser.add_argument(
        "--config",
        help="Path to a YAML config file",
    )
    parser.add_argument(
        "--budget",
        type=float,
        default=None,
        help="Spending limit in USD (free models cost $0)",
    )
    parser.add_argument(
        "--no-color",
        action="store_true",
        help="Disable colored output",
    )
    parser.add_argument(
        "--demo",
        action="store_true",
        help="Run a quick demo of @tool exports (no API key needed)",
    )
    parser.add_argument(
        "--version", "-V",
        action="version",
        version=f"%(prog)s {__import__('agent_friend').__version__}",
    )

    args = parser.parse_args()

    if args.no_color:
        global CYAN, GREEN, YELLOW, GRAY, BOLD, RESET
        CYAN = GREEN = YELLOW = GRAY = BOLD = RESET = ""

    if args.demo:
        _run_demo()
        return

    if not args.message and not args.interactive:
        parser.print_help()
        sys.exit(0)

    api_key = _get_api_key()
    if not api_key:
        print("No API key found. Set one of:", file=sys.stderr)
        print("  export OPENROUTER_API_KEY=sk-or-...  (free at openrouter.ai)", file=sys.stderr)
        print("  export ANTHROPIC_API_KEY=sk-ant-...", file=sys.stderr)
        print("  export OPENAI_API_KEY=sk-...", file=sys.stderr)
        sys.exit(1)

    model = args.model or _auto_model(api_key, "claude-haiku-4-5-20251001")
    tools_list = [t.strip() for t in args.tools.split(",") if t.strip()]

    if args.interactive:
        _run_interactive(args, model, api_key, tools_list)
    else:
        _run_single(args, model, api_key, tools_list)


def _run_audit_command(argv: list) -> None:
    """Handle `agent-friend audit <file.json>` subcommand."""
    audit_parser = argparse.ArgumentParser(
        prog="agent-friend audit",
        description="Analyze tool definitions and report token cost across all 5 formats.",
    )
    audit_parser.add_argument(
        "file",
        nargs="?",
        default="-",
        help='Path to a JSON file with tool definitions, or "-" for stdin (default: stdin)',
    )
    _add_example_flag(audit_parser)
    audit_parser.add_argument(
        "--no-color",
        action="store_true",
        help="Disable colored output",
    )
    audit_parser.add_argument(
        "--json",
        action="store_true",
        dest="json_output",
        help="Output as JSON (machine-readable)",
    )
    audit_parser.add_argument(
        "--threshold",
        type=int,
        default=None,
        help="Fail (exit 2) if total tokens exceed this value",
    )
    audit_args = audit_parser.parse_args(argv)

    from .audit import run_audit

    file_path = _resolve_file_or_example(audit_args)
    use_color = not audit_args.no_color
    exit_code = run_audit(
        file_path,
        use_color=use_color,
        json_output=audit_args.json_output,
        threshold=audit_args.threshold,
    )
    sys.exit(exit_code)


def _run_optimize_command(argv: list) -> None:
    """Handle `agent-friend optimize <file.json>` subcommand."""
    optimize_parser = argparse.ArgumentParser(
        prog="agent-friend optimize",
        description="Suggest token-saving rewrites for tool definitions.",
    )
    optimize_parser.add_argument(
        "file",
        nargs="?",
        default="-",
        help='Path to a JSON file with tool definitions, or "-" for stdin (default: stdin)',
    )
    _add_example_flag(optimize_parser)
    optimize_parser.add_argument(
        "--no-color",
        action="store_true",
        help="Disable colored output",
    )
    optimize_parser.add_argument(
        "--json",
        action="store_true",
        dest="json_output",
        help="Output suggestions as JSON (machine-readable)",
    )
    optimize_args = optimize_parser.parse_args(argv)

    from .optimize import run_optimize

    file_path = _resolve_file_or_example(optimize_args)
    use_color = not optimize_args.no_color
    exit_code = run_optimize(
        file_path,
        use_color=use_color,
        json_output=optimize_args.json_output,
    )
    sys.exit(exit_code)


def _run_validate_command(argv: list) -> None:
    """Handle `agent-friend validate <file.json>` subcommand."""
    validate_parser = argparse.ArgumentParser(
        prog="agent-friend validate",
        description="Check tool schemas for correctness errors.",
    )
    validate_parser.add_argument(
        "file",
        nargs="?",
        default="-",
        help='Path to a JSON file with tool definitions, or "-" for stdin (default: stdin)',
    )
    _add_example_flag(validate_parser)
    validate_parser.add_argument(
        "--no-color",
        action="store_true",
        help="Disable colored output",
    )
    validate_parser.add_argument(
        "--json",
        action="store_true",
        dest="json_output",
        help="Output as JSON (machine-readable)",
    )
    validate_parser.add_argument(
        "--strict",
        action="store_true",
        help="Treat warnings as errors",
    )
    validate_args = validate_parser.parse_args(argv)

    from .validate import run_validate

    file_path = _resolve_file_or_example(validate_args)
    use_color = not validate_args.no_color
    exit_code = run_validate(
        file_path,
        use_color=use_color,
        json_output=validate_args.json_output,
        strict=validate_args.strict,
    )
    sys.exit(exit_code)


def _run_fix_command(argv: list) -> None:
    """Handle `agent-friend fix <file.json>` subcommand."""
    fix_parser = argparse.ArgumentParser(
        prog="agent-friend fix",
        description="Auto-fix tool schema issues (ESLint --fix for MCP schemas).",
    )
    fix_parser.add_argument(
        "file",
        nargs="?",
        default="-",
        help='Path to a JSON file with tool definitions, or "-" for stdin (default: stdin)',
    )
    _add_example_flag(fix_parser)
    fix_parser.add_argument(
        "--no-color",
        action="store_true",
        help="Disable colored output",
    )
    fix_parser.add_argument(
        "--json",
        action="store_true",
        dest="json_output",
        help="Output only the fixed JSON (no summary text)",
    )
    fix_parser.add_argument(
        "--diff",
        action="store_true",
        help="Show a before/after diff of changes",
    )
    fix_parser.add_argument(
        "--dry-run",
        action="store_true",
        dest="dry_run",
        help="Show what would change without outputting fixed JSON",
    )
    fix_parser.add_argument(
        "--only",
        default=None,
        help="Comma-separated list of rules to apply (e.g., --only names,prefixes)",
    )
    fix_args = fix_parser.parse_args(argv)

    from .fix import run_fix

    file_path = _resolve_file_or_example(fix_args)
    use_color = not fix_args.no_color
    only = fix_args.only.split(",") if fix_args.only else None
    exit_code = run_fix(
        file_path,
        use_color=use_color,
        json_output=fix_args.json_output,
        diff=fix_args.diff,
        dry_run=fix_args.dry_run,
        only=only,
    )
    sys.exit(exit_code)


def _run_grade_command(argv: list) -> None:
    """Handle `agent-friend grade <file.json>` subcommand."""
    grade_parser = argparse.ArgumentParser(
        prog="agent-friend grade",
        description="Combined schema quality report card (validate + audit + optimize).",
    )
    grade_parser.add_argument(
        "file",
        nargs="?",
        default="-",
        help='Path to a JSON file, URL (https://...), or "-" for stdin (default: stdin)',
    )
    _add_example_flag(grade_parser)
    grade_parser.add_argument(
        "--no-color",
        action="store_true",
        help="Disable colored output",
    )
    grade_parser.add_argument(
        "--json",
        action="store_true",
        dest="json_output",
        help="Output as JSON (machine-readable)",
    )
    grade_parser.add_argument(
        "--threshold",
        type=int,
        default=None,
        help="Fail (exit 2) if overall score is below this value",
    )
    grade_args = grade_parser.parse_args(argv)

    from .grade import run_grade

    file_path = _resolve_file_or_example(grade_args)
    use_color = not grade_args.no_color
    exit_code = run_grade(
        file_path,
        use_color=use_color,
        json_output=grade_args.json_output,
        threshold=grade_args.threshold,
    )
    sys.exit(exit_code)


def _run_examples_command(argv: list) -> None:
    """Handle `agent-friend examples` subcommand — list available example schemas."""
    examples_parser = argparse.ArgumentParser(
        prog="agent-friend examples",
        description="List available bundled example schemas for use with --example.",
    )
    examples_parser.parse_args(argv)

    from .examples import get_example_info

    info = get_example_info()

    print("")
    print("{bold}agent-friend examples{reset} — bundled MCP server schemas".format(
        bold=BOLD, reset=RESET,
    ))
    print("")

    for name, description in info.items():
        print("  {green}{name:<14s}{reset}{gray}{desc}{reset}".format(
            green=GREEN, name=name, reset=RESET, gray=GRAY, desc=description,
        ))

    print("")
    print("  {gray}Usage: agent-friend grade --example notion{reset}".format(
        gray=GRAY, reset=RESET,
    ))
    print("  {gray}       agent-friend audit --example github{reset}".format(
        gray=GRAY, reset=RESET,
    ))
    print("")


def _run_demo() -> None:
    """Show @tool decorator in action — no API key needed."""
    import json
    from .tools.function_tool import tool

    print(f"\n{BOLD}agent-friend @tool demo{RESET}")
    print(f"{GRAY}No API key required. This shows the universal export feature.{RESET}\n")

    # Define the example function
    print(f"{YELLOW}Step 1:{RESET} Define a function with @tool\n")
    print(f"""{CYAN}from agent_friend import tool

@tool
def get_weather(city: str, units: str = "celsius") -> str:
    \"\"\"Get current weather for a city.

    Args:
        city: City name (e.g. London, Tokyo)
        units: Temperature units (celsius or fahrenheit)
    \"\"\"
    return f"Weather in {{city}}: 22°C, partly cloudy"{RESET}
""")

    @tool
    def get_weather(city: str, units: str = "celsius") -> str:
        """Get current weather for a city.

        Args:
            city: City name (e.g. London, Tokyo)
            units: Temperature units (celsius or fahrenheit)
        """
        return f"Weather in {city}: 22°C, partly cloudy"

    # Show exports
    formats = [
        ("OpenAI", "to_openai", "client.chat.completions.create(tools=...)"),
        ("Anthropic", "to_anthropic", "client.messages.create(tools=...)"),
        ("Google", "to_google", "genai.GenerativeModel(tools=...)"),
        ("MCP", "to_mcp", "Model Context Protocol servers"),
        ("JSON Schema", "to_json_schema", "Any framework"),
    ]

    print(f"{YELLOW}Step 2:{RESET} Export to any AI framework\n")

    for name, method, usage in formats:
        result = getattr(get_weather, method)()
        compact = json.dumps(result[0] if isinstance(result, list) else result, indent=2)
        lines = compact.split("\n")
        preview = "\n".join(lines[:8])
        if len(lines) > 8:
            preview += f"\n  ... ({len(lines) - 8} more lines)"
        print(f"{GREEN}get_weather.{method}(){RESET}  {GRAY}# {usage}{RESET}")
        print(f"{GRAY}{preview}{RESET}\n")

    # Call the function
    print(f"{YELLOW}Step 3:{RESET} Call it like a normal function\n")
    result = get_weather("Tokyo")
    print(f"  get_weather(\"Tokyo\") → {GREEN}{result}{RESET}\n")

    # Batch export
    print(f"{YELLOW}Bonus:{RESET} Batch export with Toolkit\n")
    print(f"""{CYAN}from agent_friend import Toolkit

tk = Toolkit(tools=[get_weather, stock_price, send_email])
tk.to_openai()     # all tools in OpenAI format
tk.to_anthropic()  # all tools in Anthropic format{RESET}
""")

    # Token estimation
    print(f"{YELLOW}Bonus:{RESET} Context budget — how many tokens do your tools cost?\n")
    from .toolkit import Toolkit as _Tk
    _demo_kit = _Tk([get_weather])
    _report = _demo_kit.token_report()
    print(f"  {CYAN}tk.token_report(){RESET}")
    for fmt, tokens in _report["estimates"].items():
        bar = "\u2588" * max(1, tokens // 5)
        print(f"  {fmt:13s} ~{tokens:>3d} tokens {GRAY}{bar}{RESET}")
    print(f"  {GRAY}most expensive: {_report['most_expensive']}, least: {_report['least_expensive']}{RESET}\n")

    print(f"{BOLD}Try it:{RESET}")
    print(f"  pip install \"git+https://github.com/0-co/agent-friend.git[all]\"")
    print(f"  {GRAY}# Then use @tool in your own code{RESET}\n")


def _build_friend(args, model: str, api_key: str, tools_list: list):
    from .friend import Friend

    if getattr(args, "config", None):
        return Friend.from_yaml(args.config)

    return Friend(
        seed=args.seed,
        model=model,
        tools=tools_list,
        budget_usd=getattr(args, "budget", None),
        on_tool_call=_tool_callback,
    )


def _run_single(args, model: str, api_key: str, tools_list: list) -> None:
    friend = _build_friend(args, model, api_key, tools_list)
    try:
        response = friend.chat(args.message)
        print(response.text)
        print(
            f"{GRAY}[tokens: {response.input_tokens}+{response.output_tokens}, cost: ${response.cost_usd:.4f}]{RESET}",
            file=sys.stderr,
        )
    except Exception as error:
        print(f"Error: {error}", file=sys.stderr)
        sys.exit(1)


def _run_interactive(args, model: str, api_key: str, tools_list: list) -> None:
    friend = _build_friend(args, model, api_key, tools_list)

    print(f"{BOLD}agent-friend{RESET}", file=sys.stderr)
    print(f"  model: {GRAY}{model}{RESET}", file=sys.stderr)
    if tools_list:
        print(f"  tools: {GRAY}{', '.join(tools_list)}{RESET}", file=sys.stderr)
    print(f"  {GRAY}Type messages. 'reset' to clear history. Ctrl-C to exit.{RESET}\n", file=sys.stderr)

    session_cost = 0.0
    turn = 0

    try:
        while True:
            try:
                user_input = input(f"{BOLD}You:{RESET} ").strip()
            except EOFError:
                break

            if not user_input:
                continue
            if user_input.lower() in ("exit", "quit", "bye", "q"):
                break
            if user_input.lower() == "reset":
                friend.reset()
                print(f"{GRAY}[Conversation reset. Memory persists.]{RESET}\n", file=sys.stderr)
                continue

            try:
                response = friend.chat(user_input)
                turn += 1
                session_cost += response.cost_usd
                print(f"\n{BOLD}Friend:{RESET} {response.text}\n")
                print(
                    f"{GRAY}[Turn {turn} | ${response.cost_usd:.4f} | total: ${session_cost:.4f}]{RESET}\n",
                    file=sys.stderr,
                )
            except Exception as error:
                print(f"Error: {error}", file=sys.stderr)

    except KeyboardInterrupt:
        pass

    print(f"\n{GRAY}Session ended: {turn} turns, ${session_cost:.4f} total{RESET}", file=sys.stderr)
