"""Linear API sync helpers for task workflow.

Pure Python, no LLM calls. Uses urllib.request (no external deps).
"""

import json
import os
import re
import urllib.request
from pathlib import Path

from mixersystem.data.repository import load_settings, update_session_json

LINEAR_API_URL = "https://api.linear.app/graphql"


def _api_key() -> str:
    """Get Linear API key from arg/env."""
    key = os.environ.get("LINEAR_API_KEY", "")
    if not key:
        raise RuntimeError("LINEAR_API_KEY environment variable is not set")
    return key


def _graphql(query: str, variables: dict, api_key: str) -> dict:
    """Execute a GraphQL request against the Linear API."""
    payload = json.dumps({"query": query, "variables": variables}).encode()
    req = urllib.request.Request(
        LINEAR_API_URL,
        data=payload,
        headers={
            "Content-Type": "application/json",
            "Authorization": api_key,
        },
        method="POST",
    )
    with urllib.request.urlopen(req) as resp:
        data = json.loads(resp.read().decode())
    if "errors" in data:
        raise RuntimeError(f"Linear API error: {data['errors']}")
    return data["data"]


def _title_to_slug(title: str) -> str:
    """Convert a title to a kebab-case slug, max 4 words."""
    words = re.sub(r"[^a-zA-Z0-9\s]", "", title).lower().split()
    slug = "-".join(words[:4])
    return slug or "new-task"


def _team_id_from_settings() -> str:
    """Resolve Linear team id from settings.json."""
    settings = load_settings()
    linear = settings.get("linear", {}) if isinstance(settings.get("linear"), dict) else {}
    candidates = [
        os.environ.get("LINEAR_TEAM_ID"),
        linear.get("team_id"),
        linear.get("default_team_id"),
        settings.get("linear_team_id"),
        settings.get("team_id"),
    ]
    for value in candidates:
        if isinstance(value, str) and value.strip():
            return value.strip()
    raise RuntimeError(
        "team_id is required. Pass --team_id or set it in settings.json "
        "(linear.team_id / linear.default_team_id / linear_team_id / team_id)."
    )


def _resolve_team_id(team_id: str | None) -> str:
    return team_id if team_id else _team_id_from_settings()


def _task_title_and_description(task_path: str) -> tuple[str, str]:
    text = Path(task_path).read_text()
    title_match = re.search(r"^# (.+)$", text, re.MULTILINE)
    title = title_match.group(1).strip() if title_match else "Untitled Task"
    body = text.strip()

    # Prefer explicit Description section content if present.
    desc_match = re.search(
        r"^##\s+Description\s*$\n+(.*?)(?=\n^##\s+|\Z)",
        body,
        flags=re.MULTILINE | re.DOTALL,
    )
    if desc_match:
        description = desc_match.group(1).strip()
    else:
        description = body

    if not description:
        description = Path(task_path).read_text().strip()
    return title, description


# --- Load ---

LOAD_QUERY = """\
query GetIssue($id: String!) {
  issue(id: $id) {
    id
    identifier
    title
    description
    state { name }
    assignee { id name }
    cycle { id name }
  }
}"""


def load(issue_id: str, task_path: str, session_folder: str | None = None,
         api_key: str = None) -> str:
    """Fetch a Linear issue and write it as task.md. Returns the task_path written."""
    api_key = api_key or _api_key()
    data = _graphql(LOAD_QUERY, {"id": issue_id}, api_key)
    issue = data["issue"]

    title = issue.get("title", "Untitled")
    description = issue.get("description", "")
    status = issue.get("state", {}).get("name", "")
    assignee = issue.get("assignee")
    cycle = issue.get("cycle")
    identifier = issue.get("identifier") or issue_id

    # Write clean markdown body (no frontmatter)
    lines = [f"# {title}", ""]
    lines += ["## Description", "", description or "(no description)", ""]

    Path(task_path).parent.mkdir(parents=True, exist_ok=True)
    Path(task_path).write_text("\n".join(lines))

    # Write metadata to session.json
    sf = session_folder or str(Path(task_path).parent)
    linear_meta = {
        "id": identifier,
        "name": _title_to_slug(title),
    }
    if assignee:
        linear_meta["assignee"] = f"{assignee['id']} ({assignee['name']})"
    if cycle:
        linear_meta["cycle"] = f"{cycle['id']} ({cycle['name']})"
    if status:
        linear_meta["status"] = status

    update_session_json(sf, modules=["all"], modules_locked=True, linear=linear_meta)
    return task_path


# --- Push / Update ---

ISSUE_NODE_QUERY = """\
query GetIssueNode($id: String!) {
  issue(id: $id) {
    id
    identifier
    url
  }
}"""

PUSH_MUTATION = """\
mutation CreateIssue($input: IssueCreateInput!) {
  issueCreate(input: $input) {
    success
    issue { id identifier url }
  }
}"""

UPDATE_MUTATION = """\
mutation UpdateIssue($id: String!, $input: IssueUpdateInput!) {
  issueUpdate(id: $id, input: $input) {
    success
    issue { id identifier url }
  }
}"""

STATE_QUERY = """\
query GetStates($teamId: String!) {
  team(id: $teamId) {
    states { nodes { id name } }
  }
}"""


def _resolve_state_id(team_id: str, status: str, api_key: str) -> str | None:
    data = _graphql(STATE_QUERY, {"teamId": team_id}, api_key)
    states = data["team"]["states"]["nodes"]
    for state in states:
        if state["name"].lower() == status.lower():
            return state["id"]
    return None


def push(task_path: str, api_key: str = None, team_id: str = None,
         assignee: str = None, cycle: str = None,
         status: str = "Todo") -> str:
    """Read task.md and create a Linear issue. Returns issue identifier (e.g. TEAM-123)."""
    api_key = api_key or _api_key()
    team_id = _resolve_team_id(team_id)

    title, description = _task_title_and_description(task_path)

    input_data = {
        "teamId": team_id,
        "title": title,
        "description": description,
    }

    state_id = _resolve_state_id(team_id, status, api_key)
    if state_id:
        input_data["stateId"] = state_id

    if assignee:
        input_data["assigneeId"] = assignee
    if cycle:
        input_data["cycleId"] = cycle

    data = _graphql(PUSH_MUTATION, {"input": input_data}, api_key)
    result = data["issueCreate"]
    if not result.get("success"):
        raise RuntimeError(f"Failed to create Linear issue: {result}")

    issue = result["issue"]
    issue_id = issue["identifier"]
    print(f"[task] pushed to Linear: {issue_id} ({issue.get('url', '')})")
    return issue_id


def update(issue_id: str, task_path: str, api_key: str = None) -> str:
    """Update an existing Linear issue from task.md. Returns issue identifier."""
    api_key = api_key or _api_key()

    node_data = _graphql(ISSUE_NODE_QUERY, {"id": issue_id}, api_key)
    issue_node = node_data["issue"]
    if not issue_node:
        raise RuntimeError(f"Could not resolve Linear issue: {issue_id}")

    internal_id = issue_node["id"]
    title, description = _task_title_and_description(task_path)
    input_data = {
        "title": title,
        "description": description,
    }

    data = _graphql(UPDATE_MUTATION, {"id": internal_id, "input": input_data}, api_key)
    result = data["issueUpdate"]
    if not result.get("success"):
        raise RuntimeError(f"Failed to update Linear issue: {result}")

    issue = result["issue"]
    identifier = issue["identifier"]
    print(f"[task] updated in Linear: {identifier} ({issue.get('url', '')})")
    return identifier
