from typing import Annotated, Optional
from git import Repo
import typer

FORMAT = "* {hash} {message}"
app = typer.Typer(add_completion=False)


def _find_tag(repo, tag_name):
    return next((tag for tag in repo.tags if tag.name == tag_name), None)


def _find_latest_tag(repo):
    latest_authored_date = 0
    latest_tag = None
    for tag in repo.tags:
        if tag.commit.authored_date > latest_authored_date:
            latest_authored_date = tag.commit.authored_date
            latest_tag = tag

    return latest_tag


def _resolve_tag(repo, tag_name):
    if not tag_name:
        return None

    tag = _find_tag(repo, tag_name)
    if tag is None:
        raise typer.BadParameter(f"tag not found: {tag_name}")

    return tag


@app.command()
def changelog(
    path: Annotated[str, typer.Argument(exists=True, file_okay=False, dir_okay=True)],
    start_tag: Annotated[
        Optional[str],
        typer.Option("--start-tag", help="Start changelog after this tag."),
    ] = None,
    end_tag: Annotated[
        Optional[str],
        typer.Option("--end-tag", help="End changelog at this tag (defaults to HEAD)."),
    ] = None,
):
    repo = Repo(path)
    if end_tag and not start_tag:
        typer.echo("--end-tag requires --start-tag", err=True)
        raise typer.Exit(code=2)

    resolved_start_tag = _resolve_tag(repo, start_tag) or _find_latest_tag(repo)
    resolved_end_tag = _resolve_tag(repo, end_tag)
    end_ref = resolved_end_tag.name if resolved_end_tag else "HEAD"

    if resolved_start_tag:
        commits = list(repo.iter_commits(f"{resolved_start_tag.name}..{end_ref}"))
    else:
        commits = list(repo.iter_commits(end_ref))

    commits = [
        commit
        for commit in commits
        if not commit.message.startswith("Merge pull request")
        and not commit.message.startswith("Merge branch")
    ]

    commits.reverse()

    for commit in commits:
        typer.echo(
            FORMAT.format(
                hash=commit.hexsha, message=commit.message.strip().splitlines()[0]
            )
        )


def cli():
    app()


if __name__ == "__main__":
    cli()
