from dataclasses import dataclass

import requests


@dataclass
class User:
    name: str
    organizations: list[str]


@dataclass
class Commit:
    message: str
    user: User
    additions: int
    deletions: int


def call_with_query(query, token):
    url = "https://api.github.com/graphql"
    r = requests.post(url, json={"query": query}, headers={"Authorization": f"Bearer {token}"})
    return r.json()


def get_tag_commit_date(token, repository, tag):
    owner, name = repository.split("/")
    query = f"""
    query GetTagCommit {{
        repository(owner: "{owner}", name: "{name}"){{
            object(expression: "{tag}") {{
                ... on Commit {{
                    oid
                    message
                    committedDate
                    author {{
                        user {{
                            login
                        }}
                    }}
                }}
            }}
        }}
    }}
    """

    response = call_with_query(query, token)

    try:
        repository = response["data"]["repository"]["object"]

        if repository is None:
            if "errors" in response:
                raise ValueError(response["errors"][0]["message"])
            raise ValueError("Invalid tag. Does this tag exist?")

        committed_date = repository["committedDate"]
    except (KeyError, TypeError):
        raise ValueError("Invalid token. Does your token have the valid permissions?")

    return committed_date


def get_commits(token, repository, branch, since):
    owner, name = repository.split("/")

    def get_page_result(next_page=""):
        query = f"""
        query GetCommits {{
            repository(owner: "{owner}", name: "{name}"){{
                nameWithOwner
                object(expression: "{branch}") {{
                    ... on Commit {{
                        oid
                        history(first: 100, since: "{since}"{next_page}) {{
                            nodes {{
                                message
                                deletions
                                additions
                                author {{
                                    user {{
                                        login
                                        organizations(first: 100) {{
                                            nodes {{
                                                name
                                            }}
                                        }}
                                    }}
                                }}
                            }}
                            pageInfo {{
                                endCursor
                                hasNextPage
                            }}
                        }}
                    }}
                }}
            }}
        }}
        """
        result = call_with_query(query, token)

        if "data" not in result:
            raise ValueError(result["errors"][0]["message"])

        if result["data"]["repository"]["object"] is None:
            raise ValueError("Either the tag or the branch were incorrect.")

        nodes = result["data"]["repository"]["object"]["history"]["nodes"]
        cursor = result["data"]["repository"]["object"]["history"]["pageInfo"]["endCursor"]
        return nodes, cursor

    nodes, cursor = get_page_result()

    while cursor is not None:
        _nodes, cursor = get_page_result(f' after:"{cursor}"')
        nodes.extend(_nodes)

    commits = []
    for node in nodes:
        if node["author"]["user"] is None:
            commits.append(
                Commit(
                    message=node["message"].split("\n")[0],
                    user=User(name="<NOT FOUND>", organizations=[]),
                    additions=node.get("additions"),
                    deletions=node.get("deletions"),
                )
            )
        else:
            commits.append(
                Commit(
                    message=node["message"].split("\n")[0],
                    user=User(
                        name=node["author"]["user"]["login"],
                        organizations=[n["name"] for n in node["author"]["user"]["organizations"]["nodes"]],
                    ),
                    additions=node.get("additions"),
                    deletions=node.get("deletions"),
                )
            )

    return commits