import argparse
import html
import io
import tempfile
from contextlib import redirect_stdout
from difflib import unified_diff
from pathlib import Path

from typer.cli import app as typer_main


PACKAGE_REFERENCE_PATH = Path(__file__).parents[1] / "docs" / "source" / "en" / "package_reference" / "cli.md"

WARNING_HEADER = """<!--
# WARNING
# This entire file has been generated by Typer based on the `hf` CLI implementation.
# To re-generate the code, run `make style` or `python ./utils/generate_cli_reference.py --update`.
# WARNING
-->"""


def print_colored_diff(expected: str, current: str) -> None:
    """Print a colored line-by-line diff between expected and current content.

    Auto-generated code by Cursor.
    """
    expected_lines = expected.splitlines(keepends=True)
    current_lines = current.splitlines(keepends=True)

    diff = unified_diff(
        current_lines, expected_lines, fromfile="Current content", tofile="Expected content", lineterm=""
    )

    for line in diff:
        line = line.rstrip("\n")
        if line.startswith("+++"):
            print(f"\033[92m{line}\033[0m")  # Green for additions
        elif line.startswith("---"):
            print(f"\033[91m{line}\033[0m")  # Red for deletions
        elif line.startswith("@@"):
            print(f"\033[96m{line}\033[0m")  # Cyan for context
        elif line.startswith("+"):
            print(f"\033[92m{line}\033[0m")  # Green for additions
        elif line.startswith("-"):
            print(f"\033[91m{line}\033[0m")  # Red for deletions
        else:
            print(line)  # Default color for context


def generate_cli_reference() -> str:
    with tempfile.TemporaryDirectory() as tmpdir:
        tmp_file = Path(tmpdir) / "cli.md"
        try:
            with redirect_stdout(io.StringIO()):  # catch and ignore typer.echo output
                typer_main(
                    [
                        "src/huggingface_hub/cli/hf.py",
                        "utils",
                        "docs",
                        "--name",
                        "hf",
                        "--output",
                        str(tmp_file),
                    ]
                )
        except SystemExit as e:
            # Typer (Click) calls sys.exit() internally, so we catch it
            if e.code not in (0, None):
                raise  # re-raise if it was an error exit

        content = tmp_file.read_text()
        # Decode HTML entities that Typer generates
        content = html.unescape(content)
        return f"{WARNING_HEADER}\n\n{content}"


def check_and_update_cli_reference(update: bool, verbose: bool = False) -> None:
    new_content = generate_cli_reference()
    if PACKAGE_REFERENCE_PATH.exists():
        existing_content = PACKAGE_REFERENCE_PATH.read_text()
        if existing_content == new_content:
            print("✅ All good! (CLI reference)")
            return
    elif not update:
        print(
            f"❌ `{PACKAGE_REFERENCE_PATH}` does not exist yet.\n"
            "   Please run `make style` or `python utils/generate_cli_reference.py --update` to generate it."
        )
        exit(1)

    if update:
        PACKAGE_REFERENCE_PATH.write_text(new_content)
        print(
            f"✅ CLI reference has been updated in `{PACKAGE_REFERENCE_PATH}`.\n   Please make sure the changes are accurate and commit them."
        )
    else:
        print(
            f"❌ Expected content mismatch in `{PACKAGE_REFERENCE_PATH}`.\n"
            "   It is most likely that you've modified the CLI implementation and did not re-generate the docs.\n"
            "   Please run `make style` or `python utils/generate_cli_reference.py --update`."
        )

        if verbose:
            print("\n📋 Diff between current and expected content:")
            print_colored_diff(new_content, existing_content)

        exit(1)


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--update",
        action="store_true",
        help=(f"Whether to re-generate `{PACKAGE_REFERENCE_PATH}` if a change is detected."),
    )
    parser.add_argument(
        "--verbose",
        action="store_true",
        help="Show detailed diff when content mismatch is detected.",
    )
    args = parser.parse_args()
    check_and_update_cli_reference(update=args.update, verbose=args.verbose)
