"""
Agent Tasks — create, list, update, and complete tasks via orch-hq.

Usage from within an agent:

    from orchagent import task

    # Create a task
    task.create("Review PR #42", due_date="2026-03-20", project="StockSure", priority="high")

    # List tasks
    overdue = task.list(status="open", overdue=True)
    all_open = task.list(status="open", limit=20)
    by_project = task.list(project="StockSure")

    # Complete a task
    task.complete(task_id)

    # Update a task
    task.update(task_id, status="in_progress")

Tasks appear in the orch-hq desktop app Tasks panel. They are structured,
queryable records — unlike the old markdown-based tasks in orch storage.

All functions are synchronous (blocking) for simplicity in agent scripts.
"""

from __future__ import annotations

import os
from typing import Any, Optional

import httpx

_DEFAULT_GATEWAY_URL = "https://api.orchagent.io"
_DEFAULT_TIMEOUT = 30.0


class TaskError(Exception):
    """Failed to perform a task operation."""
    def __init__(self, message: str, status_code: int | None = None):
        super().__init__(message)
        self.status_code = status_code


def _get_client() -> tuple[httpx.Client, dict[str, str]]:
    """Get HTTP client and auth headers."""
    gateway_url = os.environ.get("ORCHAGENT_GATEWAY_URL", _DEFAULT_GATEWAY_URL)
    api_key = os.environ.get("ORCHAGENT_SERVICE_KEY") or os.environ.get("ORCHAGENT_API_KEY", "")

    headers = {"Authorization": f"Bearer {api_key}"}

    # Propagate orchestration headers if present
    for env_key, header_name in [
        ("ORCHAGENT_CALL_CHAIN", "x-orchagent-call-chain"),
        ("ORCHAGENT_BILLING_ORG_ID", "x-orchagent-billing-org-id"),
        ("ORCHAGENT_REQUEST_ID", "x-orchagent-request-id"),
    ]:
        val = os.environ.get(env_key)
        if val:
            headers[header_name] = val

    client = httpx.Client(base_url=gateway_url, timeout=_DEFAULT_TIMEOUT)
    return client, headers


def create(
    title: str,
    *,
    description: Optional[str] = None,
    due_date: Optional[str] = None,
    project: Optional[str] = None,
    priority: str = "normal",
    status: str = "open",
    metadata: Optional[dict] = None,
) -> dict:
    """
    Create a task.

    Args:
        title: Task title
        description: Optional task details
        due_date: Optional due date as "YYYY-MM-DD"
        project: Optional free-text project tag
        priority: "low" | "normal" | "high" | "urgent" (default: "normal")
        status: "open" | "in_progress" | "done" | "cancelled" (default: "open")
        metadata: Optional dict for action metadata (e.g. {"action": "cli_skill", "repo": "...", "command": "..."})

    Returns:
        dict with id, title, status, priority, due_date, project, created_by, created_at

    Raises:
        TaskError: If the create fails
    """
    client, headers = _get_client()

    payload: dict[str, Any] = {"title": title, "priority": priority, "status": status}
    if description:
        payload["description"] = description
    if due_date:
        payload["due_date"] = due_date
    if project:
        payload["project"] = project
    if metadata:
        payload["metadata"] = metadata

    try:
        response = client.post("/tasks", json=payload, headers=headers)
    except httpx.HTTPError as e:
        raise TaskError(f"HTTP error: {e}") from e
    finally:
        client.close()

    if response.status_code == 201:
        return response.json()

    try:
        err = response.json()
        msg = err.get("message", response.text)
    except Exception:
        msg = response.text

    raise TaskError(f"Failed to create task: {msg}", status_code=response.status_code)


def list(
    *,
    limit: int = 50,
    offset: int = 0,
    status: Optional[str] = None,
    project: Optional[str] = None,
    priority: Optional[str] = None,
    overdue: bool = False,
) -> list[dict]:
    """
    List tasks.

    Args:
        limit: Max tasks to return (1-200, default 50)
        offset: Pagination offset (default 0)
        status: Filter by status — "open" | "in_progress" | "done" | "cancelled"
        project: Filter by project tag
        priority: Filter by priority — "low" | "normal" | "high" | "urgent"
        overdue: If True, only return open tasks past their due date

    Returns:
        List of task dicts

    Raises:
        TaskError: If the request fails
    """
    client, headers = _get_client()

    params: dict[str, Any] = {"limit": limit, "offset": offset}
    if status:
        params["status"] = status
    if project:
        params["project"] = project
    if priority:
        params["priority"] = priority
    if overdue:
        params["overdue"] = "true"

    try:
        response = client.get("/tasks", params=params, headers=headers)
    except httpx.HTTPError as e:
        raise TaskError(f"HTTP error: {e}") from e
    finally:
        client.close()

    if response.status_code == 200:
        data = response.json()
        return data.get("tasks", [])

    try:
        err = response.json()
        msg = err.get("message", response.text)
    except Exception:
        msg = response.text

    raise TaskError(f"Failed to list tasks: {msg}", status_code=response.status_code)


def get(task_id: str) -> dict:
    """
    Get a single task by ID.

    Args:
        task_id: The task UUID

    Returns:
        Task dict with all fields

    Raises:
        TaskError: If the request fails or task not found
    """
    client, headers = _get_client()

    try:
        response = client.get(f"/tasks/{task_id}", headers=headers)
    except httpx.HTTPError as e:
        raise TaskError(f"HTTP error: {e}") from e
    finally:
        client.close()

    if response.status_code == 200:
        return response.json()

    try:
        err = response.json()
        msg = err.get("message", response.text)
    except Exception:
        msg = response.text

    raise TaskError(f"Failed to get task: {msg}", status_code=response.status_code)


def update(
    task_id: str,
    *,
    title: Optional[str] = None,
    description: Optional[str] = None,
    due_date: Optional[str] = None,
    project: Optional[str] = None,
    status: Optional[str] = None,
    priority: Optional[str] = None,
) -> dict:
    """
    Update a task. Only provided fields are changed.

    Args:
        task_id: The task UUID
        title: New title
        description: New description
        due_date: New due date ("YYYY-MM-DD") or None to clear
        project: New project tag or None to clear
        status: New status — "open" | "in_progress" | "done" | "cancelled"
        priority: New priority — "low" | "normal" | "high" | "urgent"

    Returns:
        Updated task dict

    Raises:
        TaskError: If the update fails
    """
    client, headers = _get_client()

    payload: dict[str, Any] = {}
    if title is not None:
        payload["title"] = title
    if description is not None:
        payload["description"] = description
    if due_date is not None:
        payload["due_date"] = due_date
    if project is not None:
        payload["project"] = project
    if status is not None:
        payload["status"] = status
    if priority is not None:
        payload["priority"] = priority

    if not payload:
        raise TaskError("No fields to update")

    try:
        response = client.patch(f"/tasks/{task_id}", json=payload, headers=headers)
    except httpx.HTTPError as e:
        raise TaskError(f"HTTP error: {e}") from e
    finally:
        client.close()

    if response.status_code == 200:
        return response.json()

    try:
        err = response.json()
        msg = err.get("message", response.text)
    except Exception:
        msg = response.text

    raise TaskError(f"Failed to update task: {msg}", status_code=response.status_code)


def complete(task_id: str) -> dict:
    """
    Mark a task as done. Convenience wrapper for update(task_id, status="done").

    Args:
        task_id: The task UUID

    Returns:
        Updated task dict

    Raises:
        TaskError: If the update fails
    """
    return update(task_id, status="done")
