"""
Main CLI application logic.
"""

import argparse
import asyncio
import sys

from src import __version__


def main():
    """Main CLI entry point."""
    parser = argparse.ArgumentParser(
        prog="penbot",
        description="🤖 PenBot - AI Chatbot Security Testing Framework",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  penbot onboard                             # First-run setup (env, keys, browsers)
  penbot doctor                              # Check environment health
  penbot wizard                              # Interactive setup for new target
  penbot test --config configs/client.yaml   # Run security test
  penbot test --config config.yaml --quick   # Quick smoke test (3 attacks)
  penbot sessions                            # List past test sessions
  penbot agents                              # List available security agents
  penbot patterns                            # Browse attack pattern libraries
  penbot report --latest                     # Generate report for latest session
  penbot dashboard                           # Start the Mission Control dashboard
  penbot init --name Bot --type rest --endpoint https://... --machine  # AI-agent config creation
  penbot mcp                                 # Start MCP server (stdio transport)
  penbot mcp --transport http --port 9100    # Start MCP server (HTTP/SSE transport)

Documentation: https://gitlab.com/yan-ban/penbot
        """,
    )

    parser.add_argument("--version", action="version", version=f"PenBot v{__version__}")

    # --machine is accepted both before and after the subcommand
    # (e.g. `penbot --machine doctor` OR `penbot doctor --machine`).
    _shared = argparse.ArgumentParser(add_help=False)
    _shared.add_argument(
        "--machine",
        action="store_true",
        help="Machine-readable JSON output (for AI agents and automation)",
    )

    subparsers = parser.add_subparsers(dest="command", help="Command to run")

    subparsers.add_parser(
        "onboard", parents=[_shared], help="First-run setup wizard (env, keys, browsers)"
    )
    subparsers.add_parser(
        "doctor", parents=[_shared], help="Check environment health and diagnose issues"
    )
    subparsers.add_parser(
        "wizard", parents=[_shared], help="Interactive setup for new target configuration"
    )

    test_parser = subparsers.add_parser(
        "test", parents=[_shared], help="Run security test against target"
    )
    test_parser.add_argument("--config", required=True, help="Path to client config file (YAML)")
    test_parser.add_argument(
        "--quick", action="store_true", help="Run a quick smoke test (3 attacks)"
    )
    test_parser.add_argument("--max-attacks", type=int, help="Override maximum attacks limit")
    test_parser.add_argument(
        "--agents",
        type=str,
        help="Comma-separated list of agents to use (e.g., jailbreak,encoding,rag_poisoning)",
    )
    test_parser.add_argument(
        "--phase",
        type=str,
        choices=[
            "reconnaissance",
            "trust_building",
            "boundary_testing",
            "exploitation",
            "escalation",
        ],
        help="Start at specific campaign phase",
    )
    test_parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Preview attacks without executing (show what would be sent)",
    )
    test_parser.add_argument(
        "--output", type=str, help="Output directory for reports (default: reports/)"
    )
    test_parser.add_argument("--non-interactive", action="store_true", help="Run without prompts")
    test_parser.add_argument("--verbose", "-v", action="store_true", help="Show verbose output")

    validate_parser = subparsers.add_parser(
        "validate", parents=[_shared], help="Validate target configuration and test connectivity"
    )
    validate_parser.add_argument("--config", required=True, help="Path to client config file")

    subparsers.add_parser("platforms", parents=[_shared], help="List supported chatbot platforms")

    report_parser = subparsers.add_parser(
        "report", parents=[_shared], help="Generate security assessment reports"
    )
    report_parser.add_argument("--session", help="Session ID to report on")
    report_parser.add_argument("--latest", action="store_true", help="Report on latest session")
    report_parser.add_argument(
        "--format",
        choices=["pdf", "html", "json"],
        default="html",
        help="Output format (default: html)",
    )
    report_parser.add_argument("--output", type=str, help="Output path for the report")

    sessions_parser = subparsers.add_parser(
        "sessions", parents=[_shared], help="Manage past test sessions"
    )
    sessions_subparsers = sessions_parser.add_subparsers(dest="sessions_action")

    # sessions (no action) - list sessions
    sessions_parser.set_defaults(sessions_action="list")

    # sessions view <id>
    sessions_view = sessions_subparsers.add_parser("view", help="View session details")
    sessions_view.add_argument(
        "session_id", help="Session ID or index number (e.g., 1 for most recent)"
    )

    # sessions delete <id>
    sessions_delete = sessions_subparsers.add_parser("delete", help="Delete a session")
    sessions_delete.add_argument("session_id", help="Session ID or index number")
    sessions_delete.add_argument(
        "-f", "--force", action="store_true", help="Skip confirmation prompt"
    )

    # sessions export <id>
    sessions_export = sessions_subparsers.add_parser(
        "export", help="Export session to CSV or Markdown"
    )
    sessions_export.add_argument("session_id", help="Session ID or index number")
    sessions_export.add_argument(
        "--format", choices=["csv", "markdown"], default="csv", help="Export format"
    )
    sessions_export.add_argument("--output", type=str, help="Output directory")

    agents_parser = subparsers.add_parser(
        "agents", parents=[_shared], help="List and describe available security agents"
    )
    agents_subparsers = agents_parser.add_subparsers(dest="agents_action")

    # agents (no action) - list agents
    agents_parser.set_defaults(agents_action="list")

    # agents describe <id>
    agents_describe = agents_subparsers.add_parser(
        "describe", help="Show detailed agent information"
    )
    agents_describe.add_argument("agent_id", help="Agent ID (e.g., jailbreak, encoding)")

    patterns_parser = subparsers.add_parser(
        "patterns", parents=[_shared], help="Browse and search attack pattern libraries"
    )
    patterns_subparsers = patterns_parser.add_subparsers(dest="patterns_action")

    # patterns (no action) - list libraries
    patterns_parser.set_defaults(patterns_action="list")

    # patterns search <query>
    patterns_search = patterns_subparsers.add_parser("search", help="Search patterns by keyword")
    patterns_search.add_argument("query", help="Search keyword")
    patterns_search.add_argument("--limit", type=int, default=20, help="Maximum results to show")

    # patterns show <library>
    patterns_show = patterns_subparsers.add_parser("show", help="Show patterns in a library")
    patterns_show.add_argument("library", help="Library name (partial match supported)")

    # patterns view <pattern_name>
    patterns_view = patterns_subparsers.add_parser("view", help="View pattern details")
    patterns_view.add_argument("pattern_name", help="Pattern name")

    # penbot init (non-interactive config creation)
    init_parser = subparsers.add_parser(
        "init",
        parents=[_shared],
        help="Non-interactive target config creation (for AI agents / CI)",
    )
    init_parser.add_argument("--name", required=True, help="Target display name")
    init_parser.add_argument(
        "--type",
        required=True,
        choices=["rest", "playwright"],
        help="Connector type",
    )
    init_parser.add_argument("--endpoint", required=True, help="API URL or web URL")
    init_parser.add_argument("--agents", type=str, help="Comma-separated agent IDs")
    init_parser.add_argument("--max-attacks", type=int, help="Max attack count (default 30)")
    init_parser.add_argument("--output", type=str, help="Config output path (default: auto)")

    # penbot watch (continuous testing)
    watch_parser = subparsers.add_parser(
        "watch",
        parents=[_shared],
        help="Run PenBot on a schedule and detect regressions",
    )
    watch_parser.add_argument("--config", required=True, help="Path to target config")
    watch_parser.add_argument(
        "--interval", default="6h", help="Run interval (e.g., 30m, 6h, 1d). Default: 6h"
    )
    watch_parser.add_argument("--quick", action="store_true", help="Quick test per run (3 attacks)")
    watch_parser.add_argument(
        "--max-runs", type=int, default=0, help="Stop after N runs (0 = infinite)"
    )

    # penbot diff (compare sessions)
    diff_parser = subparsers.add_parser(
        "diff",
        parents=[_shared],
        help="Compare two test sessions to detect regressions",
    )
    diff_parser.add_argument("session_a", help="First session ID (or index)")
    diff_parser.add_argument("session_b", help="Second session ID (or index)")

    # penbot benchmark
    bench_parser = subparsers.add_parser(
        "benchmark",
        parents=[_shared],
        help="Run PenBot against mock chatbots and produce a detection scorecard",
    )
    bench_parser.add_argument(
        "--mock", type=str, help="Run a single mock (e.g., dan_susceptible, hardened)"
    )
    bench_parser.add_argument(
        "--attacks", type=int, default=5, help="Max attacks per mock (default: 5)"
    )

    # penbot mcp (MCP server)
    mcp_parser = subparsers.add_parser(
        "mcp", parents=[_shared], help="Start PenBot as an MCP server (for AI agent integration)"
    )
    mcp_parser.add_argument(
        "--transport",
        choices=["stdio", "http"],
        default="stdio",
        help="Transport mode (default: stdio)",
    )
    mcp_parser.add_argument("--host", default="127.0.0.1", help="HTTP host (default: 127.0.0.1)")
    mcp_parser.add_argument("--port", type=int, default=9100, help="HTTP port (default: 9100)")

    # penbot purple (purple-team defense simulation)
    purple_parser = subparsers.add_parser(
        "purple",
        parents=[_shared],
        help="Purple-team mode: propose and verify defenses for found vulnerabilities",
    )
    purple_parser.add_argument("--session", type=str, help="Session ID to simulate defenses for")
    purple_parser.add_argument("--latest", action="store_true", help="Use the most recent session")
    purple_parser.add_argument(
        "--output", type=str, help="Path to save the purple-team report JSON"
    )

    # penbot baseline (regression baseline management)
    baseline_parser = subparsers.add_parser(
        "baseline",
        parents=[_shared],
        help="Create a security baseline from a completed session",
    )
    baseline_subparsers = baseline_parser.add_subparsers(dest="baseline_action")
    baseline_parser.set_defaults(baseline_action="create")

    bl_create = baseline_subparsers.add_parser("create", help="Create baseline from session")
    bl_create.add_argument("--session", type=str, help="Session ID (default: latest)")
    bl_create.add_argument("--latest", action="store_true", help="Use most recent session")
    bl_create.add_argument("--name", type=str, help="Baseline name (default: auto)")

    baseline_subparsers.add_parser("list", help="List existing baselines")

    # penbot regression (regression testing)
    regression_parser = subparsers.add_parser(
        "regression",
        parents=[_shared],
        help="Re-run attacks and compare against a baseline to detect regressions",
    )
    regression_parser.add_argument("--config", required=True, help="Path to target config")
    regression_parser.add_argument("--baseline", type=str, help="Baseline name (default: latest)")
    regression_parser.add_argument(
        "--fail-on-regression",
        action="store_true",
        help="Exit with code 1 if regressions detected (for CI/CD)",
    )

    dashboard_parser = subparsers.add_parser(
        "dashboard", parents=[_shared], help="Start the Mission Control dashboard server"
    )
    dashboard_parser.add_argument(
        "--port", type=int, default=8000, help="Server port (default: 8000)"
    )
    dashboard_parser.add_argument(
        "--host", type=str, default="0.0.0.0", help="Server host (default: 0.0.0.0)"
    )
    dashboard_parser.add_argument(
        "--no-browser", action="store_true", help="Don't open browser automatically"
    )
    dashboard_parser.add_argument(
        "--status", action="store_true", help="Check if dashboard is running"
    )

    # parse_known_args lets `--machine` appear before the subcommand
    # (e.g. `penbot --machine doctor`) without argparse rejecting it.
    args, remaining = parser.parse_known_args()

    # Re-parse remaining through the subparser so `--machine` before
    # the subcommand is recognised (argparse won't pass it through).
    if remaining and remaining == ["--machine"]:
        args.machine = True
    elif remaining:
        parser.error(f"unrecognized arguments: {' '.join(remaining)}")

    if not args.command:
        parser.print_help()
        sys.exit(1)

    try:
        # Platforms (synchronous, no imports needed)
        if args.command == "platforms":
            show_platforms()

        # Onboard
        elif args.command == "onboard":
            from src.cli.onboard import run_onboard

            run_onboard(args)

        # Doctor
        elif args.command == "doctor":
            from src.cli.doctor import run_doctor

            run_doctor(args)

        # Wizard
        elif args.command == "wizard":
            from src.cli.wizard import run_wizard

            asyncio.run(run_wizard(args))

        # Test
        elif args.command == "test":
            from src.cli.test_runner import run_test

            asyncio.run(run_test(args))

        # Validate
        elif args.command == "validate":
            from src.cli.validator import validate_config

            asyncio.run(validate_config(args))

        # Report
        elif args.command == "report":
            from src.cli.reporter import run_report

            asyncio.run(run_report(args))

        # Sessions
        elif args.command == "sessions":
            from src.cli.sessions import delete_session, export_session, list_sessions, view_session

            if args.sessions_action == "list" or args.sessions_action is None:
                list_sessions(args)
            elif args.sessions_action == "view":
                view_session(args)
            elif args.sessions_action == "delete":
                delete_session(args)
            elif args.sessions_action == "export":
                export_session(args)

        # Agents
        elif args.command == "agents":
            from src.cli.agents_cmd import describe_agent, list_agents

            if args.agents_action == "list" or args.agents_action is None:
                list_agents(args)
            elif args.agents_action == "describe":
                describe_agent(args)

        # Patterns
        elif args.command == "patterns":
            from src.cli.patterns import list_patterns, search_patterns, show_library, view_pattern

            if args.patterns_action == "list" or args.patterns_action is None:
                list_patterns(args)
            elif args.patterns_action == "search":
                search_patterns(args)
            elif args.patterns_action == "show":
                show_library(args)
            elif args.patterns_action == "view":
                view_pattern(args)

        # Init (non-interactive)
        elif args.command == "init":
            from src.cli.init_cmd import run_init

            run_init(args)

        # MCP server
        elif args.command == "mcp":
            from src.mcp_server import run_mcp_server

            run_mcp_server(
                transport=args.transport,
                host=args.host,
                port=args.port,
            )

        # Watch
        elif args.command == "watch":
            from src.cli.watch_cmd import run_watch

            run_watch(args)

        # Diff
        elif args.command == "diff":
            from src.cli.watch_cmd import run_diff

            run_diff(args)

        # Benchmark
        elif args.command == "benchmark":
            from src.cli.benchmark_cmd import run_benchmark

            run_benchmark(args)

        # Purple-team
        elif args.command == "purple":
            from src.cli.purple_cmd import run_purple

            run_purple(args)

        # Baseline
        elif args.command == "baseline":
            from src.cli.regression_cmd import run_baseline

            run_baseline(args)

        # Regression
        elif args.command == "regression":
            from src.cli.regression_cmd import run_regression

            run_regression(args)

        # Dashboard
        elif args.command == "dashboard":
            from src.cli.dashboard_cmd import check_server_status, start_dashboard

            if hasattr(args, "status") and args.status:
                check_server_status(args)
            else:
                start_dashboard(args)

    except KeyboardInterrupt:
        if args.machine:
            from src.cli.machine_output import emit_error

            emit_error("interrupted", "Operation cancelled by user")
        else:
            print("\n⚠️  Operation cancelled by user")
        sys.exit(130)
    except Exception as e:
        if args.machine:
            from src.cli.machine_output import emit_error

            emit_error("error", str(e))
        else:
            print(f"\n❌ Error: {str(e)}")
            import traceback

            if "--verbose" in sys.argv or "-v" in sys.argv:
                traceback.print_exc()
        sys.exit(1)


def show_platforms():
    """List supported platforms and connection types."""
    print("\n🤖 Supported Platforms & Connection Types:\n")
    print("  ── REST-based (via GenericRestConnector) ──────────────────")
    rest_platforms = [
        ("rest", "Generic REST API", "Any HTTP/JSON chatbot endpoint"),
        ("dialogflow", "Google Dialogflow CX/ES", "detectIntent REST API + bearer auth"),
        ("rasa", "Rasa Open Source", "Rasa webhook REST endpoint"),
    ]
    for pid, name, desc in rest_platforms:
        print(f"  • {pid:<15} {name:<28} {desc}")

    print("\n  ── Protocol-specific connectors ────────────────────────")
    proto_platforms = [
        ("azure-bot", "Azure Bot Service", "Direct Line 3.0 (start → send → poll)"),
        ("aws-lex", "Amazon Lex V2", "boto3 recognize_text (optional dep)"),
    ]
    for pid, name, desc in proto_platforms:
        print(f"  • {pid:<15} {name:<28} {desc}")

    print("\n  ── Browser automation ──────────────────────────────────")
    print(f"  • {'playwright':<15} {'Playwright':<28} Any web-based chatbot UI")

    print(
        "\n  Config templates are available in configs/templates/ for each platform."
        "\n  Copy a template and fill in your credentials to get started."
        "\n"
        "\nUse 'penbot wizard' to configure a new target.\n"
    )


if __name__ == "__main__":
    main()
