"""Context-recovery helpers — read-only list queries (#58, #83)."""
from __future__ import annotations

from .core import GitHubError, _graphql


def list_thread_comments(
    token: str,
    thread_id: str,
    thread_type: str,
    limit: int = 20,
) -> list[dict]:
    """Return comments on an Issue or Discussion with author info.

    Each entry: {"comment_id": str, "author": str,
                 "created_at": str, "body": str}

    Ordered oldest → newest.
    """
    if not thread_id.strip():
        raise ValueError("thread_id must not be empty.")
    if thread_type not in ("issue", "discussion"):
        raise ValueError(
            f"thread_type must be 'issue' or 'discussion', got: {thread_type!r}"
        )
    if limit < 1 or limit > 100:
        raise ValueError("limit must be between 1 and 100.")

    fragment = "Discussion" if thread_type == "discussion" else "Issue"
    query = (
        "query($id: ID!, $last: Int!) {\n"
        "  node(id: $id) {\n"
        f"    ... on {fragment} {{\n"
        "      comments(last: $last) {\n"
        "        nodes { id body createdAt author { login } }\n"
        "      }\n"
        "    }\n"
        "  }\n"
        "}\n"
    )
    data = _graphql(token, query, {"id": thread_id, "last": limit})
    node = data.get("node")
    if not node:
        raise GitHubError(
            f"{fragment} {thread_id} not found."
        )
    nodes = (node.get("comments") or {}).get("nodes") or []
    return [
        {
            "comment_id": n["id"],
            "author": (n.get("author") or {}).get("login", ""),
            "created_at": n["createdAt"],
            "body": n["body"],
        }
        for n in nodes
    ]


def list_open_issues(
    token: str,
    owner: str,
    repo: str,
    limit: int = 100,
) -> list[dict]:
    """Return open Issues in the repo ordered by updated_at descending.

    Each entry: {"number": int, "title": str, "url": str,
                 "updated_at": str, "labels": list[str]}
    """
    if not owner.strip() or not repo.strip():
        raise ValueError("owner and repo must not be empty.")
    if limit < 1 or limit > 100:
        raise ValueError("limit must be between 1 and 100.")

    query = """
    query($owner: String!, $repo: String!, $limit: Int!) {
      repository(owner: $owner, name: $repo) {
        issues(
          states: [OPEN],
          first: $limit,
          orderBy: {field: UPDATED_AT, direction: DESC}
        ) {
          nodes {
            number
            title
            url
            updatedAt
            labels(first: 20) { nodes { name } }
          }
        }
      }
    }
    """
    data = _graphql(
        token, query, {"owner": owner, "repo": repo, "limit": limit}
    )
    repo_node = data.get("repository") or {}
    issues = (repo_node.get("issues") or {}).get("nodes") or []
    return [
        {
            "number": n["number"],
            "title": n["title"],
            "url": n["url"],
            "updated_at": n["updatedAt"],
            "labels": [
                lb["name"] for lb in (n.get("labels") or {}).get("nodes") or []
            ],
        }
        for n in issues
    ]


def list_recent_discussions(
    token: str,
    owner: str,
    repo: str,
    since_days: int = 7,
    limit: int = 50,
) -> list[dict]:
    """Return Discussions updated within the last `since_days` days.

    Each entry: {"number": int, "title": str, "url": str,
                 "updated_at": str, "category": str}
    """
    if not owner.strip() or not repo.strip():
        raise ValueError("owner and repo must not be empty.")
    if since_days < 1:
        raise ValueError("since_days must be positive.")
    if limit < 1 or limit > 100:
        raise ValueError("limit must be between 1 and 100.")

    query = """
    query($owner: String!, $repo: String!, $limit: Int!) {
      repository(owner: $owner, name: $repo) {
        discussions(
          first: $limit,
          orderBy: {field: UPDATED_AT, direction: DESC}
        ) {
          nodes {
            number
            title
            url
            updatedAt
            category { name }
          }
        }
      }
    }
    """
    data = _graphql(
        token, query, {"owner": owner, "repo": repo, "limit": limit}
    )
    repo_node = data.get("repository") or {}
    nodes = (repo_node.get("discussions") or {}).get("nodes") or []

    # Filter by since_days client-side
    from datetime import datetime, timedelta, timezone

    cutoff = datetime.now(timezone.utc) - timedelta(days=since_days)
    out: list[dict] = []
    for n in nodes:
        updated_at_str = n["updatedAt"]
        try:
            updated_at = datetime.fromisoformat(
                updated_at_str.replace("Z", "+00:00")
            )
        except ValueError:
            # Unparseable timestamps fall through (treated as recent).
            updated_at = datetime.now(timezone.utc)
        if updated_at < cutoff:
            # List is ordered DESC — anything older than cutoff means
            # the rest are older too.
            break
        out.append(
            {
                "number": n["number"],
                "title": n["title"],
                "url": n["url"],
                "updated_at": updated_at_str,
                "category": (n.get("category") or {}).get("name", ""),
            }
        )
    return out


def list_milestones_with_progress(
    token: str,
    owner: str,
    repo: str,
) -> list[dict]:
    """Return open Milestones with completion percentage.

    Each entry: {"title": str, "due_on": str | None,
                 "percent_complete": float}
    """
    if not owner.strip() or not repo.strip():
        raise ValueError("owner and repo must not be empty.")

    query = """
    query($owner: String!, $repo: String!) {
      repository(owner: $owner, name: $repo) {
        milestones(first: 20, states: [OPEN]) {
          nodes {
            title
            dueOn
            progressPercentage
          }
        }
      }
    }
    """
    data = _graphql(token, query, {"owner": owner, "repo": repo})
    repo_node = data.get("repository") or {}
    nodes = (repo_node.get("milestones") or {}).get("nodes") or []
    return [
        {
            "title": n["title"],
            "due_on": n.get("dueOn"),
            "percent_complete": float(n.get("progressPercentage") or 0.0),
        }
        for n in nodes
    ]


def get_milestone_by_title(
    token: str,
    owner: str,
    repo: str,
    title: str,
) -> dict | None:
    """Look up a milestone by exact-match title; return its state and open issues.

    Returns None when no milestone matches `title`. Otherwise:

    ``{"number": int, "title": str, "state": "OPEN"|"CLOSED",
       "open_issues": [{"number": int, "title": str, "url": str}, ...]}``

    Backing query for `validate_release`'s `milestone_closed` check (#107).
    """
    if not owner.strip() or not repo.strip():
        raise ValueError("owner and repo must not be empty.")
    if not title.strip():
        raise ValueError("title must not be empty.")

    query = """
    query($owner: String!, $repo: String!) {
      repository(owner: $owner, name: $repo) {
        milestones(first: 50, states: [OPEN, CLOSED]) {
          nodes {
            number
            title
            state
            issues(first: 100, states: [OPEN]) {
              nodes { number title url }
            }
          }
        }
      }
    }
    """
    data = _graphql(token, query, {"owner": owner, "repo": repo})
    repo_node = data.get("repository") or {}
    nodes = (repo_node.get("milestones") or {}).get("nodes") or []
    for n in nodes:
        if n["title"] == title:
            open_issues = [
                {"number": i["number"], "title": i["title"], "url": i["url"]}
                for i in (n.get("issues") or {}).get("nodes") or []
            ]
            return {
                "number": n["number"],
                "title": n["title"],
                "state": n["state"],
                "open_issues": open_issues,
            }
    return None
