#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Author: Chmouel Boudjnah <chmouel@chmouel.com>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# See README.md for documentation
import typing
import argparse
import base64
import hashlib
import hmac
import json
import os
import subprocess
import sys
import time

import requests
import ghapp_token

NAMESPACE = "pipelines-as-code"
SECRET_NAME = "pipelines-as-code-secret"
ELNAME = "pipelines-as-code"


EXPIRE_MINUTES_AS_SECONDS = (
    int(os.environ.get("GITHUBAPP_TOKEN_EXPIRATION_MINUTES", 10)) * 60
)


def get_controller_route():
    elroute = subprocess.run(
        f"kubectl get route -n {NAMESPACE} -l pipelines-as-code/route=controller -o json",
        shell=True,
        check=True,
        capture_output=True,
    )
    return (
        "https://"
        + json.loads(elroute.stdout)["items"][0]["status"]["ingress"][0]["host"]
    )


def get_controller_ingress():
    elroute = subprocess.run(
        f"kubectl get ingress -n {NAMESPACE} -l pipelines-as-code/route=controller -o json",
        shell=True,
        check=True,
        capture_output=True,
    )
    return (
        "http://" + json.loads(elroute.stdout)["items"][0]["spec"]["rules"][0]["host"]
    )


def get_token_secret(
    github_api_url=ghapp_token.GITHUB_API_URL, expiration_time=EXPIRE_MINUTES_AS_SECONDS
):
    secret = subprocess.run(
        f"kubectl get secret {SECRET_NAME} -n{NAMESPACE} -o json",
        shell=True,
        check=True,
        capture_output=True,
    )
    jeez = json.loads(secret.stdout)
    private_key = base64.b64decode(jeez["data"]["github-private-key"])
    app_id = base64.b64decode(jeez["data"]["github-application-id"])
    webhook_secret = base64.b64decode(jeez["data"]["webhook.secret"]).decode()
    if not private_key or not app_id or not webhook_secret:
        print(
            f"private_key={private_key[1:10]} or app_id={app_id} or webhook_secret={webhook_secret} are empty"
        )
        sys.exit(1)

    gh = ghapp_token.GitHub(
        private_key,
        app_id,
        expiration_time,
        github_api_url,
    )
    return gh.token, webhook_secret, app_id


def _request_app_delivery(token, iid=None, api_url=ghapp_token.GITHUB_API_URL):
    url = f"{api_url}/app/hook/deliveries"
    if iid:
        url += f"/{iid}"
    headers = {
        "Accept": "application/vnd.github.v3+json",
        "Authorization": f"Bearer {token}",
    }
    return requests.request("GET", url, headers=headers)


def _request_webhooks_installed(
    token: str,
    owner_repo: str,
    iid: typing.Union[int, None] = None,
    api_url: str = ghapp_token.GITHUB_API_URL,
):
    url = f"{api_url}/repos/{owner_repo}/hooks"
    if iid:
        url += f"/{iid}/deliveries"
    headers = {
        "Accept": "application/vnd.github.v3+json",
        "Authorization": f"Bearer {token}",
    }
    return requests.request("GET", url, headers=headers)


def _request_webhooks_reattempt(
    token: str,
    owner_repo: str,
    iid: int,
    delivery_id: int,
    api_url: str = ghapp_token.GITHUB_API_URL,
):
    url = f"{api_url}/repos/{owner_repo}/hooks/{iid}/deliveries/{delivery_id}/attempts"
    print(url)
    headers = {
        "Accept": "application/vnd.github.v3+json",
        "Authorization": f"Bearer {token}",
    }
    return requests.request("POST", url, headers=headers)


def ask_which(token: str, api_url: str, last: bool, deliveries: dict) -> int:
    dico = []
    i = 1
    if "message" in deliveries:
        print(deliveries)
        sys.exit(0)
    for delivery in deliveries:
        print(
            f"{i}) Action={delivery['action']} Event={delivery['event']} Delivered at {delivery['delivered_at']}"
        )
        dico.append(delivery["id"])
        if i == 10:
            break
        i += 1
    chosen = input("Choose a delivery: ")
    # return _request_app_delivery(token, dico[int(chosen) - 1], api_url=api_url).json()
    return int(chosen) - 1


def webhook_get_delivery(
    token: str,
    owner_repo: str,
    last: bool = False,
    api_url: str = ghapp_token.GITHUB_API_URL,
) -> str:
    r = _request_webhooks_installed(token, api_url=api_url, owner_repo=owner_repo)
    r.raise_for_status()
    webhooks = r.json()
    if len(webhooks) == 1:
        webhook_id = int(webhooks[0]["id"])
    elif len(webhooks) > 1:
        cnt = 1
        for wh in webhooks:
            print(f"{cnt}) {wh['name']} - {wh['config']['url']} ")
            cnt += 1
        chosen = input("Choose a delivery: ")
        webhook_id = int(webhooks[int(chosen) - 1]["id"])
    else:
        print("could not find any webhook configuration on your repo {}")
        sys.exit(1)

    r = _request_webhooks_installed(
        token, api_url=api_url, owner_repo=owner_repo, iid=webhook_id
    )
    r.raise_for_status()
    deliveries = r.json()
    if not deliveries:
        print("no deliveries has been set ")
        sys.exit(1)
    if last:
        delivery_id = deliveries[0]["id"]
    else:
        chosen = ask_which(token, api_url, last, r.json())
        delivery_id = deliveries[chosen]["id"]

    r = _request_webhooks_reattempt(
        token=token,
        owner_repo=owner_repo,
        iid=webhook_id,
        api_url=api_url,
        delivery_id=delivery_id,
    )
    r.raise_for_status()
    print(f"Delivery has been replayed, you can replay directly it with: ")
    s = f"http POST {api_url}/repos/{owner_repo}/hooks/{webhook_id}/deliveries/{delivery_id}/attempts"
    s += f' Authorization:"Bearer { os.environ.get("PASS_TOKEN", "$TOKEN") }"'
    s += " Accept:application/vnd.github.v3+json"
    print(s)
    return s


def app_get_delivery(
    token: str, last: bool = False, api_url: str = ghapp_token.GITHUB_API_URL
) -> dict:
    r = _request_app_delivery(token, api_url=api_url)
    r.raise_for_status()
    deliveries = r.json()
    if not deliveries:
        print("no deliveries has been set ")
        sys.exit(1)
    if last:
        return _request_app_delivery(token, deliveries[0]["id"], api_url=api_url).json()

    chosen = ask_which(token, api_url, last, deliveries)
    return _request_app_delivery(
        token, deliveries[chosen]["id"], api_url=api_url
    ).json()


def save_script(target: str, el_route: str, headers: dict, payload: str):
    s = f"""#!/usr/bin/env python3
import requests
import sys
payload = \"\"\"{json.dumps(payload)}\"\"\"
headers={headers}
el_route = "http://localhost:8080" if (len(sys.argv) > 1 and sys.argv[1] == "-l") else "{el_route}"
r = requests.request("POST",el_route,data=payload.encode("utf-8"),headers=headers)
r.raise_for_status()
print("Request has been replayed on " + el_route)
"""
    with open(target, "w") as fp:
        fp.write(s)
    os.chmod(target, 0o755)
    print(f"Request saved to {target}")


def main(args):
    el = args.eroute
    if not el:
        try:
            el = get_controller_route()
        except subprocess.CalledProcessError:
            try:
                el = get_controller_ingress()
            except subprocess.CalledProcessError:
                print("Could not find an ingress or route")
                sys.exit(1)
    if args.webhook_repo:
        token, webhook_secret = args.webhook_token, args.webhook_secret
        replays = webhook_get_delivery(
            token,
            last=args.last_event,
            api_url=args.api_url,
            owner_repo=args.webhook_repo,
        )
        if args.save:
            open(args.save, "w").write(f"""#!/usr/bin/env bash\n{replays}\n""")
            os.chmod(args.save, 0o755)
            print(f"Saved to {args.save}")
        sys.exit(0)
    else:
        token, webhook_secret, app_id = get_token_secret(github_api_url=args.api_url)
        delivery = app_get_delivery(token, args.last_event, args.api_url)
    jeez = delivery["request"]["payload"]
    headers = delivery["request"]["headers"]
    payload = json.dumps(jeez)
    esha256 = hmac.new(
        webhook_secret.encode("utf-8"),
        msg=payload.encode("utf-8"),
        digestmod=hashlib.sha256,
    ).hexdigest()
    esha1 = hmac.new(
        webhook_secret.encode("utf-8"),
        msg=payload.encode("utf-8"),
        digestmod=hashlib.sha1,
    ).hexdigest()

    print("Replay event for repo " + jeez["repository"]["full_name"])
    headers.update(
        {
            "X-Hub-Signature": "sha1=" + esha1,
            "X-Hub-Signature-256": "sha256=" + esha256,
        }
    )

    if args.save:
        save_script(args.save, el, headers, jeez)
        sys.exit(0)
    for _ in range(args.retry):
        try:
            r = requests.request(
                "POST", el, data=payload.encode("utf-8"), headers=headers
            )
        except requests.exceptions.ConnectionError:
            print(f"sleeping until {el} is up")
            time.sleep(5)
            continue

        print(f"Payload has been replayed on {el}: {r}")
        return
    print("You have reached the maximum number of retries")


def parse_args():
    parser = argparse.ArgumentParser(description="Replay a webhook")
    parser.add_argument(
        "--installation-id",
        "-i",
        default=os.environ.get("INSTALLATION_ID"),
        help="Installation ID",
    )
    parser.add_argument(
        "--controller-route",
        "-e",
        dest="eroute",
        help="Route hostname (default to detect on openshift/ingress)",
        default=os.environ.get("EL_ROUTE"),
    )
    parser.add_argument("--last-event", "-L", action="store_true")

    parser.add_argument(
        "--webhook-repo", "-w", help="Use a webhook-repo instead of app"
    )
    parser.add_argument("--webhook-token", "-t", help="Use this token")
    parser.add_argument("--webhook-secret", "-S", help="Use this webhook secret")

    parser.add_argument(
        "--save", "-s", help="save the request to a shell script to replay easily"
    )
    parser.add_argument(
        "-a",
        "--api-url",
        help="Github API URL",
        default=os.environ.get("GITHUB_API_URL", ghapp_token.GITHUB_API_URL),
    )
    parser.add_argument(
        "--retry",
        type=int,
        default=1,
        help="how many time to try to contact the el route",
    )
    return parser.parse_args()


if __name__ == "__main__":
    main(parse_args())

[((42, 14, 47, 5), 'subprocess.run', 'subprocess.run', (), '', False, 'import subprocess\n'), ((55, 14, 60, 5), 'subprocess.run', 'subprocess.run', (), '', False, 'import subprocess\n'), ((69, 13, 74, 5), 'subprocess.run', 'subprocess.run', (), '', False, 'import subprocess\n'), ((75, 11, 75, 36), 'json.loads', 'json.loads', ({(75, 22, 75, 35): 'secret.stdout'}, {}), '(secret.stdout)', False, 'import json\n'), ((76, 18, 76, 70), 'base64.b64decode', 'base64.b64decode', ({(76, 35, 76, 69): "jeez['data']['github-private-key']"}, {}), "(jeez['data']['github-private-key'])", False, 'import base64\n'), ((77, 13, 77, 68), 'base64.b64decode', 'base64.b64decode', ({(77, 30, 77, 67): "jeez['data']['github-application-id']"}, {}), "(jeez['data']['github-application-id'])", False, 'import base64\n'), ((85, 9, 90, 5), 'ghapp_token.GitHub', 'ghapp_token.GitHub', ({(86, 8, 86, 19): 'private_key', (87, 8, 87, 14): 'app_id', (88, 8, 88, 23): 'expiration_time', (89, 8, 89, 22): 'github_api_url'}, {}), '(private_key, app_id, expiration_time, github_api_url)', False, 'import ghapp_token\n'), ((102, 11, 102, 56), 'requests.request', 'requests.request', (), '', False, 'import requests\n'), ((118, 11, 118, 56), 'requests.request', 'requests.request', (), '', False, 'import requests\n'), ((134, 11, 134, 57), 'requests.request', 'requests.request', (), '', False, 'import requests\n'), ((239, 4, 239, 27), 'os.chmod', 'os.chmod', ({(239, 13, 239, 19): 'target', (239, 21, 239, 26): '(493)'}, {}), '(target, 493)', False, 'import os\n'), ((272, 14, 272, 30), 'json.dumps', 'json.dumps', ({(272, 25, 272, 29): 'jeez'}, {}), '(jeez)', False, 'import json\n'), ((311, 13, 311, 68), 'argparse.ArgumentParser', 'argparse.ArgumentParser', (), '', False, 'import argparse\n'), ((37, 8, 37, 64), 'os.environ.get', 'os.environ.get', ({(37, 23, 37, 59): '"""GITHUBAPP_TOKEN_EXPIRATION_MINUTES"""', (37, 61, 37, 63): '(10)'}, {}), "('GITHUBAPP_TOKEN_EXPIRATION_MINUTES', 10)", False, 'import os\n'), ((83, 8, 83, 19), 'sys.exit', 'sys.exit', ({(83, 17, 83, 18): '(1)'}, {}), '(1)', False, 'import sys\n'), ((142, 8, 142, 19), 'sys.exit', 'sys.exit', ({(142, 17, 142, 18): '(0)'}, {}), '(0)', False, 'import sys\n'), ((185, 8, 185, 19), 'sys.exit', 'sys.exit', ({(185, 17, 185, 18): '(1)'}, {}), '(1)', False, 'import sys\n'), ((216, 8, 216, 19), 'sys.exit', 'sys.exit', ({(216, 17, 216, 18): '(1)'}, {}), '(1)', False, 'import sys\n'), ((266, 8, 266, 19), 'sys.exit', 'sys.exit', ({(266, 17, 266, 18): '(0)'}, {}), '(0)', False, 'import sys\n'), ((294, 8, 294, 19), 'sys.exit', 'sys.exit', ({(294, 17, 294, 18): '(0)'}, {}), '(0)', False, 'import sys\n'), ((78, 21, 78, 69), 'base64.b64decode', 'base64.b64decode', ({(78, 38, 78, 68): "jeez['data']['webhook.secret']"}, {}), "(jeez['data']['webhook.secret'])", False, 'import base64\n'), ((176, 8, 176, 19), 'sys.exit', 'sys.exit', ({(176, 17, 176, 18): '(1)'}, {}), '(1)', False, 'import sys\n'), ((202, 36, 202, 74), 'os.environ.get', 'os.environ.get', ({(202, 51, 202, 63): '"""PASS_TOKEN"""', (202, 65, 202, 73): '"""$TOKEN"""'}, {}), "('PASS_TOKEN', '$TOKEN')", False, 'import os\n'), ((230, 26, 230, 45), 'json.dumps', 'json.dumps', ({(230, 37, 230, 44): 'payload'}, {}), '(payload)', False, 'import json\n'), ((264, 12, 264, 38), 'os.chmod', 'os.chmod', ({(264, 21, 264, 30): 'args.save', (264, 32, 264, 37): '(493)'}, {}), '(args.save, 493)', False, 'import os\n'), ((315, 16, 315, 49), 'os.environ.get', 'os.environ.get', ({(315, 31, 315, 48): '"""INSTALLATION_ID"""'}, {}), "('INSTALLATION_ID')", False, 'import os\n'), ((323, 16, 323, 42), 'os.environ.get', 'os.environ.get', ({(323, 31, 323, 41): '"""EL_ROUTE"""'}, {}), "('EL_ROUTE')", False, 'import os\n'), ((340, 16, 340, 76), 'os.environ.get', 'os.environ.get', ({(340, 31, 340, 47): '"""GITHUB_API_URL"""', (340, 49, 340, 75): 'ghapp_token.GITHUB_API_URL'}, {}), "('GITHUB_API_URL', ghapp_token.GITHUB_API_URL)", False, 'import os\n'), ((302, 12, 302, 25), 'time.sleep', 'time.sleep', ({(302, 23, 302, 24): '(5)'}, {}), '(5)', False, 'import time\n'), ((253, 16, 253, 27), 'sys.exit', 'sys.exit', ({(253, 25, 253, 26): '(1)'}, {}), '(1)', False, 'import sys\n'), ((50, 10, 50, 36), 'json.loads', 'json.loads', ({(50, 21, 50, 35): 'elroute.stdout'}, {}), '(elroute.stdout)', False, 'import json\n'), ((62, 20, 62, 46), 'json.loads', 'json.loads', ({(62, 31, 62, 45): 'elroute.stdout'}, {}), '(elroute.stdout)', False, 'import json\n')]