"""proxym do — one-command workflow: describe → ticket → tool → run → logs."""

from __future__ import annotations

import sys
import time

import click

from proxym.cli._client import make_client
from proxym.cli.smart_defaults import (
    build_context_snippet,
    default_priority,
    detect_ticket_type,
    resolve_model,
    resolve_project,
    resolve_tool,
)
from proxym.project_config import ProjectConfig, load_project_config


# ── helpers (each CC ≤ 6) ────────────────────────────────────


def _fetch_projects(client) -> list[dict]:
    """Get project list from API, tolerating errors."""
    try:
        r = client.get("/dashboard/projects")
        data = r.json()
        return data if isinstance(data, list) else data.get("projects", [])
    except Exception:
        return []


def _resolve_all(client, task, project_name, tool_name, task_type, priority, model_name=None):
    """Resolve project, type, tool, priority, model from smart defaults + .proxym.yaml."""
    projects = _fetch_projects(client)
    project = resolve_project(projects, explicit=project_name)
    proj_dir = (project.get("local_path") or project.get("source_path") or "") if project else ""
    pcfg = load_project_config(proj_dir) if proj_dir else ProjectConfig()
    task_type = task_type or detect_ticket_type(task)
    tool = resolve_tool(task_type, project, tool_name, project_config=pcfg)
    model = resolve_model(project, model_name, project_config=pcfg)
    priority = priority or default_priority(task_type)
    return project, task_type, tool, priority, model, pcfg


def _print_plan(task, project, tool, task_type, priority, model="", pcfg=None):
    """Display resolved plan."""
    proj_display = project["name"] if project else "(none)"
    proj_dir = (project.get("local_path") or project.get("source_path") or "") if project else ""
    click.echo(f"\n  Task:     {task}")
    click.echo(f"  Project:  {proj_display}")
    if proj_dir:
        click.echo(f"  Path:     {proj_dir}")
    click.echo(f"  Tool:     {tool}")
    if model:
        click.echo(f"  Model:    {model}")
    click.echo(f"  Type:     {task_type}")
    click.echo(f"  Priority: {priority}")
    if pcfg and pcfg.context_files:
        click.echo(f"  Context:  {', '.join(pcfg.context_files)}")


def _print_preflight(result: dict) -> None:
    """Pretty-print preflight validation results."""
    click.echo("\n  Pre-flight check")
    if result.get("errors"):
        click.echo("  Errors:")
        for err in result["errors"]:
            click.echo(f"    - {err}")
    if result.get("warnings"):
        click.echo("  Warnings:")
        for warn in result["warnings"]:
            click.echo(f"    - {warn}")
    ticket = result.get("ticket") or {}
    if ticket:
        click.echo(f"  Ticket: {ticket.get('id', '?')} · {ticket.get('title', ticket.get('task', ''))}")
    if result.get("project_dir"):
        click.echo(f"  Project dir: {result['project_dir']}")


def _preflight_dispatch(client, ticket_id: str, tool: str, project_dir: str = "") -> dict:
    """Ask the dashboard to validate the dispatch plan before enqueueing."""
    payload = {
        "ticket_id": ticket_id,
        "tool": tool,
        "project_dir": project_dir,
    }
    r = client.post("/dashboard/tools/dispatch/dry-run", json=payload)
    data = r.json()
    _print_preflight(data)
    return data


def _create_ticket(client, task, project, tool, task_type, priority):
    """Create ticket via API. Returns (ticket_id, ok)."""
    payload = {"task": task, "type": task_type, "priority": priority, "tool": tool}
    if project:
        payload["project_id"] = project.get("id", "")
    try:
        r = client.post("/dashboard/tickets", json=payload)
        r.raise_for_status()
        ticket_id = r.json().get("id", "?")
        click.echo(f"\n  Ticket {ticket_id} created")
        return ticket_id, True
    except Exception as exc:
        click.echo(f"\n  Failed to create ticket: {exc}")
        return None, False


def _dispatch(client, ticket_id, tool, model=""):
    """Dispatch tool for ticket. Returns job_id or None."""
    try:
        payload = {"ticket_id": ticket_id, "tool": tool}
        if model:
            payload["model"] = model
        r = client.post("/dashboard/tools/dispatch", json=payload)
        if r.status_code >= 400:
            click.echo(f"  Dispatch failed ({r.status_code}): {r.text[:200]}")
            return None
        data = r.json()
        job_id = data.get("job", {}).get("id") or data.get("job_id", "?")
        click.echo(f"  Dispatched -> {tool} (job {job_id})")
        return job_id
    except Exception as exc:
        click.echo(f"  Dispatch error: {exc}")
        return None


def _print_done(job):
    """Print completion summary."""
    result = job.get("result") or {}
    dur = job.get("duration_s", 0)
    click.echo(f"\n  Done in {dur:.0f}s")
    files = result.get("files_changed", [])
    if files:
        click.echo(f"  Files: {', '.join(files[:10])}")
    output = result.get("stdout", "")[-500:]
    if output:
        click.echo(f"\n{output}")


def _follow_job(client, job_id, poll_interval=5, timeout=600, stream=False):
    """Poll job status until done/failed. Returns final job dict or None.

    When *stream* is True, display incremental stdout from the running job.
    """
    click.echo(f"\n  {'─' * 50}")
    click.echo("  Waiting for output... (Ctrl+C to detach)\n")
    max_polls = max(timeout // poll_interval, 1)
    seen_bytes = 0
    try:
        for tick in range(max_polls):
            time.sleep(poll_interval)
            try:
                r = client.get(f"/dashboard/tools/queue/jobs/{job_id}")
            except Exception:
                break
            if r.status_code != 200:
                break
            job = r.json()
            status = job.get("status", "unknown")

            # Stream incremental stdout while running
            if stream and status == "running":
                stdout = (job.get("result") or {}).get("stdout", "")
                if len(stdout) > seen_bytes:
                    new_text = stdout[seen_bytes:]
                    seen_bytes = len(stdout)
                    sys.stdout.write(new_text)
                    sys.stdout.flush()

            if status == "done":
                if stream:
                    # Print any remaining stdout
                    stdout = (job.get("result") or {}).get("stdout", "")
                    if len(stdout) > seen_bytes:
                        sys.stdout.write(stdout[seen_bytes:])
                        sys.stdout.flush()
                _print_done(job)
                return job
            if status == "failed":
                err = job.get("error") or job.get("result", {}).get("stderr", "")
                click.echo(f"\n  Failed: {err[:300]}")
                return job
            if status == "cancelled":
                click.echo("\n  Cancelled.")
                return job
            if not stream:
                sys.stdout.write(f"\r  {status}... ({tick * poll_interval}s)")
                sys.stdout.flush()
        else:
            click.echo(f"\n\n  Timeout after {timeout}s. Job {job_id} still running.")
            click.echo(f"  Check: proxym tools job {job_id}")
    except KeyboardInterrupt:
        click.echo(f"\n\n  Detached. Job {job_id} still running.")
        click.echo(f"  Check: proxym tools job {job_id}")
    return None


# ── CLI command ──────────────────────────────────────────────


def _run_analyze(client, ticket_id: str) -> bool:
    """Run ticket analysis and return True if PASS."""
    try:
        r = client.get(f"/dashboard/tickets/{ticket_id}")
        if r.status_code != 200:
            return False
        data = r.json()
        last_job = data.get("last_job") or data.get("last_execution")
        if not last_job:
            return False
        success = last_job.get("success")
        job_status = last_job.get("status", "?")
        if success and job_status == "done":
            click.echo(f"\n  \033[32mResult: PASS\033[0m — ticket completed")
            return True
        elif success is False or job_status == "failed":
            err = last_job.get("error", "")
            click.echo(f"\n  \033[31mResult: FAIL\033[0m")
            if err:
                click.echo(f"  Error: {err[:200]}")
            return False
        else:
            click.echo(f"\n  Result: {job_status}")
            return False
    except Exception:
        return False


@click.command("do")
@click.argument("task", required=False)
@click.option("--on", "project_name", default=None, help="Project name or id")
@click.option("--with", "tool_name", default=None, help="Tool to use (aider, claude-code, ...)")
@click.option("--model", "model_name", default=None, help="Model alias or full name")
@click.option("--priority", "-p", default=None, help="Priority (critical/high/medium/low)")
@click.option("--type", "task_type", default=None, help="Task type (bug/feature/refactor/test/task)")
@click.option("--no-run", is_flag=True, help="Create ticket but don't dispatch")
@click.option("--no-wait", is_flag=True, help="Dispatch but don't follow output")
@click.option("--analyze", is_flag=True, help="Run analysis after job completes")
@click.option("--stream", is_flag=True, help="Show incremental output from the running job")
@click.option("--timeout", type=int, default=None, help="Wait timeout in seconds (default: from .proxym.yaml or 300)")
@click.option("--dry-run", is_flag=True, help="Show plan without executing")
@click.pass_context
def do_cmd(ctx, task, project_name, tool_name, model_name, priority, task_type,
           no_run, no_wait, analyze, stream, timeout, dry_run):
    """One command to rule them all.

    \b
    Examples:
      proxym do "fix crash in /health endpoint"
      proxym do "refactor panel.jsx" --on proxym --with claude-code
      proxym do "add tests" --on my-project --model balanced --analyze
      proxym do                          # interactive mode
    """
    obj = ctx.obj or {}
    client = make_client(obj.get("proxy"), obj.get("key"))

    if not task:
        task = click.prompt("  What to do?")

    project, task_type, tool, priority, model, pcfg = _resolve_all(
        client, task, project_name, tool_name, task_type, priority, model_name,
    )
    if project_name and not project:
        click.echo(f"  Project '{project_name}' not found.")
        ctx.exit(1)
        return

    # Auto-analyze from .proxym.yaml
    if pcfg.auto_analyze and not no_wait:
        analyze = True
    # Timeout from .proxym.yaml or default
    timeout = timeout or pcfg.timeout or 300

    # Append context files from .proxym.yaml to task description
    proj_dir = (project.get("local_path") or project.get("source_path") or "") if project else ""
    context_snippet = build_context_snippet(pcfg, proj_dir)
    full_task = task + context_snippet if context_snippet else task

    _print_plan(task, project, tool, task_type, priority, model, pcfg)

    if project and not proj_dir:
        click.echo("  [warn] Project has no local path; dispatch will fall back to ticket/project defaults.")

    if dry_run:
        click.echo("\n  [dry-run] Would create ticket and dispatch.")
        click.echo("  [dry-run] No changes will be made.")
        return

    ticket_id, ok = _create_ticket(client, full_task, project, tool, task_type, priority)
    if not ok:
        ctx.exit(1)
        return

    preflight = _preflight_dispatch(client, ticket_id, tool, project_dir=proj_dir)
    if not preflight.get("can_dispatch", False):
        click.echo("  Dispatch blocked by pre-flight checks.")
        ctx.exit(1)
        return

    if no_run:
        click.echo("  [--no-run] Ticket created, not dispatched.")
        return

    job_id = _dispatch(client, ticket_id, tool, model)
    if not job_id:
        ctx.exit(1)
        return

    if no_wait:
        click.echo(f"\n  [--no-wait] Job {job_id} queued.")
        click.echo(f"  Check: proxym tools job {job_id}")
        return

    final_job = _follow_job(client, job_id, timeout=timeout, stream=stream)

    if analyze and ticket_id:
        passed = _run_analyze(client, ticket_id)
        if not passed:
            ctx.exit(1)
            return

    # Exit code based on job result
    if final_job:
        status = final_job.get("status", "unknown")
        if status != "done":
            ctx.exit(1)
