"""appxen workflow — manage orchestrator workflows and executions."""

from __future__ import annotations

import time
from pathlib import Path
from typing import Any

import typer

from appxen_cli.display import console, error, print_json, success, warn

app = typer.Typer(help="Manage agent orchestrator workflows.")


@app.callback(invoke_without_command=True)
def workflow_default(ctx: typer.Context) -> None:
    """List workflows (default when no subcommand given)."""
    if ctx.invoked_subcommand is not None:
        return
    _list_workflows(json_output=False)


@app.command("list")
def list_cmd(
    json_output: bool = typer.Option(False, "--json", help="Output raw JSON"),
) -> None:
    """List all workflows."""
    _list_workflows(json_output)


def _list_workflows(json_output: bool) -> None:
    from appxen_cli.main import get_client

    try:
        client = get_client()
    except typer.Exit:
        raise

    try:
        data = client.list_workflows()
    except Exception as e:
        error(f"Failed to list workflows: {e}")
        raise typer.Exit(1)

    if json_output:
        print_json(data)
        return

    workflows = data.get("workflows", [])
    if not workflows:
        console.print("[dim]No workflows found.[/dim]")
        return

    from rich.table import Table

    table = Table(title=f"Workflows ({len(workflows)})")
    table.add_column("ID", style="dim")
    table.add_column("Name", style="bold")
    table.add_column("Version", justify="right")
    table.add_column("Status")
    table.add_column("Updated")

    for wf in workflows:
        status_style = "green" if wf.get("status") == "active" else "yellow"
        table.add_row(
            wf.get("workflow_id", ""),
            wf.get("name", ""),
            str(wf.get("version", "")),
            f"[{status_style}]{wf.get('status', 'unknown')}[/{status_style}]",
            (wf.get("updated_at") or "")[:19],
        )

    console.print(table)


@app.command()
def compile(
    file: Path = typer.Argument(..., help="Markdown workflow file", exists=True),
    json_output: bool = typer.Option(False, "--json", help="Output raw JSON"),
) -> None:
    """Compile a markdown workflow without saving (preview)."""
    from appxen_cli.main import get_client

    try:
        client = get_client()
    except typer.Exit:
        raise

    markdown = file.read_text()

    try:
        data = client.compile_workflow(markdown)
    except Exception as e:
        error(f"Compilation failed: {e}")
        raise typer.Exit(1)

    if json_output:
        print_json(data)
        return

    _display_plan(data)


@app.command()
def create(
    file: Path = typer.Argument(..., help="Markdown workflow file", exists=True),
    name: str = typer.Option(None, "--name", "-n", help="Workflow name (defaults to filename)"),
    json_output: bool = typer.Option(False, "--json", help="Output raw JSON"),
) -> None:
    """Compile and save a markdown workflow."""
    from appxen_cli.main import get_client

    try:
        client = get_client()
    except typer.Exit:
        raise

    markdown = file.read_text()
    wf_name = name or file.stem

    try:
        data = client.create_workflow(wf_name, markdown)
    except Exception as e:
        error(f"Failed to create workflow: {e}")
        raise typer.Exit(1)

    if json_output:
        print_json(data)
        return

    wf_id = data.get("workflow_id", "")
    success(f"Workflow created: [bold]{wf_id}[/bold]")
    _display_plan(data)


@app.command()
def get(
    workflow_id: str = typer.Argument(..., help="Workflow ID"),
    json_output: bool = typer.Option(False, "--json", help="Output raw JSON"),
) -> None:
    """Get workflow details."""
    from appxen_cli.main import get_client

    try:
        client = get_client()
    except typer.Exit:
        raise

    try:
        data = client.get_workflow(workflow_id)
    except Exception as e:
        error(f"Failed to get workflow: {e}")
        raise typer.Exit(1)

    if json_output:
        print_json(data)
        return

    from rich.panel import Panel

    lines = [
        f"[bold]ID:[/bold]       {data.get('workflow_id', '')}",
        f"[bold]Name:[/bold]     {data.get('name', '')}",
        f"[bold]Version:[/bold]  {data.get('version', '')}",
        f"[bold]Status:[/bold]   {data.get('status', '')}",
        f"[bold]Created:[/bold]  {(data.get('created_at') or '')[:19]}",
        f"[bold]Updated:[/bold]  {(data.get('updated_at') or '')[:19]}",
    ]
    plan = data.get("plan", {})
    if plan:
        lines.append(f"[bold]Agents:[/bold]   {len(plan.get('agents', []))}")
        lines.append(f"[bold]Steps:[/bold]    {len(plan.get('steps', []))}")
    console.print(Panel("\n".join(lines), title="Workflow", border_style="blue"))


@app.command()
def delete(
    workflow_id: str = typer.Argument(..., help="Workflow ID"),
    yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
) -> None:
    """Delete a workflow."""
    from appxen_cli.main import get_client

    if not yes:
        confirm = typer.confirm(f"Delete workflow {workflow_id}?")
        if not confirm:
            raise typer.Exit()

    try:
        client = get_client()
    except typer.Exit:
        raise

    try:
        client.delete_workflow(workflow_id)
    except Exception as e:
        error(f"Failed to delete workflow: {e}")
        raise typer.Exit(1)

    success(f"Workflow [bold]{workflow_id}[/bold] deleted.")


@app.command()
def run(
    workflow_id: str = typer.Argument(..., help="Workflow ID to execute"),
    inputs: list[str] = typer.Option([], "--input", "-i", help="Input key=value pairs"),
    poll: bool = typer.Option(True, "--poll/--no-poll", help="Poll for completion"),
    save: Path = typer.Option(None, "--save", "-o", help="Save output to file on completion"),
    json_output: bool = typer.Option(False, "--json", help="Output raw JSON"),
) -> None:
    """Start a workflow execution."""
    from appxen_cli.main import get_client

    try:
        client = get_client()
    except typer.Exit:
        raise

    # Parse key=value inputs
    user_inputs: dict[str, Any] = {}
    for kv in inputs:
        if "=" not in kv:
            error(f"Invalid input format: {kv!r}. Use key=value.")
            raise typer.Exit(1)
        key, value = kv.split("=", 1)
        user_inputs[key.strip()] = value.strip()

    try:
        data = client.run_workflow(workflow_id, user_inputs)
    except Exception as e:
        error(f"Failed to start execution: {e}")
        raise typer.Exit(1)

    exec_id = data.get("execution_id", "")

    if json_output and not poll:
        print_json(data)
        return

    success(f"Execution started: [bold]{exec_id}[/bold]")

    if not poll:
        return

    # Poll for completion (with Ctrl+C handling)
    _poll_execution(client, exec_id, json_output, save_path=save)


@app.command()
def output(
    execution_id: str = typer.Argument(..., help="Execution ID"),
    step: str = typer.Option(None, "--step", "-s", help="Show specific step output"),
    save: Path = typer.Option(None, "--save", "-o", help="Save output to file"),
    json_output: bool = typer.Option(False, "--json", help="Output raw JSON"),
) -> None:
    """Get the full output of a completed execution."""
    from appxen_cli.main import get_orchestrator_client

    try:
        client = get_orchestrator_client()
    except typer.Exit:
        raise

    try:
        exec_data = client.get_execution(execution_id)
    except Exception as e:
        error(f"Failed to get execution: {e}")
        raise typer.Exit(1)

    if json_output and not step:
        print_json(exec_data)
        return

    # If requesting a specific step's output
    if step:
        try:
            steps_data = client.get_steps(execution_id)
        except Exception as e:
            error(f"Failed to get steps: {e}")
            raise typer.Exit(1)

        step_list = steps_data.get("steps", [])
        matched = [s for s in step_list if s.get("step_id") == step]
        if not matched:
            available = [s.get("step_id", "?") for s in step_list]
            error(f"Step '{step}' not found. Available: {', '.join(available)}")
            raise typer.Exit(1)

        step_output = matched[0].get("output", "")
        if json_output:
            print_json({"step_id": step, "output": step_output})
            return

        if not step_output:
            console.print(f"[dim]Step '{step}' has no output.[/dim]")
            return

        output_type = exec_data.get("output_type", "text")
        _render_output(step_output, output_type, save)
        return

    # Full execution output
    exec_status = exec_data.get("status", "unknown")
    if exec_status not in ("completed", "failed"):
        warn(f"Execution is still {exec_status}. Output may not be available yet.")

    final_output = exec_data.get("final_output")
    if not final_output:
        console.print("[dim]No output available.[/dim]")
        raise typer.Exit(1)

    output_type = exec_data.get("output_type", "text")
    _render_output(str(final_output), output_type, save)


@app.command()
def status(
    execution_id: str = typer.Argument(..., help="Execution ID"),
    poll: bool = typer.Option(False, "--poll/--no-poll", help="Poll for completion"),
    json_output: bool = typer.Option(False, "--json", help="Output raw JSON"),
) -> None:
    """Get execution status and steps."""
    from appxen_cli.main import get_client

    try:
        client = get_client()
    except typer.Exit:
        raise

    if poll:
        _poll_execution(client, execution_id, json_output)
        return

    try:
        exec_data = client.get_execution(execution_id)
        steps_data = client.get_steps(execution_id)
    except Exception as e:
        error(f"Failed to get execution: {e}")
        raise typer.Exit(1)

    if json_output:
        print_json({"execution": exec_data, "steps": steps_data.get("steps", [])})
        return

    _display_execution(exec_data, steps_data.get("steps", []))


@app.command()
def stop(
    execution_id: str = typer.Argument(..., help="Execution ID"),
    yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
) -> None:
    """Stop a running execution."""
    from appxen_cli.main import get_client

    if not yes:
        confirm = typer.confirm(f"Stop execution {execution_id}?")
        if not confirm:
            raise typer.Exit()

    try:
        client = get_client()
    except typer.Exit:
        raise

    try:
        data = client.stop_execution(execution_id)
    except Exception as e:
        error(f"Failed to stop execution: {e}")
        raise typer.Exit(1)

    success(f"Execution [bold]{execution_id}[/bold] stopped (status: {data.get('status', '?')}).")


# -- Output rendering helpers --


# Map code:<lang> to file extensions
_LANG_EXTENSIONS: dict[str, str] = {
    "python": ".py", "py": ".py",
    "javascript": ".js", "js": ".js",
    "typescript": ".ts", "ts": ".ts",
    "rust": ".rs", "rs": ".rs",
    "go": ".go",
    "java": ".java",
    "c": ".c", "cpp": ".cpp", "c++": ".cpp",
    "ruby": ".rb", "rb": ".rb",
    "shell": ".sh", "bash": ".sh", "sh": ".sh",
    "sql": ".sql",
    "yaml": ".yaml", "yml": ".yaml",
    "toml": ".toml",
    "css": ".css",
    "html": ".html",
    "xml": ".xml",
}


def _render_output(text: str, output_type: str, save_path: Path | None) -> None:
    """Render output to terminal and optionally save to file."""
    if save_path:
        save_path.write_text(text)
        success(f"Output saved to [bold]{save_path}[/bold] ({output_type}, {len(text):,} chars)")
        return

    if output_type == "markdown":
        from rich.markdown import Markdown
        console.print(Markdown(text))
    elif output_type == "json":
        from rich.syntax import Syntax
        console.print(Syntax(text, "json", theme="monokai"))
    elif output_type.startswith("code:"):
        from rich.syntax import Syntax
        lang = output_type.split(":", 1)[1]
        console.print(Syntax(text, lang, theme="monokai"))
    elif output_type in ("html", "svg"):
        console.print(text)
        console.print(f"\n[dim]Hint: save with --save to open in browser[/dim]")
    else:
        # text or unknown
        console.print(text)


def _infer_extension(output_type: str) -> str:
    """Infer file extension from output type."""
    if output_type == "markdown":
        return ".md"
    if output_type == "json":
        return ".json"
    if output_type == "html":
        return ".html"
    if output_type == "svg":
        return ".svg"
    if output_type.startswith("code:"):
        lang = output_type.split(":", 1)[1].lower()
        return _LANG_EXTENSIONS.get(lang, ".txt")
    return ".txt"


# -- Display helpers --


def _display_plan(data: dict) -> None:
    """Display a compiled plan."""
    from rich.panel import Panel
    from rich.tree import Tree

    plan = data.get("plan", {})
    notes = data.get("notes", [])
    usage = data.get("usage", {})

    console.print()
    console.print(f"[bold]{plan.get('name', 'Unnamed')}[/bold]")
    console.print(f"[dim]{plan.get('description', '')}[/dim]")

    # Agents
    agents = plan.get("agents", [])
    if agents:
        console.print()
        tree = Tree("[bold]Agents[/bold]")
        for a in agents:
            tools = f" [dim]tools: {', '.join(a.get('tools', []))}[/dim]" if a.get("tools") else ""
            tree.add(f"[cyan]{a['id']}[/cyan] ({a.get('model', '?')}){tools}")
        console.print(tree)

    # Steps
    steps = plan.get("steps", [])
    if steps:
        console.print()
        tree = Tree("[bold]Steps[/bold]")
        for s in steps:
            deps = f" [dim]← {', '.join(s.get('depends_on', []))}[/dim]" if s.get("depends_on") else ""
            pattern = f" [yellow]({s.get('pattern', 'sequential')})[/yellow]" if s.get("pattern") != "sequential" else ""
            tree.add(f"[cyan]{s['id']}[/cyan] → {s.get('agent', '?')}{pattern}{deps}")
        console.print(tree)

    # Notes
    if notes:
        console.print()
        for note in notes:
            console.print(f"  [dim]- {note}[/dim]")

    # Usage
    if usage:
        tokens_in = usage.get("input_tokens", 0)
        tokens_out = usage.get("output_tokens", 0)
        console.print(f"\n[dim]Compiler tokens: {tokens_in} in / {tokens_out} out[/dim]")


def _display_execution(exec_data: dict, steps: list[dict]) -> None:
    """Display execution status and steps."""
    from rich.panel import Panel

    status_colors = {
        "running": "yellow",
        "completed": "green",
        "failed": "red",
        "aborted": "red",
    }
    exec_status = exec_data.get("status", "unknown")
    color = status_colors.get(exec_status, "dim")

    exec_id = exec_data.get("execution_id", "")
    lines = [
        f"[bold]ID:[/bold]         {exec_id}",
        f"[bold]Workflow:[/bold]   {exec_data.get('workflow_id', '')}",
        f"[bold]Status:[/bold]     [{color}]{exec_status}[/{color}]",
        f"[bold]Started:[/bold]    {(exec_data.get('started_at') or '')[:19]}",
    ]
    if exec_data.get("completed_at"):
        lines.append(f"[bold]Completed:[/bold]  {exec_data['completed_at'][:19]}")
    if exec_data.get("error"):
        lines.append(f"[bold red]Error:[/bold red]      {exec_data['error']}")
    if exec_data.get("final_output"):
        output_str = str(exec_data["final_output"])
        if len(output_str) > 200:
            output_str = output_str[:200] + "..."
            lines.append(f"[bold]Output:[/bold]     {output_str}")
            lines.append(
                f"[dim]Use 'appxen workflow output {exec_id}' for full output[/dim]"
            )
        else:
            lines.append(f"[bold]Output:[/bold]     {output_str}")

    console.print(Panel("\n".join(lines), title="Execution", border_style="blue"))

    # Steps
    if steps:
        from rich.table import Table

        table = Table(title="Steps")
        table.add_column("Step", style="bold")
        table.add_column("Agent")
        table.add_column("Status")
        table.add_column("Tokens", justify="right")

        for s in steps:
            step_status = s.get("status", "pending")
            step_color = status_colors.get(step_status, "dim")
            tokens = s.get("tokens", {})
            token_str = ""
            if tokens:
                token_str = f"{tokens.get('input', 0)} / {tokens.get('output', 0)}"
            table.add_row(
                s.get("step_id", ""),
                s.get("agent_id", ""),
                f"[{step_color}]{step_status}[/{step_color}]",
                token_str,
            )

        console.print(table)


def _poll_execution(
    client: Any,
    execution_id: str,
    json_output: bool,
    save_path: Path | None = None,
) -> None:
    """Poll an execution until it completes."""
    from rich.live import Live
    from rich.spinner import Spinner

    terminal_statuses = {"completed", "failed", "aborted", "timed_out"}
    poll_interval = 3
    max_polls = 200  # 10 minutes at 3s intervals

    exec_data: dict = {}
    steps: list[dict] = []

    try:
        with Live(Spinner("dots", text="Running..."), console=console, transient=True) as live:
            for i in range(max_polls):
                try:
                    exec_data = client.get_execution(execution_id)
                    steps_data = client.get_steps(execution_id)
                except Exception as e:
                    warn(f"Poll error: {e}")
                    time.sleep(poll_interval)
                    continue

                poll_status = exec_data.get("status", "unknown")
                steps = steps_data.get("steps", [])

                completed_steps = sum(1 for s in steps if s.get("status") in terminal_statuses)
                total_steps = len(steps)
                step_info = f" ({completed_steps}/{total_steps} steps)" if total_steps else ""

                live.update(Spinner("dots", text=f"[yellow]{poll_status}[/yellow]{step_info}"))

                if poll_status in terminal_statuses:
                    break

                time.sleep(poll_interval)
    except KeyboardInterrupt:
        console.print()
        console.print(f"[yellow]Execution still running in background.[/yellow]")
        console.print(f"  Check status:  [bold]appxen workflow status {execution_id}[/bold]")
        console.print(f"  Get output:    [bold]appxen workflow output {execution_id} --save result.md[/bold]")
        raise typer.Exit()

    # Final display
    if json_output:
        print_json({"execution": exec_data, "steps": steps})
        return

    _display_execution(exec_data, steps)

    # Save output if requested
    if save_path and exec_data.get("final_output"):
        output_text = str(exec_data["final_output"])
        output_type = exec_data.get("output_type", "text")
        save_path.write_text(output_text)
        success(f"Output saved to [bold]{save_path}[/bold] ({output_type}, {len(output_text):,} chars)")
