#!/usr/bin/env python3

# Copyright Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT

"""
Add SPDX license headers to source files that don't have them.

This script adds the short SPDX license format to files tracked by git
that don't already contain a license header.
"""

import argparse
import sys
from pathlib import Path

repo_dir = Path(__file__).resolve().parent.parent
sys.path.append(str(repo_dir / "scripts" / "lib"))

from rrperf import git  # noqa: E402

# New license text for different file types
LICENSE_CPP = """\
// Copyright Advanced Micro Devices, Inc., or its affiliates.
// SPDX-License-Identifier: MIT

"""

LICENSE_HASH = """\
# Copyright Advanced Micro Devices, Inc., or its affiliates.
# SPDX-License-Identifier: MIT

"""

# Files that should never have licenses added
EXCLUDE_NAMES = [
    "CODEOWNERS",
    "CppCheckSuppressions.txt",
    "requirements.txt",
    ".clang-format",
    ".gitignore",
    ".gitattributes",
    "LICENSE.md",
]

# Paths containing these substrings should be excluded
EXCLUDE_PATHS = [
    "cmake/CMakeRC.cmake",  # External: vector-of-bool
    "GPUArchitectureGenerator/isa_spec_manager/tinyxml2",  # External: Lee Thomason
    "GPUArchitectureGenerator/isa_spec_manager/amdisa_",  # Different AMD project
    "GPUArchitectureGenerator/isa_spec_manager/isa_xml_reader",  # Different AMD project
    "_deps/",
]

# Extensions that can have licenses added
EXTENSIONS_CPP = [".hpp", ".cpp", ".h", ".c", ".in", ".cl", ".ql", ".qll"]
EXTENSIONS_HASH = [".py", ".sh", ".cmake", ".txt"]


def should_exclude(path: Path) -> bool:
    """Check if a file should be excluded from processing."""
    if path.name in EXCLUDE_NAMES:
        return True
    path_str = str(path)
    for exclude in EXCLUDE_PATHS:
        if exclude in path_str:
            return True
    return False


def get_license_format(path: Path) -> str | None:
    """Determine license format for file, or None if not applicable."""
    if path.suffix in EXTENSIONS_CPP:
        return "cpp"
    elif path.suffix in EXTENSIONS_HASH:
        return "hash"
    elif "dockerfile" in path.name.lower():
        # Dockerfiles use # comments but have no extension
        return "hash"
    elif "doxyfile" in path.name.lower():
        # Doxyfiles use # comments
        return "hash"
    elif path.suffix == "" and path.name not in EXCLUDE_NAMES:
        # Scripts without extension - check for shebang
        try:
            with open(path, "r") as f:
                first_line = f.readline()
                if first_line.startswith("#!"):
                    return "hash"
        except (IOError, UnicodeDecodeError):
            pass
    return None


def has_license(text: str) -> bool:
    """Check if file already has a license."""
    return "SPDX-License-Identifier" in text or "Copyright" in text


def prepend_license(text: str, license_format: str) -> str:
    """Prepend license to file content, preserving shebang and encoding lines."""
    lines = text.split("\n")
    insert_index = 0

    # Preserve shebang
    if lines and lines[0].startswith("#!"):
        insert_index = 1

    # Preserve encoding declaration
    if len(lines) > insert_index and "coding:" in lines[insert_index]:
        insert_index += 1

    license_text = LICENSE_CPP if license_format == "cpp" else LICENSE_HASH
    license_lines = license_text.rstrip("\n").split("\n")

    # Insert license and blank line
    lines[insert_index:insert_index] = license_lines + [""]

    return "\n".join(lines)


def process_file(path: Path, dry_run: bool = False) -> bool:
    """Process a single file, returning True if it was modified."""
    if should_exclude(path):
        return False

    license_format = get_license_format(path)
    if license_format is None:
        return False

    try:
        text = path.read_text()
    except (IOError, UnicodeDecodeError) as e:
        print(f"Error reading {path}: {e}")
        return False

    if has_license(text):
        return False

    new_text = prepend_license(text, license_format)

    if dry_run:
        print(f"Would add license: {path}")
    else:
        print(f"Adding license: {path}")
        path.write_text(new_text)

    return True


def main():
    parser = argparse.ArgumentParser(
        description="Add SPDX license headers to source files"
    )
    parser.add_argument(
        "dir",
        type=Path,
        default=repo_dir,
        nargs="?",
        help="Directory to process (default: repo root)",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Show what would be changed without making changes",
    )

    args = parser.parse_args()

    updated = 0
    errors = []

    for x in git.ls_tree(args.dir):
        path = args.dir / Path(x)
        if path.is_file():
            try:
                if process_file(path, args.dry_run):
                    updated += 1
            except Exception as e:
                print(f"Error processing {path}: {e}")
                errors.append(path)

    print(
        f"\n{'Would add license to' if args.dry_run else 'Added license to'} {updated} files"
    )
    if errors:
        print(f"\nErrors in {len(errors)} files:")
        for e in errors:
            print(f"  {e}")


if __name__ == "__main__":
    main()
