import pkg_resources
import time
import json
import math
import pycurl
import certifi
from io import BytesIO
from docassemble.base.util import as_datetime


# -----------------------------------------------------
# Extract server name
# -----------------------------------------------------
def get_server_name(interview_url):
    begin = interview_url.find("//") + 2
    end = interview_url.find("/interview?")

    return interview_url[begin:end]


# -----------------------------------------------------
# Crawl installed packages on the current server.
# Store key and non key docassemble packages separately.
# -----------------------------------------------------
def installed_pkg_list(target: list) -> dict:
    installed_packages = {}
    key_packages = {}
    non_key_packages = {}

    for p in pkg_resources.working_set:
        # docassemble packages
        if "docassemble" in p.project_name:
            # Key packages
            if p.project_name in target:
                key_packages[p.project_name] = p.version
            # non-key packages
            if p.project_name not in target:
                non_key_packages[p.project_name] = p.version

    sorted_key_packages = sort_dict(key_packages)
    sorted_non_key_packages = sort_dict(non_key_packages)

    installed_packages["key_pkgs"] = sorted_key_packages
    installed_packages["non_key_pkgs"] = sorted_non_key_packages

    return installed_packages


def sort_dict(raw_data: dict):
    return dict(sorted(raw_data.items(), key=lambda x: x[0].lower()))


# -----------------------------------------------------
# Crawl github packages (default branch) under a given github user name
# -----------------------------------------------------
# Borrowed ideas from https://github.com/rsain/GitHub-Crawler
# It's 4 years old and broken, but the structure is still valid.
#
# The GitHub API limits the queries to get 100 elements per page and up to 1,000 elements in total.
# To get more than 1,000 elements, the main query should be split in multiple subqueries
# using different time windows through sub_queries (a list of subqueries).
#
# The original had a comment that DELAY_BETWEEN_QUERYS is used to avoid be banned - Is this still valid?
# See documentation regarding Github Search API limitations:
# https://docs.github.com/en/rest/reference/search
# https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting

URL = "https://api.github.com/search/repositories?q="  # The basic URL to use the GitHub API
PARAMETERS = "&per_page=100"  # Additional parameters for the query (by default 100 items per page)
DELAY_BETWEEN_QUERYS = 3  # The time to wait between different queries to GitHub


def getUrl(url):
    """Given a URL it returns its body"""
    buffer = BytesIO()
    c = pycurl.Curl()
    c.setopt(c.URL, url)
    c.setopt(c.WRITEDATA, buffer)
    c.setopt(c.CAINFO, certifi.where())
    c.perform()
    c.close()
    body = buffer.getvalue()
    # Body is a byte string.
    # We have to know the encoding in order to print it to a text file.
    return body.decode("iso-8859-1")


def fetch_github_repos(github_user, sub_queries) -> dict:
    """Given a github user input, returns soughted info. It doesn't contain version number."""
    repositories = {}

    # Run queries to get information in json format and download ZIP file for each repository
    for subquery in range(1, len(sub_queries) + 1):
        # Obtain the number of pages for the current subquery (by default each page contains 100 items)
        url = URL + github_user + str(sub_queries[subquery - 1]) + PARAMETERS
        dataRead = json.loads(getUrl(url))
        numberOfPages = int(math.ceil(dataRead.get("total_count") / 100.0))

        # Results are in different pages
        for currentPage in range(1, numberOfPages + 1):
            url = (
                URL
                + github_user
                + str(sub_queries[subquery - 1])
                + PARAMETERS
                + "&page="
                + str(currentPage)
            )
            dataRead = json.loads(getUrl(url))

            # Iteration over all the repositories in the current json page [a list of dicts]
            for repo in dataRead["items"]:
                repositories[repo["name"]] = {
                    "branch_name": repo["default_branch"],
                    "created_date": as_datetime(repo["created_at"]).format_date(),
                    "push_date": as_datetime(repo["pushed_at"]).format_date(),
                    "open_issues_count": repo["open_issues_count"],
                    "repo_url": repo["html_url"],
                }

        # A delay between different subqueries
        if subquery < len(sub_queries):
            time.sleep(DELAY_BETWEEN_QUERYS)
    return sort_dict(repositories)


# -----------------------------------------------------
# Grab github repo version number in setup.py and add it to the input repo_list
# Separate the pile into key repos/non-key repos & sort each, then store them in a new nested dict.
# -----------------------------------------------------
def fetch_github_repo_version(repo_list, key_pkgs, github_user) -> dict:
    import requests

    github_repos = {}  # Parent dict
    key_repos = {}  # Child dict
    nonkey_repos = {}  # Child dict

    github_key_pkg_names = [a.replace(".", "-") for a in key_pkgs]

    for k, v in list(repo_list.items()):
        # Construct url for repo's setup.py file
        setup_py_URL = (
            "https://raw.githubusercontent.com/"
            + github_user
            + "/"
            + k
            + "/"
            + v["branch_name"]
            + "/setup.py"
        )

        # Fetch file content
        file_content = requests.get(setup_py_URL)
        has_version_num = False
        # Not every package has a setup.py file
        if file_content.text:
            # Find the line containing "version=" and copy the version number
            for line in file_content:
                decoded_line = line.decode("utf-8")
                if "version=" in decoded_line:
                    str_start = decoded_line.find("version=")
                    str_end = decoded_line.find("description")
                    version_num = decoded_line[str_start:str_end][9:].replace(
                        "',\n", ""
                    )
                    v["version"] = (
                        version_num  # Add version number to the original repo_list.
                    )
                    has_version_num = True
                    break

        # Separate key pkgs from non-key pkgs and save them into new dicts
        if k in github_key_pkg_names:
            key_repos[k] = v  # copy the record into key_repos
        else:
            if has_version_num:  # Only care about repos with version#/setup.py
                nonkey_repos[k] = v  # copy the record into nonkey_repos

    # Store the sorted new repos into the parent dict
    github_repos["key_repos"] = sort_dict(key_repos)
    github_repos["non_key_repos"] = sort_dict(nonkey_repos)

    return github_repos


# -----------------------------------------------------
# Compare server repo version with github repo version
# Return the results as a dict for screen display
# -----------------------------------------------------
def compare_repo_version(server_repo_dict, github_repo_dict) -> dict:
    version_table = {}
    for k1, v1 in server_repo_dict.items():  # Loop thru server repos
        version_table[k1] = {"server": v1, "github": "No new commit"}

        if len(github_repo_dict) > 0:  # If there are any commits in the period
            for k2, v2 in github_repo_dict.items():
                if k1 == k2.replace("-", "."):  # If repo match is found
                    if v1 in v2["version"]:  # Check its version
                        # Override the version info in the table record
                        version_table[k1] = {"server": v1, "github": v2["version"]}
                    else:  # Override the not-matched version info with an alert sign
                        version_table[k1] = {
                            "server": v1 + " &#9940",
                            "github": v2["version"],
                        }

    return version_table
