import argparse
import os
import re
import subprocess
import sys
from datetime import datetime


def get_remote_commit_sha(repo_url: str, branch: str) -> str:
    output = subprocess.check_output(
        ["git", "ls-remote", repo_url, f"refs/heads/{branch}"], text=True
    )
    return output.split("\t")[0]


def get_remote_latest_tag(repo_url: str, tag_pattern: str) -> str:
    output = subprocess.check_output(
        ["git", "ls-remote", "--tags", repo_url], text=True
    )
    tags = []
    for line in output.split("\n"):
        if line.strip():
            parts = line.split("\t")
            if len(parts) == 2:
                ref = parts[1]
                if ref.startswith("refs/tags/") and not ref.endswith("^{}"):
                    tag = ref.replace("refs/tags/", "")
                    if re.match(tag_pattern + "$", tag):
                        tags.append(tag)

    def version_key(tag: str) -> tuple[int, ...]:
        version = tag.lstrip("v")
        return tuple(map(int, version.split(".")))

    return sorted(tags, key=version_key)[-1]


def get_current_version_from_file(file_path: str, pattern: str) -> str:
    with open(file_path) as f:
        content = f.read()

    match = re.search(pattern, content)
    return match.group(1)


def update_file_version(
    file_path: str, old_pattern: str, new_value: str, comment_pattern: str
) -> None:
    with open(file_path) as f:
        content = f.read()

    new_content = re.sub(old_pattern, new_value, content)

    current_date = datetime.now().strftime("%b %d, %Y")
    new_content = re.sub(
        comment_pattern,
        lambda m: m.group(0).split(", as of")[0] + f", as of {current_date}.",
        new_content,
    )

    with open(file_path, "w") as f:
        f.write(new_content)


def generate_commit_message(
    repo_name: str,
    repo_url: str,
    old_version: str,
    new_version: str,
    is_tag: bool,
    commit_url_template: str,
    diff_url_template: str,
) -> str:
    if is_tag:
        version_link = (
            f"[Tag: {new_version}]({repo_url}/releases/tag/{new_version})"
        )
        diff_url = diff_url_template.format(
            repo_url=repo_url, old_version=old_version, new_version=new_version
        )
        diff_link = f"[Diff]({diff_url})"
        description = "between the previously used tag and the new tag."
    else:
        commit_url = commit_url_template.format(
            repo_url=repo_url, version=new_version
        )
        version_link = f"[Commit: {new_version}]({commit_url})"
        diff_url = diff_url_template.format(
            repo_url=repo_url, old_version=old_version, new_version=new_version
        )
        diff_link = f"[Diff]({diff_url})"
        description = (
            "between the last commit hash merged to this repository "
            "and the new commit."
        )

    return f"## {repo_name}\n{version_link}\n\n{diff_link} {description}"


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Bump a single dependency version"
    )
    parser.add_argument(
        "--name", required=True, help="Display name for the dependency"
    )
    parser.add_argument("--repo-url", required=True, help="Git repository URL")
    parser.add_argument(
        "--branch", default="main", help="Branch to check (default: main)"
    )
    parser.add_argument(
        "--file-path", required=True, help="File containing current version"
    )
    parser.add_argument(
        "--current-version-pattern",
        required=True,
        help="Regex to extract current version (group 1)",
    )
    parser.add_argument(
        "--update-pattern", required=True, help="Regex pattern for replacement"
    )
    parser.add_argument(
        "--comment-pattern",
        required=True,
        help="Regex pattern for comment update",
    )
    parser.add_argument(
        "--tag", action="store_true", help="Check tags instead of commits"
    )
    parser.add_argument(
        "--tag-pattern", default=r"v?[0-9\.]*", help="Pattern for tag matching"
    )
    parser.add_argument(
        "--commit-url-template",
        default="{repo_url}/commit/{version}",
        help="Template for commit URLs",
    )
    parser.add_argument(
        "--diff-url-template",
        default="{repo_url}/compare/{old_version}...{new_version}",
        help="Template for diff URLs",
    )
    parser.add_argument(
        "--commit-message-fd",
        type=int,
        help="File descriptor to write commit message to",
    )

    args = parser.parse_args()

    current_version = get_current_version_from_file(
        args.file_path, args.current_version_pattern
    )

    if args.tag:
        latest_version = get_remote_latest_tag(args.repo_url, args.tag_pattern)
    else:
        latest_version = get_remote_commit_sha(args.repo_url, args.branch)

    if current_version == latest_version:
        print(f"{args.name}: No update needed (current: {current_version})")
        if not args.commit_message_fd:
            with open(os.environ["GITHUB_OUTPUT"], "a") as f:
                f.write("HAS_UPDATES=false\n")
        return 0

    print(
        f"{args.name}: Update available "
        f"({current_version} -> {latest_version})"
    )

    replacement = args.update_pattern.replace("{new_version}", latest_version)
    update_file_version(
        args.file_path,
        args.current_version_pattern,
        replacement,
        args.comment_pattern,
    )

    commit_msg = generate_commit_message(
        args.name,
        args.repo_url,
        current_version,
        latest_version,
        args.tag,
        args.commit_url_template,
        args.diff_url_template,
    )

    if args.commit_message_fd:
        with os.fdopen(args.commit_message_fd, "w") as f:
            f.write(commit_msg)
    else:
        with open(os.environ["GITHUB_OUTPUT"], "a") as f:
            f.write("COMMIT_MSG<<EOF\n")
            f.write(commit_msg)
            f.write("\nEOF\n")
            f.write("HAS_UPDATES=true\n")

    return 0


if __name__ == "__main__":
    sys.exit(main())
