"""Tasks & reminders — thin client to the shared Railway brain, with HUD cards.

Jarvis can add/list/complete tasks and reminders; they live in the SAME Railway
service as long-term memory (:Task nodes), so they SYNC across every device.
Adding one also pops a card on the HUD (like the email/calendar confirm cards),
and Ahmed can tick a task off there — the HUD writes control/card_action.json,
the ControlWatcher marks it done on Railway and tells Jarvis.

  Jarvis (voice) -> task_add / task_list / task_done
  Ahmed  (HUD)   -> ticks a checkbox -> card_action.json {kind:"task", ...}

Reuses MEMORY_API_URL / MEMORY_API_KEY (same service). MCP_TASKS=0 disables.

Engine → HUD events (voice.events.emit):
  task_added {id, text, due}          — one new task/reminder
  tasks      {tasks:[{id,text,due,done}]} — full current list (panel refresh)
  task_done_evt {id}                  — a task was completed (remove from panel)
"""
from __future__ import annotations

import json
import os
import urllib.parse
import urllib.request

from voice.events import emit

_URL = os.environ.get("MEMORY_API_URL", "").rstrip("/")
_KEY = os.environ.get("MEMORY_API_KEY", "")
_GROUP = os.environ.get("MEMORY_GROUP", "ahmed")


def enabled() -> bool:
    if os.environ.get("MCP_TASKS", "1") == "0":
        return False
    return bool(_URL)


def _text(msg: str) -> dict:
    return {"content": [{"type": "text", "text": msg}]}


def _req(method: str, path: str, body: dict | None = None, timeout: int = 15):
    data = json.dumps(body).encode() if body is not None else None
    req = urllib.request.Request(_URL + path, data=data, method=method)
    if _KEY:
        req.add_header("Authorization", f"Bearer {_KEY}")
    req.add_header("Content-Type", "application/json")
    with urllib.request.urlopen(req, timeout=timeout) as r:
        raw = r.read()
        return json.loads(raw) if raw else {}


def fetch_tasks(include_done: bool = False) -> list[dict]:
    qs = urllib.parse.urlencode({"group": _GROUP,
                                 "include_done": str(include_done).lower()})
    return _req("GET", f"/tasks?{qs}").get("tasks", [])


def fetch_due() -> list[dict]:
    """Reminders whose time has passed and haven't been announced yet. The
    server marks each 'fired' so it's returned only once."""
    qs = urllib.parse.urlencode({"group": _GROUP})
    return _req("GET", f"/reminders/due?{qs}").get("due", [])


def refresh_panel() -> None:
    """Push the current open task list to the HUD panel."""
    try:
        emit("tasks", tasks=fetch_tasks())
    except Exception as e:  # noqa: BLE001
        print(f"  [tasks] panel refresh failed: {str(e)[:100]}")


def mark_done(task_id: str) -> str:
    """Complete a task on Railway; used by the ControlWatcher on a HUD tick."""
    _req("POST", "/task/done", {"id": task_id, "group": _GROUP})
    emit("task_done_evt", id=task_id)
    return "done"


def build_server():
    import asyncio
    from claude_agent_sdk import tool, create_sdk_mcp_server

    @tool("task_add",
          "Add a task or reminder to Ahmed's shared list (syncs to all his "
          "devices) and pop it on his HUD. Use for 'remind me to…', 'add a "
          "task', 'I need to…'. `due` = ISO local time (2026-07-08T17:00:00) "
          "for a timed reminder — he'll be told when it's due; omit for a plain "
          "to-do. Keep `text` short and imperative ('Call the supplier').",
          {"text": str, "due": str})
    async def task_add(args: dict) -> dict:
        text = str(args.get("text", "")).strip()
        if not text:
            return _text("task_add failed: no task text.")
        try:
            body = {"text": text, "group": _GROUP, "source": "jarvis"}
            if args.get("due"):
                body["due"] = str(args["due"])
            out = await asyncio.to_thread(_req, "POST", "/task", body)
            emit("task_added", id=out.get("id", ""), text=text,
                 due=body.get("due", ""))
            await asyncio.to_thread(refresh_panel)
            return _text(f"Added: {text}"
                         + (f" (reminder set for {body['due']})"
                            if body.get("due") else ""))
        except Exception as e:  # noqa: BLE001
            return _text(f"task_add failed: {str(e)[:150]}")

    @tool("task_list",
          "List Ahmed's open tasks and reminders (also refreshes the HUD "
          "panel). Use for 'what are my tasks', 'what do I have to do'.", {})
    async def task_list(args: dict) -> dict:
        try:
            tasks = await asyncio.to_thread(fetch_tasks)
            emit("tasks", tasks=tasks)
            if not tasks:
                return _text("No open tasks.")
            lines = []
            for t in tasks:
                due = f"  (due {t['due'][:16].replace('T',' ')})" \
                    if t.get("due") else ""
                lines.append(f"- {t['text']}{due}  [id {t['id']}]")
            return _text("\n".join(lines))
        except Exception as e:  # noqa: BLE001
            return _text(f"task_list failed: {str(e)[:150]}")

    @tool("task_done",
          "Mark a task/reminder complete by its id (from task_list). Ahmed can "
          "also tick it on the HUD himself.", {"id": str})
    async def task_done(args: dict) -> dict:
        tid = str(args.get("id", "")).strip()
        if not tid:
            return _text("task_done failed: no task id.")
        try:
            await asyncio.to_thread(mark_done, tid)
            return _text("Marked done.")
        except Exception as e:  # noqa: BLE001
            return _text(f"task_done failed: {str(e)[:150]}")

    return create_sdk_mcp_server(
        name="tasks", version="1.0.0",
        tools=[task_add, task_list, task_done],
    )
