from github3 import GitHub
from pathlib import Path
from cryptography.hazmat.backends import default_backend
import time
import json
import os
import jwt
import requests


def get_issue_handle(owner, repo, pem, app_id, issue_number):
    "Returns handle for the issue (which is also the PR)"
    with open('app.pem', 'w') as f:
        f.write(pem)
    app = GitHubApp(pem_path='app.pem', app_id=app_id)
    i_id = app.get_installation_id(owner=owner, repo=repo)
    client = app.get_installation(i_id)
    issue_handle = client.issue(username=owner, repository=repo, number=issue_number)
    return issue_handle

def get_pr_metadata(owner, repo, pr_number, token):
    "fetch information about the pr"
    url = f"https://api.github.com/repos/{owner}/{repo}/pulls/{pr_number}"
    headers = {"Accept": "application/vnd.github.v3+json",
               "Authorization": f"token {token}"}
    response = requests.get(url, headers=headers)
    return response

class GitHubApp(GitHub):
    """
    This is a small wrapper around the github3.py library
    
    Provides some convenience functions for testing purposes.
    """
    
    def __init__(self, pem_path, app_id):
        super().__init__()
        
        self.path = Path(pem_path)
        self.app_id = app_id
        
        if not self.path.is_file():
            raise ValueError(f'argument: `pem_path` must be a valid filename. {pem_path} was not found.')        
    
    def get_app(self):
        with open(self.path, 'rb') as key_file:
            client = GitHub()
            client.login_as_app(private_key_pem=key_file.read(),
                        app_id=self.app_id)
        return client
    
    def get_installation(self, installation_id):
        "login as app installation without requesting previously gathered data."
        with open(self.path, 'rb') as key_file:
            client = GitHub()
            client.login_as_app_installation(private_key_pem=key_file.read(),
                                             app_id=self.app_id,
                                             installation_id=installation_id)
        return client
        
    def get_jwt(self):
        """
        This is needed to retrieve the installation access token (for debugging). 
        
        Useful for debugging purposes.  Must call .decode() on returned object to get string.
        """
        now = self._now_int()
        payload = {
            "iat": now,
            "exp": now + (60),
            "iss": self.app_id
        }
        with open(self.path, 'rb') as key_file:
            private_key = default_backend().load_pem_private_key(key_file.read(), None)
            return jwt.encode(payload, private_key, algorithm='RS256')
    
    def get_installation_id(self, owner, repo):
        "https://developer.github.com/v3/apps/#find-repository-installation"
        url = f'https://api.github.com/repos/{owner}/{repo}/installation'

        headers = {'Authorization': f'Bearer {self.get_jwt().decode()}',
                   'Accept': 'application/vnd.github.machine-man-preview+json'}
        
        response = requests.get(url=url, headers=headers)
        if response.status_code != 200:
            raise Exception(f'Status code : {response.status_code}, {response.json()}')
        return response.json()['id']

    def get_installation_access_token(self, installation_id):
        "Get the installation access token for debugging."
        
        url = f'https://api.github.com/app/installations/{installation_id}/access_tokens'
        headers = {'Authorization': f'Bearer {self.get_jwt().decode()}',
                   'Accept': 'application/vnd.github.machine-man-preview+json'}
        
        response = requests.post(url=url, headers=headers)
        if response.status_code != 201:
            raise Exception(f'Status code : {response.status_code}, {response.json()}')
        return response.json()['token']


    def _extract(self, d, keys):
        "extract selected keys from a dict."
        return dict((k, d[k]) for k in keys if k in d)
    
    def _now_int(self):
        return int(time.time())

    def generate_installation_curl(self, endpoint):
        iat = self.get_installation_access_token()
        print(f'curl -i -H "Authorization: token {iat}" -H "Accept: application/vnd.github.machine-man-preview+json" https://api.github.com{endpoint}')

if __name__ == "__main__":
    pem = os.getenv('INPUT_APP_PEM')
    app_id = os.getenv('INPUT_APP_ID')
    trigger_phrase = os.getenv('INPUT_TRIGGER_PHRASE')
    trigger_label = os.getenv('INPUT_INDICATOR_LABEL')
    payload_fname = os.getenv('GITHUB_EVENT_PATH')
    test_payload_fname = os.getenv('INPUT_TEST_EVENT_PATH')
    github_token = os.getenv('GITHUB_TOKEN')

    if trigger_label and not (pem and app_id):
        raise EnvironmentError("If you supply a value for INDICATOR_LABEL you must also provide APP_PEM and APP_ID to authenticate as a GitHub App.")
 
    assert github_token, "Error: system environment variable GITHUB_TOKEN must be provided."
    assert trigger_phrase, "Error: must supply input TRIGGER_PHRASE"
    assert payload_fname or test_payload_fname, "Error: System environment variable GITHUB_EVENT_PATH or TEST_EVENT_PATH not found"
    
    fname = payload_fname if not test_payload_fname else test_payload_fname
    owner, repo = os.getenv('GITHUB_REPOSITORY').split('/')
    
    with open(fname, 'r') as f:
        payload = json.load(f)

    if pem and app_id:
        with open('temp_pem_file.txt', 'w') as f:
            f.write(pem)
        app = GitHubApp(pem_path='temp_pem_file.txt', app_id=app_id)
        installation_id = app.get_installation_id(owner=owner, repo=repo)
        app_token = app.get_installation_access_token(installation_id=installation_id)
        assert app_token, "Was not able to retrieve token for the App Installation"
        print(f"::add-mask::{app_token}")
        print(f"::set-output name=APP_INSTALLATION_TOKEN::{app_token}")

    issue_data = payload['issue']
    issue_number = issue_data['number']
    comment_data = payload['comment']
    username = comment_data['user']['login']
    
    assert 'issue' in payload and 'comment' in payload, 'Error: this action is designed to operate on the event issue_comment only.'

    # For Output Variable BOOL_TRIGGERED
    triggered = False
    if 'pull_request' in issue_data and trigger_phrase in comment_data['body']:
        triggered = True

        response = get_pr_metadata(owner=owner, repo=repo, pr_number=issue_number, token=github_token)
        assert response, f"Error: unable to retrieve PR metadata: {response.status_code}"
        head_branch = response.json()['head']['ref']
        head_sha = response.json()['head']['sha']

        if trigger_label and pem:
            issue_handle = get_issue_handle(owner=owner, repo=repo, pem=pem, app_id=app_id, issue_number=issue_number)
            result = issue_handle.add_labels(trigger_label)
            labels = [x.name for x in result]
            assert result and trigger_label in labels, "issue annotation on PR not successfull."
            print(f'Successfully added label {trigger_label} to {issue_handle.state} PR: {issue_handle.html_url}')
            
        # emit output variablesOne w
        trailing_text = comment_data['body'].split(trigger_phrase)[-1]
        trailing_line = trailing_text.splitlines()[0].strip() if trailing_text.splitlines() else ''
        trailing_token = trailing_line.split()[0] if trailing_line.split() else ''
        print(f"::set-output name=TRAILING_LINE::{trailing_line}")
        print(f"::set-output name=TRAILING_TOKEN::{trailing_token}")
        print(f"::set-output name=PULL_REQUEST_NUMBER::{issue_number}")
        print(f"::set-output name=COMMENTER_USERNAME::{username}")
        print(f"::set-output name=BRANCH_NAME::{head_branch}")
        print(f"::set-output name=SHA::{head_sha}")
    
    print(f"::set-output name=BOOL_TRIGGERED::{triggered}")