#!/usr/bin/env python

"""Starts an interactive Jira session in an ipython terminal.

Script arguments support changing the server and a persistent authentication
over HTTP BASIC or Kerberos.
"""

import argparse
import configparser
import os
import sys
import webbrowser
from getpass import getpass
from urllib.parse import parse_qsl

import keyring
import requests
from oauthlib.oauth1 import SIGNATURE_RSA
from requests_oauthlib import OAuth1

from jira import JIRA, __version__

CONFIG_PATH = os.path.join(os.path.expanduser("~"), ".jira-python", "jirashell.ini")
SENTINEL = object()


def oauth_dance(server, consumer_key, key_cert_data, print_tokens=False, verify=None):
    if verify is None:
        verify = server.startswith("https")

    # step 1: get request tokens
    oauth = OAuth1(consumer_key, signature_method=SIGNATURE_RSA, rsa_key=key_cert_data)
    r = requests.post(
        server + "/plugins/servlet/oauth/request-token", verify=verify, auth=oauth
    )
    request = dict(parse_qsl(r.text))
    request_token = request.get("oauth_token", SENTINEL)
    request_token_secret = request.get("oauth_token_secret", SENTINEL)
    if request_token is SENTINEL or request_token_secret is SENTINEL:
        problem = request.get("oauth_problem")
        if problem is not None:
            message = f"OAuth error: {problem}"
        else:
            message = " ".join(f"{key}:{value}" for key, value in request.items())
        exit(message)

    if print_tokens:
        print("Request tokens received.")
        print(f"    Request token:        {request_token}")
        print(f"    Request token secret: {request_token_secret}")

    # step 2: prompt user to validate
    auth_url = f"{server}/plugins/servlet/oauth/authorize?oauth_token={request_token}"
    if print_tokens:
        print(f"Please visit this URL to authorize the OAuth request:\n\t{auth_url}")
    else:
        webbrowser.open_new(auth_url)
        print(
            "Your browser is opening the OAuth authorization for this client session."
        )

    approved = input(
        f"Have you authorized this program to connect on your behalf to {server}? (y/n)"
    )

    if approved.lower() != "y":
        exit(
            "Abandoning OAuth dance. Your partner faceplants. The audience boos. You feel shame."
        )

    # step 3: get access tokens for validated user
    oauth = OAuth1(
        consumer_key,
        signature_method=SIGNATURE_RSA,
        rsa_key=key_cert_data,
        resource_owner_key=request_token,
        resource_owner_secret=request_token_secret,
    )
    r = requests.post(
        server + "/plugins/servlet/oauth/access-token", verify=verify, auth=oauth
    )
    access = dict(parse_qsl(r.text))

    if print_tokens:
        print("Access tokens received.")
        print(f"    Access token:        {access['oauth_token']}")
        print(f"    Access token secret: {access['oauth_token_secret']}")

    return {
        "access_token": access["oauth_token"],
        "access_token_secret": access["oauth_token_secret"],
        "consumer_key": consumer_key,
        "key_cert": key_cert_data,
    }


def process_config():
    if not os.path.exists(CONFIG_PATH):
        return {}, {}, {}, {}

    parser = configparser.ConfigParser()
    try:
        parser.read(CONFIG_PATH)
    except configparser.ParsingError as err:
        print(f"Couldn't read config file at path: {CONFIG_PATH}\n{err}")
        raise

    if parser.has_section("options"):
        options = {}
        for option, value in parser.items("options"):
            if option in ("verify", "async"):
                value = parser.getboolean("options", option)
            options[option] = value
    else:
        options = {}

    if parser.has_section("basic_auth"):
        basic_auth = dict(parser.items("basic_auth"))
    else:
        basic_auth = {}

    if parser.has_section("oauth"):
        oauth = {}
        for option, value in parser.items("oauth"):
            if option in ("oauth_dance", "print_tokens"):
                value = parser.getboolean("oauth", option)
            oauth[option] = value
    else:
        oauth = {}

    if parser.has_section("kerberos_auth"):
        kerberos_auth = {}
        for option, value in parser.items("kerberos_auth"):
            if option in ("use_kerberos"):
                value = parser.getboolean("kerberos_auth", option)
            kerberos_auth[option] = value
    else:
        kerberos_auth = {}

    return options, basic_auth, oauth, kerberos_auth


def process_command_line():
    parser = argparse.ArgumentParser(
        description="Start an interactive Jira shell with the REST API."
    )
    jira_group = parser.add_argument_group("Jira server connection options")
    jira_group.add_argument(
        "-s",
        "--server",
        help="The Jira instance to connect to, including context path.",
    )
    jira_group.add_argument(
        "-r", "--rest-path", help="The root path of the REST API to use."
    )
    jira_group.add_argument("--auth-url", help="Path to URL to auth against.")
    jira_group.add_argument(
        "-v",
        "--rest-api-version",
        help="The version of the API under the specified name.",
    )

    jira_group.add_argument(
        "--no-verify", action="store_true", help="do not verify the ssl certificate"
    )

    basic_auth_group = parser.add_argument_group("BASIC auth options")
    basic_auth_group.add_argument(
        "-u", "--username", help="The username to connect to this Jira instance with."
    )
    basic_auth_group.add_argument(
        "-p", "--password", help="The password associated with this user."
    )
    basic_auth_group.add_argument(
        "-P",
        "--prompt-for-password",
        action="store_true",
        help="Prompt for the password at the command line.",
    )

    oauth_group = parser.add_argument_group("OAuth options")
    oauth_group.add_argument(
        "-od",
        "--oauth-dance",
        action="store_true",
        help="Start a 3-legged OAuth authentication dance with Jira.",
    )
    oauth_group.add_argument("-ck", "--consumer-key", help="OAuth consumer key.")
    oauth_group.add_argument(
        "-k",
        "--key-cert",
        help="Private key to sign OAuth requests with (should be the pair of the public key\
                                   configured in the Jira application link)",
    )
    oauth_group.add_argument(
        "-pt",
        "--print-tokens",
        action="store_true",
        help="Print the negotiated OAuth tokens as they are retrieved.",
    )

    oauth_already_group = parser.add_argument_group(
        "OAuth options for already-authenticated access tokens"
    )
    oauth_already_group.add_argument(
        "-at", "--access-token", help="OAuth access token for the user."
    )
    oauth_already_group.add_argument(
        "-ats", "--access-token-secret", help="Secret for the OAuth access token."
    )

    kerberos_group = parser.add_argument_group("Kerberos options")
    kerberos_group.add_argument(
        "--use-kerberos-auth", action="store_true", help="Use kerberos auth"
    )
    kerberos_group.add_argument(
        "--mutual-authentication",
        choices=["OPTIONAL", "DISABLED"],
        help="Mutual authentication",
    )
    args = parser.parse_args()

    options = {}
    if args.server:
        options["server"] = args.server

    if args.rest_path:
        options["rest_path"] = args.rest_path

    if args.auth_url:
        options["auth_url"] = args.auth_url

    if args.rest_api_version:
        options["rest_api_version"] = args.rest_api_version

    options["verify"] = True
    if args.no_verify:
        options["verify"] = False

    if args.prompt_for_password:
        args.password = getpass()

    basic_auth = {}
    if args.username:
        basic_auth["username"] = args.username

    if args.password:
        basic_auth["password"] = args.password

    key_cert_data = None
    if args.key_cert:
        with open(args.key_cert) as key_cert_file:
            key_cert_data = key_cert_file.read()

    oauth = {}
    if args.oauth_dance:
        oauth = {
            "oauth_dance": True,
            "consumer_key": args.consumer_key,
            "key_cert": key_cert_data,
            "print_tokens": args.print_tokens,
        }
    elif (
        args.access_token
        and args.access_token_secret
        and args.consumer_key
        and args.key_cert
    ):
        oauth = {
            "access_token": args.access_token,
            "oauth_dance": False,
            "access_token_secret": args.access_token_secret,
            "consumer_key": args.consumer_key,
            "key_cert": key_cert_data,
        }

    kerberos_auth = {"use_kerberos": args.use_kerberos_auth}

    if args.mutual_authentication:
        kerberos_auth["mutual_authentication"] = args.mutual_authentication

    return options, basic_auth, oauth, kerberos_auth


def get_config():
    options, basic_auth, oauth, kerberos_auth = process_config()

    cmd_options, cmd_basic_auth, cmd_oauth, cmd_kerberos_auth = process_command_line()

    options.update(cmd_options)
    basic_auth.update(cmd_basic_auth)
    oauth.update(cmd_oauth)
    kerberos_auth.update(cmd_kerberos_auth)

    return options, basic_auth, oauth, kerberos_auth


def handle_basic_auth(auth, server):
    if auth.get("password"):
        password = auth["password"]
        if input("Would you like to remember password in OS keyring? (y/n)") == "y":
            keyring.set_password(server, auth["username"], password)
    else:
        print("Getting password from keyring...")
        password = keyring.get_password(server, auth["username"])
        assert password, "No password provided!"
    return auth["username"], password


def main():

    try:
        try:
            get_ipython
        except NameError:
            pass
        else:
            sys.exit("Running ipython inside ipython isn't supported. :(")

        options, basic_auth, oauth, kerberos_auth = get_config()

        if basic_auth:
            basic_auth = handle_basic_auth(auth=basic_auth, server=options["server"])

        if oauth.get("oauth_dance") is True:
            oauth = oauth_dance(
                options["server"],
                oauth["consumer_key"],
                oauth["key_cert"],
                oauth["print_tokens"],
                options["verify"],
            )
        elif not all(
            (
                oauth.get("access_token"),
                oauth.get("access_token_secret"),
                oauth.get("consumer_key"),
                oauth.get("key_cert"),
            )
        ):
            oauth = None

        use_kerberos = kerberos_auth.get("use_kerberos", False)
        del kerberos_auth["use_kerberos"]

        jira = JIRA(
            options=options,
            basic_auth=basic_auth,
            kerberos=use_kerberos,
            kerberos_options=kerberos_auth,
            oauth=oauth,
        )

        import IPython

        # The top-level `frontend` package has been deprecated since IPython 1.0.
        if IPython.version_info[0] >= 1:
            from IPython.terminal.embed import InteractiveShellEmbed
        else:
            from IPython.frontend.terminal.embed import InteractiveShellEmbed

        ip_shell = InteractiveShellEmbed(
            banner1="<Jira Shell " + __version__ + " (" + jira.server_url + ")>"
        )
        ip_shell("*** Jira shell active; client is in 'jira'. Press Ctrl-D to exit.")
    except Exception as e:
        print(e, file=sys.stderr)
        return 2


if __name__ == "__main__":
    status = main()
    sys.exit(status)
