#!/usr/bin/env python3

import sys
import re
import os

import github  # this is PyGithub.

import requests
import string


def run_query(query):
    """A simple function to use requests.post to make the GraphQL API call."""

    request = requests.post(
        "https://api.github.com/graphql",
        json={"query": query},
        headers={"Authorization": f'Bearer {os.environ.get("GH_TOKEN")}'},
        timeout=20,
    )
    response = request.json()

    # Have to work around the unique GraphQL convention of returning 200 for errors.
    if request.status_code != 200 or "errors" in response:
        raise ValueError(
            f"Query failed to run by returning code of {request.status_code}."
            f"\nQuery: '{query}'"
            f"\nResponse: '{request.json()}'"
        )

    return response


def get_referenced_issues(pr_number):
    """Get the numbers of issue fixed by the given pull request."""

    ref_result = run_query(string.Template("""
        query {
            repository(owner: "timescale", name: "timescaledb") {
              pullRequest(number: $pr_number) {
                closingIssuesReferences(first: 100) {
                  edges {
                    node {
                      number
                    }
                  }
                }
              }
            }
          }
          """).substitute({"pr_number": pr_number}))

    # The above returns {'data': {'repository': {'pullRequest': {'closingIssuesReferences': {'edges': [{'node': {'number': 4944}}]}}}}}

    ref_edges = ref_result["data"]["repository"]["pullRequest"][
        "closingIssuesReferences"
    ]["edges"]

    if not ref_edges:
        return []

    return [edge["node"]["number"] for edge in ref_edges if edge]


# Check if a line matches any of the specified patterns
def is_valid_line(line):
    patterns = [
        r"^Fixes:\s*.*$",
        r"^Implements:\s*.*$",
        r"^Thanks:\s*.*$",
        r"^Backward-Incompatible Change:\s*.*$",
    ]
    for pattern in patterns:
        if re.match(pattern, line):
            return True
    return False


def main():
    github_token = os.environ.get("GH_TOKEN")

    if not github_token:
        print("Please populate the GH_TOKEN environment variable.")
        sys.exit(1)

    github_obj = github.Github(github_token)
    repo = github_obj.get_repo("timescale/timescaledb")
    # Get the file name from the command line argument
    if len(sys.argv) != 2:
        print("Please provide a file name as a command-line argument.")
        sys.exit(1)

    file_name = sys.argv[1]

    # Check if the file exists
    if not os.path.exists(file_name):
        print(f"{file_name} does not exist")
        sys.exit(1)

    this_pr_number = int(os.environ["PR_NUMBER"])
    pr_issues = set(get_referenced_issues(this_pr_number))

    # Read the file and check non-empty lines
    changelog_issues = set()
    with open(file_name, "r", encoding="utf-8") as file:
        for line in file:
            line = line.strip()
            if not is_valid_line(line):
                print(f'Invalid entry in change log: "{line}"')
                sys.exit(1)

            # The referenced issue number should be valid.
            for issue_number in re.findall("#([0-9]+)", line):
                issue_number = int(issue_number)
                try:
                    issue = repo.get_issue(number=issue_number)
                except github.UnknownObjectException:
                    print(
                        f"The changelog entry references an invalid issue #{issue_number}:\n{line}"
                    )
                    sys.exit(1)

                as_pr = None
                try:
                    as_pr = issue.as_pull_request()
                except github.UnknownObjectException:
                    # Not a pull request
                    pass

                # Accept references to PR itself.
                if as_pr:
                    if issue_number != this_pr_number:
                        print(
                            f"The changelog for PR #{this_pr_number} references another PR #{issue_number}"
                        )
                        sys.exit(1)
                    changelog_issues = pr_issues
                else:
                    changelog_issues.add(issue_number)

    if changelog_issues != pr_issues:
        print(
            "Instead of "
            + (f"the issues {pr_issues}" if pr_issues else "no issues")
            + f" linked to the PR #{this_pr_number}, the changelog references "
            + (f"the issues {changelog_issues}" if changelog_issues else "no issues")
        )
        sys.exit(1)


if __name__ == "__main__":
    main()
