from __future__ import annotations

import subprocess
import sys

import click

from .git.diff import get_diff
from .git.files import get_file_contents
from .git.metadata import get_metadata
from .git.segments import get_review_segments
from .local_ui import LocalUiError, serve_local_review
from .payload.encode import write_payload
from .payload.types import AgentReviewPayload
from .vcs import detect_repository, emit_verbose, verbose_activity

TERMINAL_OUTPUT_WARNING_BYTES = 10 * 1024 * 1024


HELP_EPILOG = """
\b
Examples:
  agentreview
    Review all staged, unstaged, and untracked changes in your working tree.
  agentreview --local
    Start the local review UI and load the current review directly from disk.
  agentreview --staged
    Review only what is staged for the next commit in Git.
  agentreview --branch main
    Review committed changes on your branch relative to main.
  agentreview --branch main --uncommitted
    Review committed branch changes plus local working tree and untracked changes.
  agentreview --commit HEAD~3
    Review committed changes since a specific commit or revision.
  agentreview --commit HEAD~3 --uncommitted
    Review committed changes since a revision plus local working tree and untracked changes.
  agentreview --branch origin/main
    Compare against the remote-tracking branch instead of a local branch ref.

\b
Common use cases:
  agentreview | pbcopy
    Copy the payload so you can paste it into ChatGPT, Codex, Claude, or another LLM.
  agentreview --branch main > review.txt
    Save a feature-branch review payload to a file for later use.
  git add -p && agentreview --staged
    Review only the Git hunks you intentionally staged.

\b
Notes:
  Use only one of --staged, --branch, or --commit.
  --staged is only available in Git repositories.
  --local serves the bundled web UI locally instead of printing a payload blob.
  --uncommitted only affects --branch and --commit. Plain agentreview still reviews your working tree.
  COMMIT can be any git commit-ish or Sapling revision identifier.

\b
Web UI:
  https://agentreview-web.vercel.app/
"""


@click.command(epilog=HELP_EPILOG)
@click.option("--staged", is_flag=True, help="Only include staged changes (Git only; uses git diff --cached).")
@click.option(
    "--uncommitted",
    "--uncomitted",
    "include_uncommitted",
    is_flag=True,
    help="Also include working tree and untracked changes with --branch or --commit.",
)
@click.option(
    "--local",
    "local_mode",
    is_flag=True,
    help="Launch the local web UI instead of printing the encoded payload.",
)
@click.option(
    "-v",
    "--verbose",
    is_flag=True,
    help="Print timestamped progress and underlying git/sl commands to stderr.",
)
@click.option(
    "--branch",
    "base_branch",
    default=None,
    metavar="BASE",
    help=(
        "Compare your current HEAD against the common ancestor with BASE. "
        "Add --uncommitted to also include local working tree changes."
    ),
)
@click.option(
    "--commit",
    "base_commit",
    default=None,
    metavar="COMMIT",
    help=(
        "Compare your current HEAD against COMMIT or another revision identifier. "
        "Add --uncommitted to also include local working tree changes."
    ),
)
def main(
    staged: bool,
    include_uncommitted: bool,
    local_mode: bool,
    verbose: bool,
    base_branch: str | None,
    base_commit: str | None,
) -> None:
    """Generate an LLM-friendly code review payload from git or sl changes.

    Default mode includes staged, unstaged, and untracked file changes.
    """
    selected_modes = sum(
        1 for enabled in (staged, base_branch is not None, base_commit is not None) if enabled
    )
    if selected_modes > 1:
        raise click.UsageError("Choose only one of --staged, --branch, or --commit.")

    if base_branch is not None:
        diff_mode = "branch"
        base_ref = base_branch or "main"
    elif base_commit is not None:
        diff_mode = "commit"
        base_ref = base_commit
    elif staged:
        diff_mode = "staged"
        base_ref = "main"
    else:
        diff_mode = "default"
        base_ref = "main"

    try:
        repository = detect_repository(verbose=verbose)
    except Exception as exc:
        click.echo(f"Error detecting repository: {exc}", err=True)
        sys.exit(1)

    emit_verbose(verbose, f"mode={diff_mode} base={base_ref}")

    if staged and repository.kind != "git":
        raise click.UsageError("--staged is only available in Git repositories.")

    try:
        diff = get_diff(
            repository,
            diff_mode,
            base_ref,
            include_uncommitted=include_uncommitted,
        )
    except subprocess.CalledProcessError as exc:
        detail = exc.stderr.strip() or exc.stdout.strip() or str(exc)
        click.echo(f"Error running {repository.kind} diff: {detail}", err=True)
        sys.exit(1)
    except Exception as exc:
        click.echo(f"Error running {repository.kind} diff: {exc}", err=True)
        sys.exit(1)

    if not diff.strip():
        click.echo("No changes detected.", err=True)
        sys.exit(1)

    diff_bytes = len(diff.encode("utf-8"))
    emit_verbose(verbose, f"diff bytes={diff_bytes}")
    emit_verbose(verbose, "collecting metadata")
    meta = get_metadata(repository, diff_mode, base_ref)
    emit_verbose(
        verbose,
        f"metadata repo={meta.repo} branch={meta.branch} commit={meta.commit_hash}",
    )
    emit_verbose(verbose, "extracting file contents")
    with verbose_activity(verbose, "extracting file contents"):
        files = get_file_contents(
            repository,
            diff,
            diff_mode,
            base_ref,
            include_uncommitted=include_uncommitted,
        )
    emit_verbose(verbose, f"files={len(files)}")
    segments = []
    if diff_mode == "commit":
        try:
            emit_verbose(verbose, "collecting review segments")
            with verbose_activity(verbose, "collecting review segments"):
                segments = get_review_segments(
                    repository,
                    diff_mode,
                    base_ref,
                    include_uncommitted=include_uncommitted,
                )
            emit_verbose(verbose, f"segments={len(segments)}")
        except subprocess.CalledProcessError as exc:
            detail = exc.stderr.strip() or exc.stdout.strip() or str(exc)
            click.echo(f"Error collecting commit provenance: {detail}", err=True)
            sys.exit(1)
        except Exception as exc:
            click.echo(f"Error collecting commit provenance: {exc}", err=True)
            sys.exit(1)

    payload = AgentReviewPayload(meta=meta, files=files, segments=segments)
    if local_mode:
        emit_verbose(verbose, "launching local review UI")
        try:
            serve_local_review(payload)
        except LocalUiError as exc:
            click.echo(f"Error launching local review UI: {exc}", err=True)
            sys.exit(1)
        return

    if verbose and sys.stdout.isatty() and diff_bytes >= TERMINAL_OUTPUT_WARNING_BYTES:
        emit_verbose(
            True,
            "stdout is a terminal; writing a very large payload may be slow. Pipe to pbcopy or a file to avoid terminal rendering overhead.",
        )
    emit_verbose(verbose, "writing payload")
    with verbose_activity(verbose, "writing payload"):
        write_payload(payload, sys.stdout)
        sys.stdout.write("\n")
        sys.stdout.flush()
