"""Command-line interface for glreview."""

from __future__ import annotations

import sys
from datetime import datetime, timezone
from pathlib import Path
from importlib.resources import files

import click
from jinja2 import Environment, FileSystemLoader, BaseLoader

from . import __version__
from .analyze import analyze_module
from .config import Config, load_config
from .discovery import discover_modules, sync_registry
from .git import (
    count_lines,
    get_current_commit,
    get_gitlab_host,
    get_gitlab_project,
    has_changes_since,
)
from .gitlab import (
    add_issue_comment,
    close_issue,
    create_issue,
    get_compare_url,
    get_issue,
    get_project_members,
    gitlab_available,
)
from .registry import ModuleStatus, Registry, load_registry, save_registry
from .claude import (
    build_review_prompt,
    chunk_source_code,
    claude_available,
    get_file_diff,
    run_claude_review,
)


@click.group()
@click.version_option(version=__version__)
@click.pass_context
def main(ctx: click.Context) -> None:
    """glreview - GitLab code review tracking for scientific software."""
    ctx.ensure_object(dict)
    ctx.obj["config"] = load_config()


def _load_issue_template(config) -> str:
    """Load the issue template content.

    Uses custom template from config.issue_template if specified,
    otherwise falls back to the default bundled template.

    Args:
        config: Config object with optional issue_template path.

    Returns:
        Template content as string.
    """
    if config.issue_template:
        custom_path = config.root / config.issue_template
        if custom_path.exists():
            return custom_path.read_text()
        else:
            click.secho(f"Warning: Custom template not found: {custom_path}", fg="yellow")
            click.echo("Falling back to default template.")

    # Load default template from package
    template_file = files("glreview.templates").joinpath("issue.md")
    return template_file.read_text()


def _render_issue_description(
    config,
    path: str,
    module,
    current_commit: str,
    diff_url: str | None = None,
) -> str:
    """Render the issue description from template.

    Args:
        config: Config object.
        path: Module path.
        module: ModuleStatus object.
        current_commit: Current git commit SHA.
        diff_url: URL to diff (for re-reviews).

    Returns:
        Rendered issue description.
    """
    template_content = _load_issue_template(config)

    env = Environment(loader=BaseLoader())
    template = env.from_string(template_content)

    # Analyze module structure
    summary = analyze_module(path, cwd=config.root)
    module_contents = summary.format_summary()

    context = {
        "path": path,
        "lines": module.lines,
        "priority": module.priority,
        "reviewers_required": module.reviewers_required,
        "current_commit": current_commit,
        "previous_commit": module.last_reviewed_commit if module.status == "reviewed" else None,
        "previous_date": module.last_reviewed_date or "unknown",
        "previous_reviewers": ", ".join(module.reviewers) or "none",
        "diff_url": diff_url or "",
        "module_contents": module_contents,
        "classes": summary.public_classes,
        "functions": summary.public_functions,
    }

    return template.render(**context)


def _print_pyproject_suggestion(sources: str) -> None:
    """Print suggested pyproject.toml configuration."""
    click.echo()
    click.echo("Add to pyproject.toml:")
    click.echo()
    click.echo("  [tool.glreview]")
    click.echo(f'  sources = ["{sources}"]')
    click.echo('  exclude = ["**/_version.py", "**/test_*.py"]')
    click.echo()
    click.echo("  # Optional: custom issue template (Jinja2 format)")
    click.echo('  # issue_template = ".glreview/issue_template.md"')
    click.echo()
    click.echo("  # Optional: priority rules (first match wins)")
    click.echo("  # [[tool.glreview.priority]]")
    click.echo('  # pattern = "src/**/core.py"')
    click.echo('  # level = "critical"')
    click.echo("  # reviewers_required = 2")


@main.command()
@click.option(
    "--sources",
    default="src/**/*.py",
    help="Glob pattern for source files",
)
@click.pass_context
def init(ctx: click.Context, sources: str) -> None:
    """Initialize glreview in a project.

    This command is idempotent: if a registry already exists, it behaves
    like 'glreview sync' - adding new modules and removing deleted ones
    while preserving review status for existing modules.
    """
    config = ctx.obj["config"]

    # Idempotent: if registry exists, sync instead of failing
    if config.registry_path.exists():
        click.echo(f"Registry exists at {config.registry}, syncing...")
        ctx.invoke(sync)
        _print_pyproject_suggestion(sources)
        return

    # Discover modules
    patterns = [sources]
    modules = discover_modules(config.root, patterns, config.exclude)

    if not modules:
        click.echo(f"No modules found matching pattern: {sources}")
        click.echo("Check your source pattern or create some files first.")
        return

    # Create registry
    registry = Registry()

    for path in modules:
        level, reviewers_required = config.get_priority(path)
        lines = count_lines(path, cwd=config.root)

        registry.set(
            ModuleStatus(
                path=path,
                status="needs_review",
                priority=level,
                lines=lines,
                reviewers_required=reviewers_required,
            )
        )

    # Save
    commit = get_current_commit(cwd=config.root)
    save_registry(registry, config.registry_path, commit=commit)

    click.echo(f"Initialized glreview with {len(modules)} modules")
    click.echo(f"Registry: {config.registry}")
    _print_pyproject_suggestion(sources)
    click.echo()
    click.echo("Next steps:")
    click.echo("  1. Commit review_registry.json")
    click.echo("  2. Run 'glreview status' to see review coverage")
    click.echo("  3. Run 'glreview ci-config' for GitLab CI setup")


@main.command()
@click.argument("path", required=False)
@click.option("--json", "as_json", is_flag=True, help="Output as JSON")
@click.pass_context
def status(ctx: click.Context, path: str | None, as_json: bool) -> None:
    """Check review status."""
    config = ctx.obj["config"]
    registry = load_registry(config.registry_path)

    if not registry.modules:
        click.echo("No modules in registry. Run 'glreview init' first.")
        return

    if path:
        # Single module status
        module = registry.get(path)
        if not module:
            click.echo(f"Error: {path} not in registry.")
            sys.exit(1)

        if as_json:
            import json
            click.echo(json.dumps(module.to_dict(), indent=2))
            return

        click.echo(f"Module: {path}")
        click.echo(f"  Status: {module.status}")
        click.echo(f"  Priority: {module.priority}")
        click.echo(f"  Lines: {module.lines}")

        if module.status == "reviewed":
            click.echo(f"  Last reviewed: {module.last_reviewed_date}")
            click.echo(f"  Commit: {module.last_reviewed_commit}")
            click.echo(f"  Reviewers: {', '.join(module.reviewers)}")

            if has_changes_since(path, module.last_reviewed_commit, cwd=config.root):
                click.secho("  ⚠ Changes detected since last review!", fg="yellow")
            else:
                click.secho("  ✓ No changes since last review", fg="green")

        elif module.status == "in_progress":
            click.echo(f"  Issue: {module.review_issue or 'unknown'}")
            click.echo(f"  Assigned: {module.assigned_reviewer or 'unassigned'}")

    else:
        # Summary status
        summary = registry.summary

        if as_json:
            import json
            click.echo(json.dumps(summary, indent=2))
            return

        total = summary["total"]
        reviewed = summary["reviewed"]
        in_progress = summary["in_progress"]
        needs_review_count = summary["needs_review"]
        pct = (reviewed / total * 100) if total > 0 else 0
        line_pct = (summary["reviewed_lines"] / summary["total_lines"] * 100) if summary["total_lines"] > 0 else 0

        # Header with progress bar
        bar_width = 20
        filled = int(bar_width * pct / 100)
        bar = "█" * filled + "░" * (bar_width - filled)

        click.echo()
        click.echo(f"  Review Progress: [{bar}] {pct:.0f}%")
        click.echo()
        click.echo(f"  Modules: {reviewed}/{total} reviewed")
        click.echo(f"  Lines:   {summary['reviewed_lines']:,}/{summary['total_lines']:,} ({line_pct:.0f}%)")
        click.echo()

        # In Progress section
        in_progress_mods = list(registry.filter(status="in_progress"))
        if in_progress_mods:
            click.secho(f"◐ In Progress ({len(in_progress_mods)})", fg="cyan", bold=True)
            for mod in sorted(in_progress_mods, key=lambda m: m.path):
                assignee = mod.assigned_reviewer or "unassigned"
                issue = mod.review_issue or ""
                # Shorten issue URL for display
                if "/" in issue:
                    issue = "#" + issue.rstrip("/").split("/")[-1]
                click.echo(f"    {mod.path}")
                click.echo(f"      Assignee: {assignee}  Issue: {issue}")
                if mod.review_started_commit:
                    click.echo(f"      Started at: {mod.review_started_commit}")
            click.echo()

        # Needs Review section
        needs_review = list(registry.filter(status="needs_review"))
        needs_review.extend(registry.filter(status="needs_update"))

        if needs_review:
            click.secho(f"○ Needs Review ({len(needs_review)})", fg="yellow", bold=True)
            for mod in sorted(needs_review, key=lambda m: (
                {"critical": 0, "high": 1, "medium": 2, "low": 3}.get(m.priority, 99),
                m.path
            )):
                priority_colors = {"critical": "red", "high": "yellow", "medium": None, "low": "bright_black"}
                color = priority_colors.get(mod.priority)
                priority_str = f"[{mod.priority}]"
                if color:
                    priority_str = click.style(priority_str, fg=color)
                click.echo(f"    {priority_str} {mod.path} ({mod.lines} lines)")
            click.echo()

        # Reviewed section (show if any have changes)
        reviewed_mods = list(registry.filter(status="reviewed"))
        stale_mods = []
        for mod in reviewed_mods:
            if has_changes_since(mod.path, mod.last_reviewed_commit, cwd=config.root):
                stale_mods.append(mod)

        if stale_mods:
            click.secho(f"⚠ Reviewed but Changed ({len(stale_mods)})", fg="red", bold=True)
            for mod in sorted(stale_mods, key=lambda m: m.path):
                click.echo(f"    {mod.path}")
                click.echo(f"      Last reviewed: {mod.last_reviewed_date} ({mod.last_reviewed_commit})")
            click.echo()
        elif reviewed_mods:
            click.secho(f"✓ Reviewed ({len(reviewed_mods)})", fg="green", bold=True)
            click.echo(f"    All {len(reviewed_mods)} reviewed modules are up to date.")
            click.echo()


@main.command("list")
@click.option("--status", "filter_status", help="Filter by status")
@click.option("--priority", "filter_priority", help="Filter by priority")
@click.pass_context
def list_modules(
    ctx: click.Context,
    filter_status: str | None,
    filter_priority: str | None,
) -> None:
    """List modules in the registry."""
    config = ctx.obj["config"]
    registry = load_registry(config.registry_path)

    modules = list(registry.filter(status=filter_status, priority=filter_priority))

    if not modules:
        click.echo("No modules match the filters.")
        return

    for mod in sorted(modules, key=lambda m: m.path):
        status_icon = {
            "reviewed": "✓",
            "in_progress": "◐",
            "needs_review": "○",
            "needs_update": "!",
        }.get(mod.status, "?")

        click.echo(f"{status_icon} [{mod.priority:8}] {mod.path}")


@main.command()
@click.argument("path")
@click.option("--assignee", "-a", help="Assign reviewer (@username)")
@click.option("--issue", "-i", help="Manual issue number (if glab unavailable)")
@click.pass_context
def start(ctx: click.Context, path: str, assignee: str | None, issue: str | None) -> None:
    """Start a review for a module."""
    config = ctx.obj["config"]
    registry = load_registry(config.registry_path)

    module = registry.get(path)
    if not module:
        click.echo(f"Error: {path} not in registry.")
        click.echo()
        click.echo("Available modules:")
        for m in sorted(registry.modules.keys())[:10]:
            click.echo(f"  {m}")
        if len(registry.modules) > 10:
            click.echo(f"  ... and {len(registry.modules) - 10} more")
        sys.exit(1)

    # Check if already in progress
    if module.status == "in_progress":
        click.echo(f"Review already in progress for {path}")
        if module.review_issue:
            click.echo(f"  Issue: {module.review_issue}")
        sys.exit(1)

    # Check if reviewed and no changes
    if module.status == "reviewed":
        if not has_changes_since(path, module.last_reviewed_commit, cwd=config.root):
            click.secho(f"✓ {path} is already reviewed at current commit", fg="green")
            click.echo("  No changes since last review.")
            sys.exit(0)
        else:
            click.echo(f"Changes detected since last review ({module.last_reviewed_commit})")

    current_commit = get_current_commit(cwd=config.root)
    module_name = Path(path).stem

    # Build issue description from template
    project = get_gitlab_project(cwd=config.root)
    host = get_gitlab_host(cwd=config.root) or "gitlab.com"

    diff_url = None
    if module.last_reviewed_commit and module.status == "reviewed":
        diff_url = get_compare_url(project, host, module.last_reviewed_commit, current_commit)

    description = _render_issue_description(
        config=config,
        path=path,
        module=module,
        current_commit=current_commit,
        diff_url=diff_url,
    )

    labels = config.issue_labels + [f"priority::{module.priority}"]

    if not gitlab_available(host):
        click.secho("Warning: GitLab authentication not configured.", fg="yellow")
        click.echo()
        click.echo("Set GITLAB_PRIVATE_TOKEN environment variable, or")
        click.echo("create the issue manually with:")
        click.echo(f"  Title: [REVIEW] {module_name}")
        click.echo(f"  Labels: {', '.join(labels)}")
        click.echo()

        if issue:
            module.status = "in_progress"
            module.review_issue = issue
            module.assigned_reviewer = assignee
            module.review_started_commit = current_commit
            save_registry(registry, config.registry_path, commit=current_commit)
            click.secho(f"✓ Registry updated with issue {issue}", fg="green")
        else:
            click.echo("Then run:")
            click.echo(f"  glreview start {path} --issue <number>")
            sys.exit(1)
        return

    # Create the issue
    click.echo(f"Creating review issue for {path}...")

    created = create_issue(
        title=f"[REVIEW] {module_name}",
        description=description,
        project_path=project,
        host=host,
        labels=labels,
        assignee=assignee,
    )

    if not created:
        click.secho("Failed to create issue.", fg="red")
        sys.exit(1)

    # Update registry
    module.status = "in_progress"
    module.review_issue = created.url or f"#{created.number}"
    module.assigned_reviewer = assignee
    module.review_started_commit = current_commit
    save_registry(registry, config.registry_path, commit=current_commit)

    click.echo()
    click.secho(f"✓ Review started for {path}", fg="green")
    click.echo(f"  Issue: {created.url or created.number}")
    click.echo(f"  Priority: {module.priority}")
    if assignee:
        click.echo(f"  Assignee: {assignee}")
    click.echo()
    click.echo("When review is complete, run:")
    click.echo(f"  glreview signoff {path}")


@main.command()
@click.argument("path")
@click.option("--reviewer", "-r", help="Reviewer (@username)")
@click.option("--skip-verify", is_flag=True, help="Skip issue verification")
@click.option("--force", "-f", is_flag=True, help="Force sign-off")
@click.option("--acknowledge", is_flag=True, help="Acknowledge changes made during review")
@click.pass_context
def signoff(
    ctx: click.Context,
    path: str,
    reviewer: str | None,
    skip_verify: bool,
    force: bool,
    acknowledge: bool,
) -> None:
    """Sign off on a completed review."""
    config = ctx.obj["config"]
    registry = load_registry(config.registry_path)

    module = registry.get(path)
    if not module:
        click.echo(f"Error: {path} not in registry.")
        sys.exit(1)

    if module.status == "reviewed" and not force:
        click.secho(f"✓ {path} is already reviewed.", fg="green")
        click.echo("  Use --force to re-sign-off.")
        sys.exit(0)

    if module.status != "in_progress" and not force:
        click.echo(f"Error: {path} is not in review (status: {module.status})")
        click.echo("Start a review first with:")
        click.echo(f"  glreview start {path}")
        sys.exit(1)

    # Check if file changed since review started
    if module.review_started_commit and not acknowledge:
        if has_changes_since(path, module.review_started_commit, cwd=config.root):
            project = get_gitlab_project(cwd=config.root)
            host = get_gitlab_host(cwd=config.root) or "gitlab.com"
            current = get_current_commit(cwd=config.root)
            diff_url = get_compare_url(project, host, module.review_started_commit, current)

            click.secho("⚠ File changed during review!", fg="yellow")
            click.echo()
            click.echo(f"  Review started at: {module.review_started_commit}")
            click.echo(f"  Current commit:    {current}")
            click.echo(f"  View changes: {diff_url}")
            click.echo()
            click.echo("Options:")
            click.echo("  --acknowledge  Sign off confirming you reviewed the changes")
            click.echo("  --force        Sign off without checks")
            sys.exit(1)

    # Extract issue number
    issue_number = None
    if module.review_issue:
        if module.review_issue.startswith("#"):
            issue_number = module.review_issue[1:]
        elif "/" in module.review_issue:
            issue_number = module.review_issue.rstrip("/").split("/")[-1]
        else:
            issue_number = module.review_issue

    # Verify issue is closed
    project = get_gitlab_project(cwd=config.root)
    host = get_gitlab_host(cwd=config.root)

    if issue_number and gitlab_available(host) and not skip_verify:
        click.echo(f"Checking issue #{issue_number}...")
        issue = get_issue(issue_number, project_path=project, host=host)

        if issue:
            if issue.state != "closed":
                click.secho(
                    f"Error: Issue #{issue_number} is not closed (state: {issue.state})",
                    fg="red",
                )
                click.echo()
                click.echo("Close the issue first, or use --skip-verify to bypass.")
                sys.exit(1)

            # Extract reviewer from issue
            if not reviewer and issue.assignees:
                reviewer = f"@{issue.assignees[0]}"

            click.secho(f"  State: {issue.state} ✓", fg="green")

    if not reviewer:
        click.secho("Error: Could not determine reviewer.", fg="red")
        click.echo("Specify with --reviewer @username")
        sys.exit(1)

    # Update registry
    current_commit = get_current_commit(cwd=config.root)

    module.status = "reviewed"
    module.last_reviewed_commit = current_commit
    module.last_reviewed_date = datetime.now(timezone.utc).strftime("%Y-%m-%d")

    # Add reviewer(s)
    new_reviewers = [r.strip() for r in reviewer.split(",")]
    existing = set(module.reviewers)
    module.reviewers = sorted(existing.union(new_reviewers))

    # Clear in-progress fields
    module.assigned_reviewer = None
    module.review_started_commit = None

    save_registry(registry, config.registry_path, commit=current_commit)

    click.echo()
    click.secho(f"✓ {path}", fg="green")
    click.echo(f"  Status: reviewed")
    click.echo(f"  Commit: {current_commit}")
    click.echo(f"  Reviewer: {reviewer}")
    if module.review_issue:
        click.echo(f"  Issue: {module.review_issue}")


@main.command()
@click.argument("path")
@click.option("--leave-open", is_flag=True, help="Leave GitLab issue open")
@click.option("--restart", is_flag=True, help="Restart review (update start commit)")
@click.option("--reason", "-m", help="Reason for cancellation")
@click.pass_context
def cancel(
    ctx: click.Context,
    path: str,
    leave_open: bool,
    restart: bool,
    reason: str | None,
) -> None:
    """Cancel or restart a review in progress."""
    config = ctx.obj["config"]
    registry = load_registry(config.registry_path)

    module = registry.get(path)
    if not module:
        click.echo(f"Error: {path} not in registry.")
        sys.exit(1)

    if module.status != "in_progress":
        click.echo(f"Error: {path} is not in progress (status: {module.status})")
        sys.exit(1)

    # Extract issue number
    issue_number = None
    issue_url = module.review_issue
    if issue_url:
        if issue_url.startswith("#"):
            issue_number = issue_url[1:]
        elif "/" in issue_url:
            issue_number = issue_url.rstrip("/").split("/")[-1]
        else:
            issue_number = issue_url

    project = get_gitlab_project(cwd=config.root)
    host = get_gitlab_host(cwd=config.root)
    current_commit = get_current_commit(cwd=config.root)

    if restart:
        # Restart: update start commit, add comment to issue
        module.review_started_commit = current_commit

        if issue_number and gitlab_available(host):
            comment = f"**Review restarted** at commit `{current_commit}`"
            if reason:
                comment += f"\n\nReason: {reason}"
            if add_issue_comment(issue_number, project, comment, host=host):
                click.echo(f"Added restart comment to issue #{issue_number}")
            else:
                click.secho("Warning: Could not add comment to issue.", fg="yellow")
        elif issue_number:
            click.secho(
                f"Warning: Could not update issue #{issue_number} (no API access).",
                fg="yellow",
            )
            click.echo("Issue may have stale information.")

        save_registry(registry, config.registry_path, commit=current_commit)

        click.echo()
        click.secho(f"✓ Review restarted for {path}", fg="green")
        click.echo(f"  New start commit: {current_commit}")
        if issue_url:
            click.echo(f"  Issue: {issue_url}")
        return

    # Cancel: restore previous state
    was_previously_reviewed = module.last_reviewed_commit is not None

    # Try to close the issue
    issue_closed = False
    if issue_number and not leave_open:
        if gitlab_available(host):
            comment = "**Review canceled**"
            if reason:
                comment += f"\n\nReason: {reason}"
            if was_previously_reviewed:
                comment += f"\n\nRestoring to previous review at `{module.last_reviewed_commit}`"

            if close_issue(issue_number, project, host=host, comment=comment):
                issue_closed = True
                click.echo(f"Closed issue #{issue_number}")
            else:
                click.secho(f"Warning: Could not close issue #{issue_number}.", fg="yellow")
        else:
            click.secho("Error: Cannot close issue (no GitLab API access).", fg="red")
            click.echo()
            click.echo("Options:")
            click.echo("  --leave-open  Proceed without closing issue")
            click.echo("  Set GITLAB_PRIVATE_TOKEN to enable API access")
            sys.exit(1)

    # Clear in-progress fields
    module.review_issue = None
    module.review_started_commit = None
    module.assigned_reviewer = None

    # Restore status
    if was_previously_reviewed:
        module.status = "reviewed"
    else:
        module.status = "needs_review"

    # Store reason in notes if provided
    if reason:
        module.notes = f"Canceled: {reason}"

    save_registry(registry, config.registry_path, commit=current_commit)

    click.echo()
    click.secho(f"✓ Review canceled for {path}", fg="green")
    if was_previously_reviewed:
        click.echo(f"  Status: restored to reviewed")
        click.echo(f"  Previous commit: {module.last_reviewed_commit}")
        click.echo(f"  Previous reviewers: {', '.join(module.reviewers) or 'none'}")
    else:
        click.echo(f"  Status: needs_review (no previous review)")

    if issue_url and not issue_closed:
        click.echo()
        click.secho(f"  Note: Issue {issue_url} left open - close manually", fg="yellow")


@main.command("claude-review")
@click.argument("path")
@click.option("--post", is_flag=True, help="Post findings to GitLab issue")
@click.option("--diff-only", is_flag=True, help="Only include diff in review (re-reviews)")
@click.option("--model", default="sonnet", help="Claude model to use")
@click.option("--dry-run", is_flag=True, help="Show prompt without running Claude")
@click.pass_context
def claude_review(
    ctx: click.Context,
    path: str,
    post: bool,
    diff_only: bool,
    model: str,
    dry_run: bool,
) -> None:
    """Run AI-assisted code review using Claude."""
    config = ctx.obj["config"]
    registry = load_registry(config.registry_path)

    module = registry.get(path)
    if not module:
        click.echo(f"Error: {path} not in registry.")
        sys.exit(1)

    if module.status != "in_progress":
        click.echo(f"Error: {path} is not in progress (status: {module.status})")
        click.echo("Start a review first with:")
        click.echo(f"  glreview start {path}")
        sys.exit(1)

    # Check claude is available
    if not dry_run and not claude_available():
        click.secho("Error: claude CLI not found.", fg="red")
        click.echo()
        click.echo("Install Claude Code from: https://claude.ai/code")
        sys.exit(1)

    # Read source file
    source_path = config.root / path
    if not source_path.exists():
        click.echo(f"Error: File not found: {source_path}")
        sys.exit(1)

    source_code = source_path.read_text()

    # Get diff if re-review
    diff = None
    if module.last_reviewed_commit:
        diff = get_file_diff(path, module.last_reviewed_commit, cwd=config.root)

    # Check if we need to chunk
    chunks = chunk_source_code(source_code)

    if len(chunks) > 1:
        click.secho(f"⚠ Large file: splitting into {len(chunks)} chunks", fg="yellow")
        click.echo()

    # Process each chunk
    all_results = []
    for chunk in chunks:
        if len(chunks) > 1:
            click.echo(f"Processing chunk {chunk.chunk_num}/{chunk.total_chunks} "
                      f"(lines {chunk.start_line}-{chunk.end_line})...")

        # Build prompt
        code_to_review = chunk.content
        if diff_only and diff:
            code_to_review = f"# Diff only mode - showing changes\n\n{diff}"

        prompt = build_review_prompt(
            path=path,
            source_code=code_to_review,
            module_status=module,
            config=config,
            diff=diff if not diff_only else None,
            chunk_info=chunk if len(chunks) > 1 else None,
        )

        if dry_run:
            click.echo("=" * 60)
            click.echo("PROMPT (dry-run mode):")
            click.echo("=" * 60)
            click.echo(prompt)
            click.echo("=" * 60)
            if len(chunks) > 1 and chunk.chunk_num < len(chunks):
                click.echo()
            continue

        # Run Claude
        click.echo(f"Running Claude review ({model})...")
        result, success = run_claude_review(prompt, model=model)

        if not success:
            click.secho(f"Error: {result}", fg="red")
            sys.exit(1)

        all_results.append(result)

        if len(chunks) > 1:
            click.echo()

    if dry_run:
        return

    # Combine results
    if len(all_results) == 1:
        final_result = all_results[0]
    else:
        final_result = "# Combined Review (Multiple Chunks)\n\n"
        for i, result in enumerate(all_results, 1):
            final_result += f"## Chunk {i}\n\n{result}\n\n---\n\n"

    # Output results
    click.echo()
    click.echo("=" * 60)
    click.secho("CLAUDE REVIEW RESULTS", fg="cyan", bold=True)
    click.echo("=" * 60)
    click.echo(final_result)
    click.echo("=" * 60)

    # Post to issue if requested
    if post:
        issue_number = None
        if module.review_issue:
            if module.review_issue.startswith("#"):
                issue_number = module.review_issue[1:]
            elif "/" in module.review_issue:
                issue_number = module.review_issue.rstrip("/").split("/")[-1]
            else:
                issue_number = module.review_issue

        if not issue_number:
            click.secho("Warning: No issue found to post to.", fg="yellow")
        else:
            project = get_gitlab_project(cwd=config.root)
            host = get_gitlab_host(cwd=config.root)

            if not gitlab_available(host):
                click.secho("Warning: GitLab API not available, cannot post.", fg="yellow")
            else:
                comment = f"## Claude Code Review\n\n{final_result}\n\n---\n_Generated by `glreview claude-review`_"
                if add_issue_comment(issue_number, project, comment, host=host):
                    click.secho(f"✓ Posted review to issue #{issue_number}", fg="green")
                else:
                    click.secho(f"Warning: Could not post to issue #{issue_number}", fg="yellow")


@main.command()
@click.option("--team", "-t", help="Filter by team name")
@click.pass_context
def reviewers(ctx: click.Context, team: str | None) -> None:
    """List available reviewers."""
    config = ctx.obj["config"]

    # Check for team filter
    if team:
        if team not in config.teams:
            click.echo(f"Unknown team: {team}")
            click.echo(f"Available teams: {', '.join(config.teams.keys()) or 'none'}")
            sys.exit(1)

        click.echo(f"Team '{team}':")
        for member in config.teams[team]:
            click.echo(f"  {member}")
        return

    # Show configured teams
    if config.teams:
        click.echo("Configured teams:")
        for team_name, members in config.teams.items():
            click.echo(f"  {team_name}: {', '.join(members)}")
        click.echo()

    # Query GitLab
    project = get_gitlab_project(cwd=config.root)
    host = get_gitlab_host(cwd=config.root)

    if not gitlab_available(host):
        click.secho("GitLab authentication not configured.", fg="yellow")
        click.echo()
        click.echo("Set one of these environment variables:")
        click.echo("  GITLAB_PRIVATE_TOKEN - Personal access token")
        click.echo("  GITLAB_TOKEN         - Alias for above")
        click.echo("  CI_JOB_TOKEN         - For CI pipelines")
        click.echo()
        click.echo("Optionally set GITLAB_URL for self-hosted instances.")
        return

    click.echo("Fetching project members...")
    members = get_project_members(project_path=project, host=host)

    if not members:
        click.echo("Could not fetch project members.")
        click.echo("Check your token has 'read_api' scope.")
        return

    # Group by access level
    by_level: dict[str, list] = {}
    for member in sorted(members, key=lambda m: (-m.access_level, m.username)):
        level_name = member.access_level_name
        if level_name not in by_level:
            by_level[level_name] = []
        by_level[level_name].append(member)

    click.echo()
    click.echo("Project members:")
    for level_name in ["Owner", "Maintainer", "Developer", "Reporter", "Guest"]:
        if level_name not in by_level:
            continue
        click.echo(f"\n  {level_name}s:")
        for member in by_level[level_name]:
            click.echo(f"    @{member.username:<20} {member.name}")

    click.echo()
    click.echo("Usage: glreview start <file> --assignee @username")


@main.command()
@click.option("--base", default="origin/main", help="Base branch to compare against")
@click.pass_context
def check(ctx: click.Context, base: str) -> None:
    """CI check for changes to reviewed files."""
    config = ctx.obj["config"]
    registry = load_registry(config.registry_path)

    warnings = []
    critical = []

    for module in registry:
        if module.status != "reviewed":
            continue

        if has_changes_since(module.path, module.last_reviewed_commit, cwd=config.root):
            msg = f"{module.path} changed since review ({module.last_reviewed_commit})"
            warnings.append(msg)
            if module.priority == "critical":
                critical.append(module.path)

    if not warnings:
        click.secho("✓ No reviewed files have changed.", fg="green")
        sys.exit(0)

    click.secho("⚠ Reviewed files have changed:", fg="yellow")
    for msg in warnings:
        click.echo(f"  {msg}")

    if critical:
        click.echo()
        click.secho("Critical modules changed:", fg="red")
        for path in critical:
            click.echo(f"  {path}")

    click.echo()
    click.echo("Consider re-reviewing these modules after merging.")
    sys.exit(1)


@main.command()
@click.pass_context
def sync(ctx: click.Context) -> None:
    """Sync registry with filesystem."""
    config = ctx.obj["config"]
    registry = load_registry(config.registry_path)

    added, removed = sync_registry(
        config.root,
        config.sources,
        config.exclude,
        registry.modules,
    )

    if not added and not removed:
        click.echo("Registry is in sync with filesystem.")
        return

    if added:
        click.echo(f"Adding {len(added)} new modules:")
        for path in added:
            level, reviewers_required = config.get_priority(path)
            lines = count_lines(path, cwd=config.root)

            registry.set(
                ModuleStatus(
                    path=path,
                    status="needs_review",
                    priority=level,
                    lines=lines,
                    reviewers_required=reviewers_required,
                )
            )
            click.echo(f"  + {path}")

    if removed:
        click.echo(f"Removing {len(removed)} deleted modules:")
        for path in removed:
            registry.remove(path)
            click.echo(f"  - {path}")

    commit = get_current_commit(cwd=config.root)
    save_registry(registry, config.registry_path, commit=commit)

    click.secho("✓ Registry synced.", fg="green")


@main.command()
@click.option("--sync", "do_sync", is_flag=True, help="Run sync before generating report")
@click.option("--format", "fmt", type=click.Choice(["markdown", "json", "badge"]), default="markdown", help="Output format")
@click.option("--output", "-o", type=click.Path(), help="Write report to file")
@click.option("--commit", "do_commit", is_flag=True, help="Commit registry changes (for CI)")
@click.option("--message", "-m", default="Update review registry", help="Commit message")
@click.pass_context
def report(
    ctx: click.Context,
    do_sync: bool,
    fmt: str,
    output: str | None,
    do_commit: bool,
    message: str,
) -> None:
    """Generate review coverage report."""
    config = ctx.obj["config"]

    # Run sync first if requested
    if do_sync:
        ctx.invoke(sync)
        click.echo()

    registry = load_registry(config.registry_path)

    if not registry.modules:
        click.echo("No modules in registry. Run 'glreview init' first.")
        sys.exit(1)

    # Calculate stats
    summary = registry.summary
    total = summary["total"]
    reviewed = summary["reviewed"]
    in_progress = summary["in_progress"]
    needs_review = summary["needs_review"]
    total_lines = summary["total_lines"]
    reviewed_lines = summary["reviewed_lines"]

    coverage_pct = (reviewed / total * 100) if total > 0 else 0
    line_coverage_pct = (reviewed_lines / total_lines * 100) if total_lines > 0 else 0

    # Generate report in requested format
    if fmt == "json":
        import json
        report_data = {
            "coverage_pct": round(coverage_pct, 1),
            "line_coverage_pct": round(line_coverage_pct, 1),
            "reviewed": reviewed,
            "in_progress": in_progress,
            "needs_review": needs_review,
            "total": total,
            "reviewed_lines": reviewed_lines,
            "total_lines": total_lines,
        }
        report_text = json.dumps(report_data, indent=2)

    elif fmt == "badge":
        # Generate shields.io badge URL
        if coverage_pct >= 90:
            color = "brightgreen"
        elif coverage_pct >= 70:
            color = "green"
        elif coverage_pct >= 50:
            color = "yellow"
        else:
            color = "red"
        badge_url = f"https://img.shields.io/badge/review-{coverage_pct:.0f}%25-{color}"
        report_text = badge_url

    else:  # markdown
        # Determine badge color
        if coverage_pct >= 90:
            color = "brightgreen"
        elif coverage_pct >= 70:
            color = "green"
        elif coverage_pct >= 50:
            color = "yellow"
        else:
            color = "red"
        badge_url = f"https://img.shields.io/badge/review-{coverage_pct:.0f}%25-{color}"

        report_text = f"""## Review Coverage

![Coverage]({badge_url})

| Status | Modules | Lines |
|--------|---------|-------|
| ✓ Reviewed | {reviewed} | {reviewed_lines:,} |
| ◐ In Progress | {in_progress} | - |
| ○ Needs Review | {needs_review} | {total_lines - reviewed_lines:,} |

**Coverage:** {coverage_pct:.0f}% of modules, {line_coverage_pct:.0f}% of lines
"""

    # Output report
    if output:
        Path(output).write_text(report_text)
        click.echo(f"Report written to {output}")
    else:
        click.echo(report_text)

    # Commit if requested
    if do_commit:
        import subprocess

        # Check if there are changes to commit
        result = subprocess.run(
            ["git", "status", "--porcelain", config.registry],
            cwd=config.root,
            capture_output=True,
            text=True,
        )

        if not result.stdout.strip():
            click.echo("No changes to commit.")
            return

        # Stage and commit
        subprocess.run(
            ["git", "add", config.registry],
            cwd=config.root,
            check=True,
        )

        if output:
            subprocess.run(
                ["git", "add", output],
                cwd=config.root,
                check=False,  # May not exist in repo yet
            )

        result = subprocess.run(
            ["git", "commit", "-m", message],
            cwd=config.root,
            capture_output=True,
            text=True,
        )

        if result.returncode == 0:
            click.secho(f"✓ Committed: {message}", fg="green")
        else:
            click.secho(f"Warning: Commit failed: {result.stderr}", fg="yellow")


@main.command("ci-config")
def ci_config() -> None:
    """Print recommended GitLab CI configuration."""
    config_yaml = '''# Add to .gitlab-ci.yml

stages:
  - review

# Sync and report on MR pushes
review-mr:
  stage: review
  image: python:3.12
  script:
    - pip install glreview
    - git config user.email "gitlab-ci@$CI_SERVER_HOST"
    - git config user.name "GitLab CI"
    - git checkout $CI_COMMIT_REF_NAME
    - glreview report --sync --format markdown --output REVIEW_COVERAGE.md --commit -m "chore: update review registry [skip ci]"
    - git push http://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git HEAD:$CI_COMMIT_REF_NAME
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"

# Sync and report on main branch
review-main:
  stage: review
  image: python:3.12
  script:
    - pip install glreview
    - git config user.email "gitlab-ci@$CI_SERVER_HOST"
    - git config user.name "GitLab CI"
    - git checkout $CI_COMMIT_BRANCH
    - glreview report --sync --format markdown --output REVIEW_COVERAGE.md --commit -m "chore: update review registry [skip ci]"
    - git push http://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git HEAD:$CI_COMMIT_BRANCH
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
      changes:
        - "src/**/*.py"  # Adjust to match your sources pattern

# Block releases without 100% coverage (optional)
release-gate:
  stage: review
  image: python:3.12
  script:
    - pip install glreview
    - |
      COVERAGE=$(glreview report --format json | python -c "import sys,json; print(int(json.load(sys.stdin)['coverage_pct']))")
      if [ "$COVERAGE" -lt 100 ]; then
        echo "Review coverage is ${COVERAGE}%, need 100% for release"
        glreview status
        exit 1
      fi
      echo "Review coverage is 100% ✓"
  rules:
    - if: $CI_COMMIT_TAG
'''
    click.echo(config_yaml)


if __name__ == "__main__":
    main()
