"""CLI for deliberate."""

import asyncio
import json
from pathlib import Path
from typing import Optional

import typer
from rich.console import Console
from rich.panel import Panel
from rich.syntax import Syntax
from rich.table import Table

from deliberate.config import DeliberateConfig
from deliberate.orchestrator import Orchestrator
from deliberate.types import JuryResult

app = typer.Typer(
    name="jury",
    help="Multi-LLM ensemble orchestrator for code generation and review",
)
console = Console()


def _load_task_content(task: str) -> str:
    """Load task content from file if prefixed with @, otherwise return as-is.

    Args:
        task: Task string or @filename to load from file

    Returns:
        Task content as string

    Raises:
        typer.Exit: If file doesn't exist or can't be read
    """
    if task.startswith("@"):
        # Load from file
        task_file = Path(task[1:])
        if not task_file.exists():
            console.print(f"[red]Error:[/red] Task file not found: {task_file}")
            raise typer.Exit(1)
        try:
            return task_file.read_text().strip()
        except Exception as e:
            console.print(f"[red]Error:[/red] Failed to read task file: {e}")
            raise typer.Exit(1)
    return task


@app.command()
def run(
    task: str = typer.Argument(..., help="The task to execute (use @filename to load from file)"),
    config: Optional[Path] = typer.Option(
        None,
        "--config",
        "-c",
        help="Path to configuration file (e.g., ./deliberate.yaml)",
    ),
    agents: Optional[str] = typer.Option(
        None,
        "--agents",
        "-a",
        help="Comma-separated list of agents to use (defined in config file, e.g., 'claude,fake')",
    ),
    skip_planning: bool = typer.Option(
        False,
        "--skip-planning",
        help="Skip the planning phase",
    ),
    skip_review: bool = typer.Option(
        False,
        "--skip-review",
        help="Skip the review phase",
    ),
    dry_run: bool = typer.Option(
        False,
        "--dry-run",
        help="Run without making actual changes",
    ),
    json_output: bool = typer.Option(
        False,
        "--json",
        help="Output results as JSON",
    ),
    verbose: bool = typer.Option(
        False,
        "--verbose",
        "-v",
        help="Show verbose output",
    ),
    enable_tracing: bool = typer.Option(
        False,
        "--trace",
        help="Enable OpenTelemetry tracing",
    ),
    otlp_endpoint: Optional[str] = typer.Option(
        None,
        "--otlp-endpoint",
        help="OTLP collector endpoint (e.g., http://localhost:4317)",
    ),
    trace_console: bool = typer.Option(
        False,
        "--trace-console",
        help="Export traces to console (for debugging)",
    ),
):
    """Run a task through the LLM jury."""
    # Load task content from file if needed
    task_content = _load_task_content(task)

    # Initialize tracing if requested
    if enable_tracing or otlp_endpoint or trace_console:
        try:
            from deliberate.tracing import init_tracing, shutdown_tracing

            init_tracing(
                service_name="jury",
                otlp_endpoint=otlp_endpoint,
                console_export=trace_console,
            )
        except ImportError:
            console.print(
                "[yellow]Warning:[/yellow] Tracing requested but opentelemetry not installed. "
                "Install with: pip install deliberate[tracing]"
            )

    try:
        cfg = DeliberateConfig.load_or_default(config)
    except FileNotFoundError as e:
        console.print(f"[red]Error:[/red] {e}")
        raise typer.Exit(1)

    # Override config with CLI options
    if agents:
        agent_list = [a.strip() for a in agents.split(",")]
        cfg.workflow.planning.agents = agent_list
        cfg.workflow.execution.agents = agent_list
        cfg.workflow.review.agents = agent_list

    if skip_planning:
        cfg.workflow.planning.enabled = False

    if skip_review:
        cfg.workflow.review.enabled = False

    if dry_run:
        cfg.limits.safety.dry_run = True

    # Run the orchestrator
    try:
        orchestrator = Orchestrator(cfg, Path.cwd())
        result = asyncio.run(orchestrator.run(task_content))
    finally:
        # Shutdown tracing to flush spans
        if enable_tracing or otlp_endpoint or trace_console:
            try:
                from deliberate.tracing import shutdown_tracing

                shutdown_tracing()
            except ImportError:
                pass

    # Output results
    if json_output:
        _output_json(result)
    else:
        _output_rich(result, verbose)

    # Exit with appropriate code
    if not result.success:
        raise typer.Exit(1)


@app.command()
def init(
    force: bool = typer.Option(
        False,
        "--force",
        "-f",
        help="Overwrite existing config file",
    ),
    user_config: bool = typer.Option(
        False,
        "--user",
        "-u",
        help="Create config in user config directory instead of current directory",
    ),
):
    """Create a new configuration file.

    By default creates .deliberate.yaml in the current directory.
    Use --user to create config.yaml in the OS-specific user config directory.
    """
    if user_config:
        config_dir = DeliberateConfig.get_user_config_dir()
        config_dir.mkdir(parents=True, exist_ok=True)
        config_path = config_dir / "config.yaml"
    else:
        config_path = Path(".deliberate.yaml")

    if config_path.exists() and not force:
        if not typer.confirm(f"{config_path} already exists. Overwrite?"):
            raise typer.Abort()

    config_content = """# deliberate configuration
# See documentation for all options

agents:
  # Claude agent (requires claude CLI)
  claude:
    type: cli
    command: ["claude", "--print", "-p"]
    capabilities: [planner, executor, reviewer]
    config:
      max_tokens: 16000
      timeout_seconds: 1200
    cost:
      weight: 1.0

  # Fake agent for testing (no API calls)
  fake:
    type: fake
    behavior: planner  # echo, planner, critic, flaky
    capabilities: [planner, executor, reviewer]

workflow:
  planning:
    enabled: true
    agents: [fake]  # Use fake for testing
    debate:
      enabled: false
    selection:
      method: first

  execution:
    enabled: true
    agents: [fake]
    worktree:
      enabled: true
      root: .deliberate-worktrees
      cleanup: true

  review:
    enabled: false  # Disabled for simple setup
    agents: []
    scoring:
      criteria: [correctness, code_quality, completeness, risk]
      scale: "1-10"
    aggregation:
      method: borda

limits:
  budget:
    max_total_tokens: 500000
    max_cost_usd: 10.0
    max_requests_per_agent: 30
  time:
    hard_timeout_minutes: 45

ci:
  enabled: false
  mode: suggest_only
"""

    config_path.write_text(config_content)
    console.print(f"[green]Created {config_path}[/green]")
    console.print("\nNext steps:")
    console.print("1. Edit the config to add your LLM agents")
    console.print("2. Run: jury run 'your task here'")


@app.command()
def validate(
    config: Optional[Path] = typer.Option(
        None,
        "--config",
        "-c",
        help="Path to configuration file",
    ),
):
    """Validate a configuration file."""
    try:
        cfg = DeliberateConfig.load_or_default(config)
        console.print("[green]Configuration valid[/green]")

        # Show summary
        table = Table(title="Configuration Summary")
        table.add_column("Setting", style="cyan")
        table.add_column("Value", style="green")

        table.add_row("Agents", ", ".join(cfg.agents.keys()))
        table.add_row("Planning agents", ", ".join(cfg.workflow.planning.agents))
        table.add_row("Execution agents", ", ".join(cfg.workflow.execution.agents))
        table.add_row("Review agents", ", ".join(cfg.workflow.review.agents))
        table.add_row("Max tokens", f"{cfg.limits.budget.max_total_tokens:,}")
        table.add_row("Max cost", f"${cfg.limits.budget.max_cost_usd:.2f}")

        console.print(table)

    except FileNotFoundError as e:
        console.print("[red]Error:[/red] Configuration file not found")
        console.print(f"  {e}")
        raise typer.Exit(1)

    except Exception as e:
        console.print("[red]Error:[/red] Invalid configuration")
        console.print(f"  {e}")
        raise typer.Exit(1)


def _output_json(result: JuryResult):
    """Output result as JSON."""
    output = {
        "task": result.task,
        "success": result.success,
        "error": result.error,
        "summary": result.summary,
        "final_diff": result.final_diff,
        "total_duration_seconds": result.total_duration_seconds,
        "total_token_usage": result.total_token_usage,
        "total_cost_usd": result.total_cost_usd,
        "selected_plan": {
            "id": result.selected_plan.id,
            "agent": result.selected_plan.agent,
            "content": result.selected_plan.content,
        }
        if result.selected_plan
        else None,
        "execution_results": [
            {
                "id": e.id,
                "agent": e.agent,
                "success": e.success,
                "error": e.error,
                "has_diff": bool(e.diff),
            }
            for e in result.execution_results
        ],
        "vote_result": {
            "winner_id": result.vote_result.winner_id,
            "rankings": result.vote_result.rankings,
            "scores": result.vote_result.scores,
            "confidence": result.vote_result.confidence,
        }
        if result.vote_result
        else None,
    }
    print(json.dumps(output, indent=2, default=str))


def _output_rich(result: JuryResult, verbose: bool):
    """Output result with rich formatting."""
    # Status panel
    status = "[green]SUCCESS[/green]" if result.success else "[red]FAILED[/red]"
    console.print(Panel(f"Jury Result: {status}", expand=False))

    if result.error:
        console.print(f"\n[red]Error:[/red] {result.error}")

    # Summary
    if result.summary:
        console.print("\n[bold]Summary:[/bold]")
        console.print(result.summary)

    # Plan info
    if result.selected_plan and verbose:
        console.print(f"\n[bold]Selected Plan[/bold] (by {result.selected_plan.agent}):")
        console.print(result.selected_plan.content[:1000])

    # Execution results
    if result.execution_results:
        console.print("\n[bold]Execution Results:[/bold]")
        for er in result.execution_results:
            status_icon = "[green]+" if er.success else "[red]x"
            console.print(f"  {status_icon} {er.agent}: {er.id}[/]")
            if er.error:
                console.print(f"    Error: {er.error}")

    # Vote results
    if result.vote_result and verbose:
        console.print("\n[bold]Vote Results:[/bold]")
        console.print(f"  Winner: {result.vote_result.winner_id}")
        console.print(f"  Confidence: {result.vote_result.confidence:.2f}")

        console.print("\n  Scores:")
        for cid, score in sorted(
            result.vote_result.scores.items(), key=lambda x: -x[1]
        ):
            bar = "+" * int(score * 20)
            console.print(f"    {cid}: {bar} {score:.2f}")

    # Final diff
    if result.final_diff:
        console.print("\n[bold]Changes:[/bold]")
        syntax = Syntax(
            result.final_diff[:3000],
            "diff",
            theme="monokai",
            line_numbers=True,
        )
        console.print(syntax)

        if len(result.final_diff) > 3000:
            console.print(f"\n[dim](diff truncated, {len(result.final_diff)} chars total)[/dim]")

    # Stats
    console.print(
        f"\n[dim]Duration: {result.total_duration_seconds:.1f}s | "
        f"Tokens: {result.total_token_usage:,} | "
        f"Cost: ${result.total_cost_usd:.4f}[/dim]"
    )


if __name__ == "__main__":
    app()
