"""CLI commands for sub-agent spawn functionality."""

from __future__ import annotations

import asyncio
from pathlib import Path

import click

from oclawma.cli_ui import (
    accent,
    bullet,
    error,
    format_duration,
    header,
    highlight,
    key_value,
    print_error,
    print_info,
    print_success,
    print_warning,
    progress_spinner,
    subheader,
    success,
)
from oclawma.config import DEFAULT_CONFIG_PATH, Config
from oclawma.config.utils import create_provider_with_fallback, get_model_from_config
from oclawma.subagent import (
    SubAgentConfig,
    SubAgentManager,
    SubAgentResult,
)


@click.group(name="spawn")
def spawn_cli() -> None:
    """Spawn minimal-context sub-agents for parallel tasks."""
    pass


@spawn_cli.command(name="exec")
@click.argument("tasks", nargs=-1, required=True)
@click.option(
    "--config-path",
    type=click.Path(exists=False),
    help="Path to config file (default: ~/.oclawma/config.yaml)",
)
@click.option(
    "--profile",
    help="Profile to use from config",
    shell_complete=lambda ctx, param, incomplete: (
        ["local", "cloud", "fallback"] if incomplete else []
    ),
)
@click.option(
    "--max-concurrent",
    type=int,
    default=4,
    help="Maximum number of concurrent sub-agents (default: 4)",
    show_default=True,
)
@click.option(
    "--timeout",
    type=int,
    default=300,
    help="Timeout per sub-agent in seconds (default: 300)",
    show_default=True,
)
@click.option(
    "--strategy",
    type=click.Choice(["concatenate", "summarize", "vote", "merge"], case_sensitive=False),
    default="concatenate",
    help="Result aggregation strategy",
    show_default=True,
)
@click.option(
    "--temperature",
    type=float,
    default=0.7,
    help="Temperature for sub-agents",
    show_default=True,
)
@click.option(
    "--no-tools",
    is_flag=True,
    help="Disable tools for sub-agents",
)
@click.option(
    "--context-budget",
    type=int,
    default=4000,
    help="Token budget per sub-agent",
    show_default=True,
)
@click.option(
    "--output",
    "-o",
    type=click.Path(),
    help="Save results to file",
)
@click.option(
    "--json",
    "output_json",
    is_flag=True,
    help="Output results as JSON",
)
def spawn_exec(
    tasks: tuple[str, ...],
    config_path: str | None,
    profile: str | None,
    max_concurrent: int,
    timeout: int,
    strategy: str,
    temperature: float,
    no_tools: bool,
    context_budget: int,
    output: str | None,
    output_json: bool,
) -> None:
    """Execute tasks in parallel using sub-agents.

    Each task runs in its own sub-agent with minimal context.
    Results are aggregated based on the chosen strategy.

    Examples:
        oclawma spawn exec "List files in /tmp" "Check disk space"
        oclawma spawn exec task1.txt task2.txt task3.txt --max-concurrent 3
        oclawma spawn exec "Analyze log1" "Analyze log2" --strategy summarize
    """
    import time

    start_time = time.time()

    # Load configuration
    cfg_path = Path(config_path) if config_path else DEFAULT_CONFIG_PATH

    with progress_spinner("Loading configuration..."):
        try:
            config = Config.ensure_exists(cfg_path)
            if profile:
                config.set_active_profile(profile)
        except Exception as e:
            print_warning(f"Could not load config: {e}")
            config = Config()

    # Load tasks from files if they look like file paths
    task_list = list(tasks)
    loaded_tasks = []

    with progress_spinner("Loading tasks..."):
        for task in task_list:
            task_path = Path(task)
            if task_path.exists() and task_path.is_file():
                try:
                    loaded_tasks.append(task_path.read_text().strip())
                except Exception:
                    loaded_tasks.append(task)
            else:
                loaded_tasks.append(task)

    # Create provider
    try:
        with progress_spinner("Initializing provider..."):
            provider = create_provider_with_fallback(config)
            model = get_model_from_config(config)
    except Exception as e:
        raise click.UsageError(f"Failed to create provider: {e}") from e

    # Create sub-agent config
    agent_config = SubAgentConfig(
        model=model,
        temperature=temperature,
        timeout_seconds=float(timeout),
        tools_enabled=not no_tools,
        context_budget=context_budget,
    )

    # Run the parallel execution
    click.echo()
    click.echo(header("SPAWNING SUB-AGENTS", width=58))
    click.echo()
    click.echo(key_value("Tasks", str(len(loaded_tasks))))
    click.echo(key_value("Max concurrent", str(max_concurrent)))
    click.echo(key_value("Strategy", strategy))
    click.echo(key_value("Model", model))
    click.echo()

    async def run_with_progress() -> tuple[list[SubAgentResult], str]:
        manager = SubAgentManager(provider, default_max_concurrent=max_concurrent)

        # Custom progress bar with color
        with click.progressbar(
            length=len(loaded_tasks),
            label=click.style("Executing tasks", fg="cyan"),
            show_eta=True,
            bar_template=f"{click.style('%(label)s', fg='cyan')} [{click.style('%(bar)s', fg='magenta')}] {click.style('%(info)s', fg='bright_black')}",
            fill_char=click.style("█", fg="green"),
            empty_char=click.style("░", fg="bright_black"),
        ) as bar:

            def progress_callback(completed: int, total: int) -> None:
                bar.update(completed - bar.pos)

            results, aggregated = await manager.parallel_map(
                tasks=loaded_tasks,
                config=agent_config,
                aggregation_strategy=strategy,
                progress_callback=progress_callback,
            )

        return results, aggregated

    try:
        results, aggregated = asyncio.run(run_with_progress())
    except KeyboardInterrupt:
        print_warning("\n\nInterrupted by user")
        return
    except Exception as e:
        print_error(f"Execution failed: {e}")
        raise click.ClickException(str(e)) from e

    elapsed = time.time() - start_time

    # Display results
    _display_results(results, aggregated, output_json, elapsed)

    # Save to file if requested
    if output:
        _save_results(results, aggregated, output, output_json)
        print_success(f"Results saved to {output}")


@spawn_cli.command(name="vote")
@click.argument("task")
@click.option(
    "--config-path",
    type=click.Path(exists=False),
    help="Path to config file",
)
@click.option(
    "--profile",
    help="Profile to use from config",
)
@click.option(
    "--num-agents",
    type=int,
    default=3,
    help="Number of agents to run for voting (default: 3)",
    show_default=True,
)
@click.option(
    "--max-concurrent",
    type=int,
    default=3,
    help="Maximum concurrent agents",
    show_default=True,
)
@click.option(
    "--temperature",
    type=float,
    default=0.7,
    help="Temperature for sub-agents",
)
def spawn_vote(
    task: str,
    config_path: str | None,
    profile: str | None,
    num_agents: int,
    max_concurrent: int,
    temperature: float,
) -> None:
    """Run the same task with multiple agents and vote on results.

    This is useful for increasing confidence through redundancy.
    The winning result is determined by majority vote.

    Examples:
        oclawma spawn vote "What is 2+2?" --num-agents 5
        oclawma spawn vote "Summarize: The quick brown fox..."
    """
    import time

    start_time = time.time()

    # Load configuration
    cfg_path = Path(config_path) if config_path else DEFAULT_CONFIG_PATH

    with progress_spinner("Loading configuration..."):
        try:
            config = Config.ensure_exists(cfg_path)
            if profile:
                config.set_active_profile(profile)
        except Exception as e:
            print_warning(f"Could not load config: {e}")
            config = Config()

    # Create provider
    try:
        with progress_spinner("Initializing provider..."):
            provider = create_provider_with_fallback(config)
            model = get_model_from_config(config)
    except Exception as e:
        raise click.UsageError(f"Failed to create provider: {e}") from e

    # Create sub-agent config
    agent_config = SubAgentConfig(
        model=model,
        temperature=temperature,
        tools_enabled=True,
    )

    # Run the voting
    click.echo()
    click.echo(header("VOTING SESSION", width=58))
    click.echo()
    click.echo(key_value("Agents", str(num_agents)))
    click.echo(key_value("Task", task[:60] + ("..." if len(task) > 60 else "")))
    click.echo()

    async def run_vote() -> tuple[list[SubAgentResult], str]:
        manager = SubAgentManager(provider, default_max_concurrent=max_concurrent)

        with click.progressbar(
            length=num_agents,
            label=click.style("Collecting votes", fg="cyan"),
            show_eta=True,
            bar_template=f"{click.style('%(label)s', fg='cyan')} [{click.style('%(bar)s', fg='magenta')}] {click.style('%(info)s', fg='bright_black')}",
            fill_char=click.style("█", fg="green"),
            empty_char=click.style("░", fg="bright_black"),
        ) as bar:

            def progress_callback(completed: int, total: int) -> None:
                bar.update(completed - bar.pos)

            results, voted_output = await manager.parallel_vote(
                task=task,
                num_agents=num_agents,
                config=agent_config,
                progress_callback=progress_callback,
            )

        return results, voted_output

    try:
        results, voted_output = asyncio.run(run_vote())
    except KeyboardInterrupt:
        print_warning("\n\nInterrupted by user")
        return
    except Exception as e:
        print_error(f"Voting failed: {e}")
        raise click.ClickException(str(e)) from e

    elapsed = time.time() - start_time

    # Display results
    click.echo()
    click.echo(header("VOTING RESULTS", width=58))
    click.echo()
    click.echo(voted_output)
    click.echo()

    # Show individual results
    click.echo(subheader("Individual Agent Results"))
    for i, result in enumerate(results, 1):
        status = success("✓") if result.success else error("✗")
        preview = result.output.split("\n")[0][:50] if result.output else "N/A"
        click.echo(f"  {status} Agent {i}: {preview}...")

    click.echo()
    print_info(f"Completed in {format_duration(elapsed)}")


@spawn_cli.command(name="map")
@click.argument("task_file", type=click.Path(exists=True))
@click.option(
    "--config-path",
    type=click.Path(exists=False),
    help="Path to config file",
)
@click.option(
    "--profile",
    help="Profile to use from config",
)
@click.option(
    "--max-concurrent",
    type=int,
    default=4,
    help="Maximum concurrent agents",
    show_default=True,
)
@click.option(
    "--delimiter",
    default="---",
    help="Delimiter between tasks in file",
    show_default=True,
)
def spawn_map(
    task_file: str,
    config_path: str | None,
    profile: str | None,
    max_concurrent: int,
    delimiter: str,
) -> None:
    """Execute multiple tasks from a file in parallel.

    The file should contain tasks separated by the delimiter (default: ---).

    Examples:
        oclawma spawn map tasks.txt
        oclawma spawn map tasks.txt --delimiter "###" --max-concurrent 8
    """
    # Load tasks from file
    try:
        content = Path(task_file).read_text()
        tasks = [t.strip() for t in content.split(delimiter) if t.strip()]
    except Exception as e:
        raise click.UsageError(f"Failed to read task file: {e}") from e

    if not tasks:
        raise click.UsageError("No tasks found in file")

    print_success(f"Loaded {len(tasks)} tasks from {task_file}")

    # Delegate to spawn exec
    ctx = click.get_current_context()
    ctx.invoke(
        spawn_exec,
        tasks=tuple(tasks),
        config_path=config_path,
        profile=profile,
        max_concurrent=max_concurrent,
    )


@spawn_cli.command(name="status")
def spawn_status() -> None:
    """Show sub-agent system status and limits."""
    click.echo(header("SUB-AGENT SPAWN SYSTEM STATUS", width=58))
    click.echo()

    click.echo(subheader("DEFAULT LIMITS"))
    click.echo(key_value("Max concurrent agents", "4 (configurable)"))
    click.echo(key_value("Default timeout", "300 seconds"))
    click.echo(key_value("Default context budget", "4000 tokens per agent"))
    click.echo()

    click.echo(subheader("AGGREGATION STRATEGIES"))
    click.echo(f"  {accent('•')} concatenate - Join results with separators")
    click.echo(f"  {accent('•')} summarize   - Summarize all results")
    click.echo(f"  {accent('•')} vote        - Majority vote (for categorical answers)")
    click.echo(f"  {accent('•')} merge       - Deduplicate and merge unique outputs")
    click.echo()

    click.echo(subheader("COMMANDS"))
    click.echo(f"  {highlight('oclawma spawn exec TASK...')}     Execute tasks in parallel")
    click.echo(f"  {highlight('oclawma spawn vote TASK')}        Vote on task results")
    click.echo(f"  {highlight('oclawma spawn map FILE')}         Execute tasks from file")
    click.echo(f"  {highlight('oclawma spawn status')}           Show this status")
    click.echo()

    click.echo(subheader("TIPS"))
    click.echo(f"  {bullet('Each sub-agent gets only task-specific context')}")
    click.echo(f"  {bullet('Use --max-concurrent to control resource usage')}")
    click.echo(f"  {bullet('Use vote for critical decisions')}")
    click.echo(f"  {bullet('Tasks can be loaded from files (pass file paths)')}")


def _display_results(
    results: list[SubAgentResult],
    aggregated: str,
    output_json: bool,
    elapsed: float = 0,
) -> None:
    """Display execution results."""
    import json

    if output_json:
        output = {
            "results": [r.to_dict() for r in results],
            "aggregated": aggregated,
            "stats": {
                "total": len(results),
                "successful": sum(1 for r in results if r.success),
                "failed": sum(1 for r in results if not r.success),
                "elapsed_seconds": elapsed,
            },
        }
        click.echo(json.dumps(output, indent=2, default=str))
        return

    # Text output
    successful = sum(1 for r in results if r.success)
    failed = len(results) - successful

    click.echo()
    click.echo(header("EXECUTION SUMMARY", width=58))
    click.echo()
    click.echo(key_value("Total agents", str(len(results))))
    click.echo(key_value("Successful", success(str(successful))))
    click.echo(key_value("Failed", error(str(failed)) if failed > 0 else str(failed)))
    if elapsed > 0:
        click.echo(key_value("Elapsed", format_duration(elapsed)))
    click.echo()

    if failed > 0:
        click.echo(subheader("Failed Agents"))
        for result in results:
            if not result.success:
                print_error(f"  - {result.agent_id}: {result.error}")
        click.echo()

    click.echo(subheader("AGGREGATED RESULTS"))
    click.echo()
    click.echo(aggregated)

    # Show individual details if requested (or if failures)
    if failed > 0 or len(results) <= 3:
        click.echo()
        click.echo(subheader("INDIVIDUAL RESULTS"))
        for i, result in enumerate(results, 1):
            status_icon = success("✓") if result.success else error("✗")
            click.echo(f"\n{status_icon} Agent {i} ({result.agent_id}) - {result.status.value}")
            if result.success:
                click.echo(result.output)
            else:
                print_error(f"  Error: {result.error}")


def _save_results(
    results: list[SubAgentResult],
    aggregated: str,
    output_path: str,
    output_json: bool,
) -> None:
    """Save results to file."""
    import json

    path = Path(output_path)

    if output_json:
        data = {
            "results": [r.to_dict() for r in results],
            "aggregated": aggregated,
        }
        path.write_text(json.dumps(data, indent=2, default=str))
    else:
        lines = [
            "# Sub-Agent Execution Results",
            "",
            "## Summary",
            f"- Total agents: {len(results)}",
            f"- Successful: {sum(1 for r in results if r.success)}",
            f"- Failed: {sum(1 for r in results if not r.success)}",
            "",
            "## Aggregated Results",
            "",
            aggregated,
            "",
            "## Individual Results",
            "",
        ]

        for i, result in enumerate(results, 1):
            lines.append(f"### Agent {i} ({result.agent_id})")
            lines.append(f"- Status: {result.status.value}")
            lines.append(f"- Task: {result.task}")
            if result.success:
                lines.append("")
                lines.append(result.output)
            else:
                lines.append(f"- Error: {result.error}")
            lines.append("")

        path.write_text("\n".join(lines))
