#!/usr/bin/env python3
"""Linear sync hook for Trellis task lifecycle.

Syncs task events to Linear via the `linearis` CLI.

Usage (called automatically by task.py hooks):
    python3 .trellis/scripts/hooks/linear_sync.py create
    python3 .trellis/scripts/hooks/linear_sync.py start
    python3 .trellis/scripts/hooks/linear_sync.py archive

Manual usage:
    TASK_JSON_PATH=.trellis/tasks/<name>/task.json python3 .trellis/scripts/hooks/linear_sync.py sync

Environment:
    TASK_JSON_PATH  - Absolute path to task.json (set by task.py)

Configuration:
    .trellis/hooks.local.json  - Local config (gitignored), example:
    {
      "linear": {
        "team": "TEAM_KEY",
        "project": "Project Name",
        "assignees": {
          "dev-name": "linear-user-id"
        }
      }
    }
"""

from __future__ import annotations

import json
import os
import subprocess
import sys
from pathlib import Path

# ─── Configuration ────────────────────────────────────────────────────────────

# Trellis priority → Linear priority (1=Urgent, 2=High, 3=Medium, 4=Low)
PRIORITY_MAP = {"P0": 1, "P1": 2, "P2": 3, "P3": 4}

# Linear status names (must match your team's workflow)
STATUS_IN_PROGRESS = "In Progress"
STATUS_DONE = "Done"


def _load_config() -> dict:
    """Load local hook config from .trellis/hooks.local.json."""
    task_json_path = os.environ.get("TASK_JSON_PATH", "")
    if task_json_path:
        # Walk up from task.json to find .trellis/
        trellis_dir = Path(task_json_path).parent.parent.parent
    else:
        trellis_dir = Path(".trellis")

    config_path = trellis_dir / "hooks.local.json"
    try:
        with open(config_path, encoding="utf-8") as f:
            return json.load(f)
    except (OSError, json.JSONDecodeError):
        return {}


CONFIG = _load_config()
LINEAR_CFG = CONFIG.get("linear", {})

TEAM = LINEAR_CFG.get("team", "")
PROJECT = LINEAR_CFG.get("project", "")
ASSIGNEE_MAP = LINEAR_CFG.get("assignees", {})

# ─── Helpers ──────────────────────────────────────────────────────────────────


def _read_task() -> tuple[dict, str]:
    path = os.environ.get("TASK_JSON_PATH", "")
    if not path:
        print("TASK_JSON_PATH not set", file=sys.stderr)
        sys.exit(1)
    with open(path, encoding="utf-8") as f:
        return json.load(f), path


def _write_task(data: dict, path: str) -> None:
    with open(path, "w", encoding="utf-8") as f:
        json.dump(data, f, indent=2, ensure_ascii=False)
        f.write("\n")


def _linearis(*args: str) -> dict | None:
    result = subprocess.run(
        ["linearis", *args],
        capture_output=True,
        text=True,
        encoding="utf-8",
        errors="replace",
    )
    if result.returncode != 0:
        print(f"linearis error: {result.stderr.strip()}", file=sys.stderr)
        sys.exit(1)
    stdout = result.stdout.strip()
    if stdout:
        return json.loads(stdout)
    return None


def _get_linear_issue(task: dict) -> str | None:
    meta = task.get("meta")
    if isinstance(meta, dict):
        return meta.get("linear_issue")
    return None


# ─── Actions ──────────────────────────────────────────────────────────────────


def cmd_create() -> None:
    if not TEAM:
        print("No linear.team configured in hooks.local.json", file=sys.stderr)
        sys.exit(1)

    task, path = _read_task()

    # Skip if already linked
    if _get_linear_issue(task):
        print(f"Already linked: {_get_linear_issue(task)}")
        return

    title = task.get("title") or task.get("name") or "Untitled"
    args = ["issues", "create", title, "--team", TEAM]

    # Map priority
    priority = PRIORITY_MAP.get(task.get("priority", ""), 0)
    if priority:
        args.extend(["-p", str(priority)])

    # Set project
    if PROJECT:
        args.extend(["--project", PROJECT])

    # Assign to Linear user
    assignee = task.get("assignee", "")
    linear_user_id = ASSIGNEE_MAP.get(assignee)
    if linear_user_id:
        args.extend(["--assignee", linear_user_id])

    # Link to parent's Linear issue if available
    parent_issue = _resolve_parent_linear_issue(task)
    if parent_issue:
        args.extend(["--parent-ticket", parent_issue])

    result = _linearis(*args)
    if result and "identifier" in result:
        if not isinstance(task.get("meta"), dict):
            task["meta"] = {}
        task["meta"]["linear_issue"] = result["identifier"]
        _write_task(task, path)
        print(f"Created Linear issue: {result['identifier']}")


def cmd_start() -> None:
    task, _ = _read_task()
    issue = _get_linear_issue(task)
    if not issue:
        return
    _linearis("issues", "update", issue, "-s", STATUS_IN_PROGRESS)
    print(f"Updated {issue} -> {STATUS_IN_PROGRESS}")
    cmd_sync()


def cmd_archive() -> None:
    task, _ = _read_task()
    issue = _get_linear_issue(task)
    if not issue:
        return
    _linearis("issues", "update", issue, "-s", STATUS_DONE)
    print(f"Updated {issue} -> {STATUS_DONE}")


def cmd_sync() -> None:
    """Sync prd.md content to Linear issue description."""
    task, _ = _read_task()
    issue = _get_linear_issue(task)
    if not issue:
        print("No linear_issue in meta, run create first", file=sys.stderr)
        sys.exit(1)

    # Find prd.md next to task.json
    task_json_path = os.environ.get("TASK_JSON_PATH", "")
    prd_path = Path(task_json_path).parent / "prd.md"
    if not prd_path.is_file():
        print(f"No prd.md found at {prd_path}", file=sys.stderr)
        sys.exit(1)

    description = prd_path.read_text(encoding="utf-8").strip()
    _linearis("issues", "update", issue, "-d", description)
    print(f"Synced prd.md to {issue} description")


# ─── Parent Issue Resolution ─────────────────────────────────────────────────


def _resolve_parent_linear_issue(task: dict) -> str | None:
    """Find parent task's Linear issue identifier."""
    parent_name = task.get("parent")
    if not parent_name:
        return None

    task_json_path = os.environ.get("TASK_JSON_PATH", "")
    if not task_json_path:
        return None

    current_task_dir = Path(task_json_path).parent
    tasks_dir = current_task_dir.parent
    parent_json = tasks_dir / parent_name / "task.json"

    if parent_json.exists():
        try:
            with open(parent_json, encoding="utf-8") as f:
                parent_task = json.load(f)
            return _get_linear_issue(parent_task)
        except (json.JSONDecodeError, OSError):
            pass
    return None


# ─── Main ─────────────────────────────────────────────────────────────────────

if __name__ == "__main__":
    action = sys.argv[1] if len(sys.argv) > 1 else ""
    actions = {
        "create": cmd_create,
        "start": cmd_start,
        "archive": cmd_archive,
        "sync": cmd_sync,
    }
    fn = actions.get(action)
    if fn:
        fn()
    else:
        print(f"Unknown action: {action}", file=sys.stderr)
        print(f"Valid actions: {', '.join(actions)}", file=sys.stderr)
        sys.exit(1)
