#!/usr/bin/env python3
"""Main CLI entry point for PentestGPT."""

import argparse
import asyncio
import sys

from rich.console import Console
from rich.text import Text


def parse_arguments() -> argparse.Namespace:
    """Parse command line arguments."""
    parser = argparse.ArgumentParser(
        prog="pentestgpt",
        description="PentestGPT - AI-Powered CTF Challenge Solver",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  # Interactive TUI mode (default) - HTB machine
  pentestgpt --target 10.10.11.234

  # Web challenge
  pentestgpt --target https://ctf.example.com/challenge1

  # With challenge context/hints
  pentestgpt --target 10.10.11.100 --instruction "Wordpress site, focus on plugin vulnerabilities"

  # Non-interactive mode
  pentestgpt --target challenge.htb --non-interactive

  # Custom model
  pentestgpt --target 10.10.11.50 --model claude-opus-4-20250514

For more information: https://github.com/yourusername/pentestgpt
        """,
    )

    parser.add_argument(
        "-t",
        "--target",
        type=str,
        required=True,
        help="Target CTF challenge or machine (URL, IP address, domain, or file path)",
    )

    parser.add_argument(
        "-i",
        "--instruction",
        type=str,
        help="Custom challenge context, hints, or instructions",
    )

    parser.add_argument(
        "-m",
        "--model",
        type=str,
        help="Claude model to use (default: claude-sonnet-4-5-20250929)",
    )

    parser.add_argument(
        "-n",
        "--non-interactive",
        action="store_true",
        help="Run in non-interactive mode (no TUI, exits on completion)",
    )

    parser.add_argument(
        "-v",
        "--verbose",
        action="store_true",
        help="Enable verbose output",
    )

    parser.add_argument(
        "-d",
        "--debug",
        action="store_true",
        help="Enable debug mode with detailed logging",
    )

    parser.add_argument(
        "--raw",
        action="store_true",
        help="Raw output mode for debugging (no TUI, no spinner, direct streaming)",
    )

    parser.add_argument(
        "--version",
        action="version",
        version="%(prog)s 1.0.0",
    )

    # Session management
    parser.add_argument(
        "-r",
        "--resume",
        action="store_true",
        help="Resume the most recent session for the target",
    )

    parser.add_argument(
        "--session-id",
        type=str,
        help="Resume a specific session by ID",
    )

    parser.add_argument(
        "--list-sessions",
        action="store_true",
        help="List available sessions and exit",
    )

    # Telemetry
    parser.add_argument(
        "--no-telemetry",
        action="store_true",
        help="Disable anonymous telemetry data collection",
    )

    return parser.parse_args()


def validate_environment() -> None:
    """
    Validate environment (optional checks).

    Note: API key is no longer required here as Claude Code manages
    its own configuration.
    """
    # No required validations - Claude Code handles API configuration
    pass


def print_banner() -> None:
    """Print welcome banner."""
    console = Console()

    banner = Text()
    banner.append("🚩 ", style="bold #6366f1")
    banner.append("PentestGPT", style="bold #6366f1")
    banner.append(" v1.0.0", style="dim")
    banner.append("\n")
    banner.append("AI-Powered CTF Challenge Solver", style="dim italic")

    console.print()
    console.print(banner)
    console.print()


async def run_cli_mode(args: argparse.Namespace) -> None:
    """Run in non-interactive CLI mode."""
    from pentestgpt.core.agent import run_pentest
    from pentestgpt.core.session import SessionStore

    console = Console()

    # Determine session to resume
    resume_session = args.session_id
    if args.resume and not resume_session:
        sessions = SessionStore()
        latest = sessions.get_latest(args.target)
        if latest:
            resume_session = latest.session_id
            console.print(f"[dim]Resuming session: {resume_session}[/]")

    console.print(f"[bold]Target:[/] {args.target}")
    if args.instruction:
        console.print(f"[bold]Challenge Context:[/] {args.instruction}")
    if args.debug:
        console.print("[dim]Debug mode: enabled[/]")
    console.print()

    # Allow up to 3 attempts
    max_attempts = 3
    attempt = 1
    custom_instruction = args.instruction
    total_cost = 0.0

    while attempt <= max_attempts:
        if attempt > 1:
            console.print(f"\n[bold cyan]Attempt {attempt}/{max_attempts}[/]")
            console.print("[dim]Adding persistence context...[/]\n")

        with console.status("[bold cyan]Solving CTF challenge...", spinner="dots"):
            result = await run_pentest(
                target=args.target,
                custom_instruction=custom_instruction,
                model=args.model,
                debug=args.debug,
                resume_session=resume_session if attempt == 1 else None,
            )

        total_cost += result.get("cost_usd", 0)

        console.print()
        if result["success"]:
            # Display flags found
            flags_found = result.get("flags_found", [])
            if flags_found:
                console.print(
                    f"[bold green]🚩 Challenge solved! {len(flags_found)} flag(s) found:[/]"
                )
                for flag_data in flags_found:
                    console.print(f"  • [bold cyan]{flag_data['flag']}[/]")

                console.print(f"\n{result['output']}")

                # Display session info
                session_id = result.get("session_id", "")
                if session_id:
                    console.print(f"\n[dim]Session: {session_id}[/]")

                if total_cost > 0:
                    console.print(f"[dim]Total cost: ${total_cost:.4f}[/]")

                # Success! Exit cleanly
                return

            else:
                # No flags found - this is a problem
                console.print("[bold red]⚠ CHALLENGE INCOMPLETE - NO FLAGS CAPTURED[/]")
                console.print("[yellow]The agent stopped without capturing flags.[/]")

                console.print(f"\n{result['output']}")

                if attempt < max_attempts:
                    console.print(f"\n[bold yellow]Attempts remaining: {max_attempts - attempt}[/]")
                    console.print(
                        "[dim]The agent will retry with stronger persistence instructions.[/]\n"
                    )

                    # Add feedback for next attempt
                    if custom_instruction:
                        custom_instruction += "\n\nPREVIOUS ATTEMPT FEEDBACK: You stopped without capturing flags. This is unacceptable. You MUST capture at least one flag. Try different approaches, enumerate harder, and do not stop until you have flags."
                    else:
                        custom_instruction = "IMPORTANT: The previous attempt failed to capture any flags. You MUST capture at least one flag. Do not stop until flags are found. Try all available techniques systematically."

                    attempt += 1
                    continue
                else:
                    console.print(f"\n[bold red]✗ Maximum attempts reached ({max_attempts})[/]")
                    console.print(
                        "[yellow]Consider providing more specific hints via --instruction[/]"
                    )

                    # Display session info for resumption
                    session_id = result.get("session_id", "")
                    if session_id:
                        console.print(
                            f"\n[dim]Session: {session_id} (resume with --session-id {session_id})[/]"
                        )

                    if total_cost > 0:
                        console.print(f"[dim]Total cost: ${total_cost:.4f}[/]")

                    sys.exit(1)
        else:
            console.print(
                f"[bold red]✗ Challenge failed:[/] {result.get('error', 'Unknown error')}"
            )
            sys.exit(1)


async def run_raw_mode(args: argparse.Namespace) -> None:
    """Run in raw CLI mode with streaming output for debugging."""
    from pentestgpt.core.config import load_config
    from pentestgpt.core.controller import AgentController
    from pentestgpt.core.events import Event, EventBus, EventType
    from pentestgpt.core.session import SessionStore

    # Print startup info
    print(f"[INFO] Target: {args.target}")
    if args.instruction:
        print(f"[INFO] Instruction: {args.instruction}")
    if args.model:
        print(f"[INFO] Model: {args.model}")
    if args.debug:
        print("[INFO] Debug mode: enabled")
    print("[INFO] Starting agent...", flush=True)

    # Set up event handlers that print directly to stdout
    events = EventBus.get()

    def on_message(event: Event) -> None:
        text = event.data.get("text", "")
        msg_type = event.data.get("type", "info")
        if text:
            print(f"[{msg_type.upper()}] {text}", flush=True)

    def on_tool(event: Event) -> None:
        status = event.data.get("status")
        name = event.data.get("name", "unknown")
        if status == "start":
            args_data = event.data.get("args", {})
            print(f"[TOOL] {name}: {args_data}", flush=True)
        else:
            print(f"[TOOL] {name} done", flush=True)

    def on_flag(event: Event) -> None:
        flag = event.data.get("flag", "")
        if flag:
            print(f"[FLAG] {flag}", flush=True)

    def on_state(event: Event) -> None:
        state = event.data.get("state", "")
        details = event.data.get("details", "")
        if details:
            print(f"[STATE] {state}: {details}", flush=True)
        else:
            print(f"[STATE] {state}", flush=True)

    events.subscribe(EventType.MESSAGE, on_message)
    events.subscribe(EventType.TOOL, on_tool)
    events.subscribe(EventType.FLAG_FOUND, on_flag)
    events.subscribe(EventType.STATE_CHANGED, on_state)

    # Determine session to resume
    resume_session = args.session_id
    if args.resume and not resume_session:
        sessions = SessionStore()
        latest = sessions.get_latest(args.target)
        if latest:
            resume_session = latest.session_id
            print(f"[INFO] Resuming session: {resume_session}", flush=True)

    # Build config
    config_kwargs = {"target": args.target}
    if args.instruction:
        config_kwargs["custom_instruction"] = args.instruction
    if args.model:
        config_kwargs["llm_model"] = args.model

    config = load_config(**config_kwargs)

    # Build task
    task = f"Solve this CTF challenge and capture the flag(s): {args.target}"
    if args.instruction:
        task += f"\n\nChallenge context: {args.instruction}"

    # Run agent with controller
    controller = AgentController(config)

    try:
        result = await controller.run(task, resume_session_id=resume_session)

        # Print final result
        if result.get("success"):
            flags = result.get("flags_found", [])
            cost = result.get("cost_usd", 0)
            session_id = result.get("session_id", "")
            print(
                f"[DONE] Flags: {len(flags)}, Cost: ${cost:.4f}, Session: {session_id}",
                flush=True,
            )
            if not flags:
                print("[WARN] No flags captured", flush=True)
                sys.exit(1)
        else:
            error = result.get("error", "Unknown error")
            print(f"[ERROR] {error}", flush=True)
            sys.exit(1)

    except Exception as e:
        print(f"[ERROR] {e!s}", flush=True)
        import traceback

        print(f"[TRACE] {traceback.format_exc()}", flush=True)
        sys.exit(1)


async def run_tui_mode(args: argparse.Namespace) -> None:
    """Run in interactive TUI mode."""
    from pentestgpt.core.session import SessionStore
    from pentestgpt.interface.tui import run_tui

    # Determine session to resume
    resume_session = args.session_id
    if args.resume and not resume_session:
        sessions = SessionStore()
        latest = sessions.get_latest(args.target)
        if latest:
            resume_session = latest.session_id

    await run_tui(
        target=args.target,
        custom_instruction=args.instruction,
        model=args.model,
        debug=args.debug,
        resume_session=resume_session,
    )


def list_sessions(target: str | None = None) -> None:
    """List available sessions."""
    from pentestgpt.core.session import SessionStore

    console = Console()
    sessions = SessionStore()
    session_list = sessions.list_sessions(target)

    if not session_list:
        console.print("[dim]No sessions found.[/]")
        return

    console.print(f"[bold]Sessions{f' for {target}' if target else ''}:[/]\n")
    console.print(f"{'ID':<10} {'Date':<18} {'Target':<25} {'Status':<12} {'Flags':<6}")
    console.print("-" * 75)

    for s in session_list:
        date_str = s.created_at.strftime("%Y-%m-%d %H:%M")
        target_str = s.target[:23] + ".." if len(s.target) > 25 else s.target
        flags_count = len(s.flags_found)
        console.print(
            f"{s.session_id:<10} {date_str:<18} {target_str:<25} {s.status.value:<12} {flags_count:<6}"
        )


def main() -> None:
    """Main entry point."""
    # Parse arguments
    args = parse_arguments()

    # Handle --list-sessions
    if args.list_sessions:
        list_sessions(args.target if hasattr(args, "target") else None)
        return

    # Validate environment
    validate_environment()

    # Initialize telemetry (enabled by default, use --no-telemetry to disable)
    from pentestgpt.core.langfuse import init_langfuse, shutdown_langfuse

    init_langfuse(disabled=args.no_telemetry)

    # Print banner in CLI mode
    if args.non_interactive:
        print_banner()

    # Run appropriate mode
    try:
        if args.raw:
            asyncio.run(run_raw_mode(args))
        elif args.non_interactive:
            asyncio.run(run_cli_mode(args))
        else:
            asyncio.run(run_tui_mode(args))

    except KeyboardInterrupt:
        console = Console()
        console.print("\n\n[yellow]Operation cancelled by user[/]")
        sys.exit(130)

    except Exception as e:
        console = Console()
        console.print(f"\n[bold red]Error:[/] {e!s}", style="red")
        # Always show traceback for debugging
        import traceback

        console.print("\n[dim]Traceback:[/]")
        console.print(traceback.format_exc())
        sys.exit(1)

    finally:
        shutdown_langfuse()


if __name__ == "__main__":
    main()
