"""Fetch PR review comments for agent context injection.

This exec command fetches unresolved (or all) PR review comments from GitHub
and outputs them as JSON for agent processing.

Usage:
    erk exec get-pr-review-comments
    erk exec get-pr-review-comments --pr 123
    erk exec get-pr-review-comments --include-resolved

Output:
    JSON with success status, PR info, and review threads

Exit Codes:
    0: Success (or graceful error with JSON output)
    1: Context not initialized

Examples:
    $ erk exec get-pr-review-comments
    {"success": true, "pr_number": 123, "threads": [...]}

    $ erk exec get-pr-review-comments --pr 456
    {"success": true, "pr_number": 456, "threads": [...]}
"""

from __future__ import annotations

import json
from dataclasses import asdict, dataclass
from typing import TypedDict

import click

from erk.cli.script_output import exit_with_error
from erk_shared.context.helpers import get_current_branch, require_github, require_repo_root
from erk_shared.gateway.github.types import PRDetails, PRNotFound, PRReviewThread


class ReviewCommentDict(TypedDict):
    """Typed dict for a single review comment in JSON output."""

    author: str
    body: str
    created_at: str


class ReviewThreadDict(TypedDict):
    """Typed dict for a review thread in JSON output."""

    id: str
    path: str
    line: int | None
    is_outdated: bool
    comments: list[ReviewCommentDict]


@dataclass(frozen=True)
class ReviewCommentSuccess:
    """Success response for PR review comments."""

    success: bool
    pr_number: int
    pr_url: str
    pr_title: str
    threads: list[ReviewThreadDict]


def _ensure_branch(branch: str | None) -> str:
    """Ensure branch was detected, exit with error if not."""
    if branch is None:
        exit_with_error("branch-detection-failed", "Could not determine current branch")
    assert branch is not None  # Type narrowing after NoReturn
    return branch


def _ensure_pr_result(
    pr_result: PRDetails | PRNotFound,
    *,
    branch: str | None,
    pr_number: int | None,
) -> PRDetails:
    """Ensure PR lookup succeeded, exit with appropriate error if not."""
    if isinstance(pr_result, PRNotFound):
        if branch is not None:
            exit_with_error("no-pr-for-branch", f"No PR found for branch '{branch}'")
        else:
            exit_with_error("pr-not-found", f"PR #{pr_number} not found")
    assert not isinstance(pr_result, PRNotFound)  # Type narrowing after NoReturn
    return pr_result


def _format_thread_for_json(thread: PRReviewThread) -> ReviewThreadDict:
    """Format a PRReviewThread for JSON output."""
    comments: list[ReviewCommentDict] = []
    for comment in thread.comments:
        comments.append(
            {
                "author": comment.author,
                "body": comment.body,
                "created_at": comment.created_at,
            }
        )

    return {
        "id": thread.id,
        "path": thread.path,
        "line": thread.line,
        "is_outdated": thread.is_outdated,
        "comments": comments,
    }


@click.command(name="get-pr-review-comments")
@click.option("--pr", type=int, default=None, help="PR number (defaults to current branch's PR)")
@click.option("--include-resolved", is_flag=True, help="Include resolved threads")
@click.pass_context
def get_pr_review_comments(ctx: click.Context, pr: int | None, include_resolved: bool) -> None:
    """Fetch PR review comments for agent context injection.

    Queries GitHub for review threads on a pull request and outputs
    structured JSON for agent processing. By default, excludes resolved
    threads.

    If --pr is not specified, finds the PR for the current branch.
    """
    # Get dependencies from context
    repo_root = require_repo_root(ctx)
    github = require_github(ctx)

    # Get PR details - either from current branch or specified PR number
    if pr is None:
        branch = _ensure_branch(get_current_branch(ctx))
        pr_result = _ensure_pr_result(
            github.get_pr_for_branch(repo_root, branch), branch=branch, pr_number=None
        )
    else:
        pr_result = _ensure_pr_result(github.get_pr(repo_root, pr), branch=None, pr_number=pr)

    # Fetch review threads
    try:
        threads = github.get_pr_review_threads(
            repo_root, pr_result.number, include_resolved=include_resolved
        )
    except RuntimeError as e:
        exit_with_error("github-api-failed", str(e))

    # Filter out threads with invalid IDs (null from GraphQL becomes empty string in gateway)
    valid_threads = [t for t in threads if t.id]

    result_success = ReviewCommentSuccess(
        success=True,
        pr_number=pr_result.number,
        pr_url=pr_result.url,
        pr_title=pr_result.title,
        threads=[_format_thread_for_json(t) for t in valid_threads],
    )
    click.echo(json.dumps(asdict(result_success), indent=2))
    raise SystemExit(0)
