"""Internal tool to update the changelog."""

import subprocess
from dataclasses import dataclass
from datetime import datetime
from typing import List


@dataclass(frozen=True)
class Change:
    commit_hash: str
    prefix: str
    message: str


def main(changelog_path: str):
    changelog = get_changelog(changelog_path)
    git_tag = get_most_recent_git_tag()
    changes = get_formatted_changes(git_tag)
    print("-" * 80)
    print(changes)

    new_version = version_bump(git_tag)
    today = datetime.now()
    header = f"Version {new_version}, {today:%Y-%m-%d}\n"
    header = header + "-" * (len(header) - 1) + "\n"
    trailer = f"\nFull Changelog: https://github.com/py-pdf/PyPDF2/compare/{git_tag}...{new_version}\n\n"
    new_entry = header + changes + trailer
    print(new_entry)

    # TODO: Make idempotent - multiple calls to this script
    # should not change the changelog
    new_changelog = new_entry + changelog
    write_changelog(new_changelog, changelog_path)


def version_bump(git_tag: str) -> str:
    # just assume a patch version change
    major, minor, patch = git_tag.split(".")
    return f"{major}.{minor}.{int(patch) + 1}"


def get_changelog(changelog_path: str) -> str:
    with open(changelog_path) as fh:
        changelog = fh.read()
    return changelog


def write_changelog(new_changelog: str, changelog_path: str) -> None:
    with open(changelog_path, "w") as fh:
        fh.write(new_changelog)


def get_formatted_changes(git_tag: str) -> str:
    commits = get_git_commits_since_tag(git_tag)

    # Group by prefix
    grouped = {}
    for commit in commits:
        if commit.prefix not in grouped:
            grouped[commit.prefix] = []
        grouped[commit.prefix].append({"msg": commit.message})

    # Order prefixes
    order = ["DEP", "ENH", "PI", "BUG", "ROB", "DOC", "DEV", "MAINT", "TST", "STY"]
    abbrev2long = {
        "DEP": "Deprecations",
        "ENH": "New Features",
        "BUG": "Bug Fixes",
        "ROB": "Robustness",
        "DOC": "Documentation",
        "DEV": "Developer Experience",
        "MAINT": "Maintenance",
        "TST": "Testing",
        "STY": "Code Style",
        "PI": "Performance Improvements",
    }

    # Create output
    output = ""
    for prefix in order:
        if prefix not in grouped:
            continue
        output += f"\n{abbrev2long[prefix]} ({prefix}):\n"  # header
        for commit in grouped[prefix]:
            output += f"- {commit['msg']}\n"
        del grouped[prefix]

    if grouped:
        print("@" * 80)
        output += "\nYou forgot something!:\n"
        for prefix in grouped:
            output += f"- {prefix}: {grouped[prefix]}\n"
        print("@" * 80)

    return output


def get_most_recent_git_tag():
    git_tag = str(
        subprocess.check_output(
            ["git", "describe", "--abbrev=0"], stderr=subprocess.STDOUT
        )
    ).strip("'b\\n")
    return git_tag


def get_git_commits_since_tag(git_tag) -> List[Change]:
    commits = str(
        subprocess.check_output(
            [
                "git",
                "--no-pager",
                "log",
                f"{git_tag}..HEAD",
                '--pretty=format:"%h%x09%s"',
            ],
            stderr=subprocess.STDOUT,
        )
    ).strip("'b\\n")
    return [parse_commit_line(line) for line in commits.split("\\n")]


def parse_commit_line(line) -> Change:
    if "\\t" not in line:
        raise ValueError(f"Invalid commit line: {line}")
    commit_hash, rest = line.split("\\t", 1)
    if ":" in rest:
        prefix, message = rest.split(":", 1)
    else:
        prefix = ""
        message = rest

    # Standardize
    message.strip()

    if message.endswith('"'):
        message = message[:-1]

    prefix = prefix.strip()
    if prefix == "DOCS":
        prefix = "DOC"

    return Change(commit_hash=commit_hash, prefix=prefix, message=message)


if __name__ == "__main__":
    main("CHANGELOG.md")