#!/usr/bin/env cspython
""" Generate a hubbearerfile for use with CodeSonar hub authentication. """

import argparse
import os
import sys

from gtr import (
    getpass as gtr_getpass,
    raw_input as gtr_raw_input,
    file_mkdir_owner_only as gtr_file_mkdir_owner_only,
    file_make_path_owner_only as gtr_file_make_path_owner_only,
)

from cs_client.cshub_client import (
    CSHubClientError,
    CSHubClient,
    CSHubPasswordCredentials,
    CSHubBearerTokenCredentials,
    load_hubbearerfile,
)


def mkdirs_owner_only(dir_path):
    """ Make a sequence of directories which will have user/owner access only. """
    dir_stack = []
    parent_dir = dir_path
    while parent_dir and not os.path.exists(parent_dir):
        dir_stack.append(parent_dir)
        parent_dir2 = os.path.dirname(parent_dir)
        if parent_dir2 == parent_dir:
            parent_dir2 = ''
        parent_dir = parent_dir2
    for parent_dir in reversed(dir_stack):
        gtr_file_mkdir_owner_only(parent_dir)


def fetch_hub_session_token(
        hub_address, hubuser, hubpass,
        cafile=None, session_kwargs=None):
    """ Sign-in to the hub and to create a new session token. """
    credentials = CSHubPasswordCredentials(hubuser, hubpass)
    hub = CSHubClient(hub_address, cafile=cafile)
    # TODO should we ensure hub client is configured to use bearer auth?
    try:
        hub.sign_in(credentials, **session_kwargs)
        session_token = hub.session_token
    finally:
        # We can close the connection, but don't sign out!
        hub.close()
    return session_token


def delete_hub_session(hub_address, hubbearerfile, cafile=None):
    """ Sign-out a hub session with a bearer token file. """
    session_token = load_hubbearerfile(hubbearerfile)
    if not session_token:
        return
    credentials = CSHubBearerTokenCredentials(session_token)
    hub = CSHubClient(hub_address, cafile=cafile)
    try:
        # sign_in() won't actually create a new session here,
        #  it just tells the hub client about the bearer token
        #  so that we can subsquently call sign_out to actually invalidate the token.
        hub.sign_in(credentials)
        # Need to force the signout, otherwise hub client
        #  would simply forget the token and not actually invalidate it.
        hub.sign_out(force=True)
    finally:
        hub.close()


def main(argv, stdio):
    """ Commandline tool to generate a hubbearerfile. """
    stdin = stdio[0]
    stderr = stdio[2]
    exit_code = 0
    arg_parser = argparse.ArgumentParser(
            prog=os.path.basename(argv[0]),
            description="Generate a hubbearerfile for CodeSonar hub authentication."
            )
    arg_parser.add_argument(
        'hub_address',
        metavar='hub',
        help='Address of CodeSonar hub')
    arg_parser.add_argument(
        'hubbearerfile',
        help="Name of file to create.  File will be created with user/owner-only permission if possible.")
    arg_parser.add_argument(
        '-d', '--delete',
        dest='delete',
        action='store_true',
        help="Invalidate session associated with an existing hubbearerfile. Use --unlink to delete the file too.")
    arg_parser.add_argument(
        '-u', '--unlink',
        dest='unlink',
        action='store_true',
        help="When deleting a session, also delete the file.  Implies -d")
    arg_parser.add_argument(
        '-hubuser',
        help='Hub user name for authentication')
    arg_parser.add_argument(
        '-hubcacert',
        help='Copy of trusted hub HTTPS server certificate file in PEM format.')
    arg_parser.add_argument(
        "-p", "--make-dirs", "--mkdirs" "--parents",
        dest='make_dirs',
        action='store_true',
        help="Create all intermediate directories in the file path as needed.  Directories will be created with user/owner-only permission if possible.")
    arg_parser.add_argument(
        "-f", "--overwrite",
        dest='overwrite',
        action='store_true',
        help="Overwrite any existing bearer file.  Implies --mkdirs")
    arg_parser.add_argument(
        "--expires-in",
        dest='expires_in',
        type=int,
        help="Number of seconds until token should expire.  Requires CodeSonar 8.0 or later.")
    arg_parser.add_argument(
        "--overflow-ok",
        dest='overflow_ok',
        action='store_true',
        help="Use a limited overflow session if no new session can be created.  Requires CodeSonar 8.0 or later.")
    arg_parser.add_argument(
        "--keepalive", "--keep-alive",
        dest='keepalive',
        action='store_true',
        help="Allow session expiry to be extended by session use.  Requires CodeSonar 8.0 or later.")
    arg_parser.add_argument(
        "-m", "--message", "--note",
        dest='session_note',
        help="Note to include with session record, visible in hub.  Requires CodeSonar 8.0 or later.")
    arg_parser.add_argument(
        "--pool",
        dest="pool_name",
        help="Name of session pool.  Requires CodeSonar 8.0 or later.")
    arg_obj = arg_parser.parse_args(argv[1:])
    hub_address = arg_obj.hub_address
    hubcacert = arg_obj.hubcacert
    hubuser = arg_obj.hubuser
    hubbearerfile = arg_obj.hubbearerfile
    should_delete_session = arg_obj.delete
    should_delete_hubbearerfile = arg_obj.unlink
    should_make_dirs = arg_obj.make_dirs
    should_overwrite = arg_obj.overwrite
    # TODO: print warning or error if session arguments are unsupported by hub:
    session_kwargs = {
            'overflow_ok': arg_obj.overflow_ok,
            'keepalive': arg_obj.keepalive,
        }
    if arg_obj.expires_in:
        session_kwargs['expires_relative'] = arg_obj.expires_in
    if arg_obj.session_note:
        session_kwargs['note'] = arg_obj.session_note
    if arg_obj.pool_name:
        session_kwargs['pool'] = arg_obj.pool_name
    if should_overwrite:
        should_make_dirs = True
    if should_delete_hubbearerfile:
        should_delete_session = True
    assert hubbearerfile
    if should_delete_session:
        delete_hub_session(hub_address, hubbearerfile, cafile=hubcacert)
        if should_delete_hubbearerfile:
            os.remove(hubbearerfile)
        return 0
    hubbearerfile_dir = os.path.dirname(hubbearerfile)
    session_token = None
    if os.path.exists(hubbearerfile) and not should_overwrite:
        stderr.write("File exists, will not overwrite: %s\n" % hubbearerfile)
        exit_code = 1
    elif hubbearerfile_dir and not os.path.exists(hubbearerfile_dir) and not should_make_dirs:
        stderr.write("Directory does not exist: %s\n" % hubbearerfile_dir)
        exit_code = 1
    if not exit_code and not hubuser:
        hubuser = gtr_raw_input("Hub username: ")
        hubuser = hubuser.rstrip('\r\n')
    if not exit_code and hubuser:
        password = gtr_getpass("Password: ")
        stderr.write("\n")
    if not exit_code and hub_address and hubuser and password:
        try:
            session_token = fetch_hub_session_token(
                    hub_address,
                    hubuser,
                    password,
                    cafile=hubcacert,
                    session_kwargs=session_kwargs,
                    )
        except CSHubClientError as ex:
            stderr.write(str(ex))
            stderr.write("\n")
            exit_code = 1
    if session_token is not None:
        if hubbearerfile_dir and not os.path.exists(hubbearerfile_dir) and should_make_dirs:
            stderr.write("Creating directory: %s\n" % hubbearerfile_dir)
            mkdirs_owner_only(hubbearerfile_dir)
        stderr.write("Writing file: %s\n" % hubbearerfile)
        # Create the file, but leave it empty temporarily:
        with open(hubbearerfile, 'w') as hubbearerfile_io:
            pass
        # Change permission to be owner only:
        gtr_file_make_path_owner_only(hubbearerfile)
        # Now we can insert the token into the file:
        with open(hubbearerfile, 'w') as hubbearerfile_io:
            hubbearerfile_io.write(session_token)
    return exit_code


if __name__ == '__main__':
    sys.exit(main(sys.argv, (sys.stdin, sys.stdout, sys.stderr)))
