"""pocli -- function libary

Copyright (c) 2016 - 2025
Florian Kaiser (florian.kaiser@mpcdf.mpg.de), Klaus Reuter (klaus.reuter@mpcdf.mpg.de)

Initial version based on the program "pyocclient.py" by Florian Kaiser.
"""


import os
import sys
import math
import json
import time
import locale
import argparse
import getpass
import nc_py_api
from six.moves import input


def get_ocrc():
    """Return the full path to the pocli <.ocrc> configuration file."""
    rcfile = os.environ.get("OC_CONFIG_FILE")
    if rcfile is None:
        home = os.path.expanduser('~')
        rcfile = os.path.join(home, '.ocrc')
    return rcfile


def init_ocrc():
    """Initialize the pocli <.ocrc> configuration file.

    By default, a MPCDF datashare compatible configuration will be created.
    """
    config = {}
    config['OC_USER'] = getpass.getuser()
    config['OC_SERVER'] = "https://datashare.mpcdf.mpg.de"
    config['OC_DEBUG'] = False
    rcfile = get_ocrc()
    with open(rcfile, 'w') as fp:
        fp.write(json.dumps(config, sort_keys=True, indent=4, separators=(',', ': ')))
    print("created config file: " + rcfile)


def _client():
    """Construct and return ownCloud client object.

    The configuration is read from the configuration file <~/ocrc>.
    The password is read from the environment variable OC_PASSWORD,
    in case this variable is not set the user is prompted interactively.
    """
    config = {}
    rcfile = get_ocrc()
    with open(rcfile, 'r') as fp:
        config = json.loads(fp.read())
    # Allow overrides with environment variables
    for key in ["OC_SERVER", "OC_USER", "OC_DEBUG"]:
        if key in os.environ:
            config[key] = os.environ[key]
    # for the MPCDF datashare service we forbid the use of OC_PASSWORD
    if (('datashare.mpcdf.mpg.de' not in config['OC_SERVER']) or ('OC_UNIT_TEST_COOKIE' in os.environ)) and ('OC_PASSWORD' in os.environ):
        password = os.environ['OC_PASSWORD']
    else:
        password = getpass.getpass()
    config['OC_PASSWORD'] = password
    client = nc_py_api.Nextcloud(nextcloud_url=config['OC_SERVER'],
                                 nc_auth_user=config['OC_USER'],
                                 nc_auth_pass=config['OC_PASSWORD'])
    return client


def _filesizeformat(value):
    """Convert an integer representing the size of a file-like object
    in bytes into a human readable form (i.e. 13 KB, 4.1 MB, etc).
    """
    _FILESIZE_BASE = 1024
    _FILESIZE_SUFFIXES = ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z')
    if value is None or value == "":
        return ""
    if type(value) != int:
        try:
            value = float(value)
        except:
            return ""
    suffix_index = 0 if value == 0 else int(math.log(value) / math.log(_FILESIZE_BASE))
    if suffix_index == 0:
        scaled_value = value
    elif suffix_index > len(_FILESIZE_SUFFIXES) - 1:
        raise ValueError("number too big")
    else:
        scaled_value = round(float(value) / pow(_FILESIZE_BASE, suffix_index), 2)
    suffix = _FILESIZE_SUFFIXES[suffix_index]
    return "%g %s" % (scaled_value, suffix)


# TODO: Improve output format
# Especially with Nextcloud mimetypes at the beginning, formatting is not very readable
def _print_file_list(l):
    """Print a file list using a pretty format."""
    file_info_str = lambda x: "{:10s} {:18s} {:s}".format(_filesizeformat(x.info.size),
                                x.info.last_modified.strftime('%x %X'), x.user_path)
    for x in l:
        print(file_info_str(x))


# Code snippet adapted from:
# https://code.activestate.com/recipes/577058-query-yesno/
def _query_yes_no(question, default="yes"):
    """Ask a yes/no question via raw_input() and return the answer as a string "yes" or "no".
    """
    valid = {"yes":"yes", "y":"yes", "ye":"yes", "no":"no", "n":"no"}
    if default == None:
        prompt = " [y/n] "
    elif default == "yes":
        prompt = " [Y/n] "
    elif default == "no":
        prompt = " [y/N] "
    else:
        raise ValueError("invalid default answer: '%s'" % default)
    while 1:
        sys.stdout.write(question + prompt)
        choice = input().lower()
        if default is not None and choice == '':
            return default
        elif choice in valid.keys():
            return valid[choice]
        else:
            sys.stdout.write("Please respond with 'yes' or 'no' (or 'y' or 'n').\n")


# --- routines called by the cli interface (cli.py) below ---


def put(argparse_args):
    """Upload one or more files to an OwnCloud server."""
    exit_status = 0
    if (argparse_args.directory):
        directory = argparse_args.directory
    else:
        directory = '/'
    args = vars(argparse_args)
    file_list = args['files']
    if (len(file_list) > 0):
        client = _client()

        dir_node = None
        try:
            dir_node = client.files.by_path(directory)
        except nc_py_api.NextcloudException as e:
            if e.status_code == 404:
                print("`%s': remote directory does not exist" % directory)
                exit_status = 1
            else:
                raise e

        if dir_node:
            for file_name in file_list:
                if os.path.isfile(file_name):
                    size_mb = float(os.path.getsize(file_name)) / float(1024 * 1024)
                    file_basename = os.path.basename(file_name)
                    if (size_mb > 100.0):
                        print("`%s': large file detected (%.2f MB), transfer may take some time ..." % (file_basename, size_mb))
                    t0 = time.time()
                    with open(file_name, "rb") as fp:
                        # TODO: pass custom chunk_size in kwargs (default 5MB, here 100MB)
                        client.files.upload_stream(os.path.join(directory, file_basename), fp, chunk_size=100*1024*1024)
                    t1 = time.time()
                    dt = t1 - t0
                    print("`%s': OK (%.2f MB/s)" % (file_basename, size_mb / dt))
                else:
                    print("`%s': skipping, not a regular file" % file_name)
    sys.exit(exit_status)


def get(argparse_args):
    """Download one or more files from an OwnCloud server."""
    exit_status = 0
    if (argparse_args.directory):
        directory = argparse_args.directory
    else:
        directory = '.'
    args = vars(argparse_args)
    file_list = args['files']
    if (len(file_list) > 0):
        if os.path.isdir(directory):
            client = _client()
            for file_name in file_list:
                try:
                    dir_node = client.files.by_path(file_name)
                except nc_py_api.NextcloudException as e:
                    if e.status_code == 404:
                        print("`%s': remote object does not exist" % (file_name,))
                        exit_status = 1
                    else:
                        raise e
                else:
                    if dir_node.is_dir:
                        print("`%s': skipping, download of remote directories is not supported" % (file_name,))
                        continue
                    else:
                        file_size_mb = float(dir_node.info.size) / float(1024 * 1024)
                        file_basename = os.path.basename(file_name)
                        if (file_size_mb > 100.0):
                            print("`%s': large file detected (%.2f MB), transfer may take some time ..." % (file_basename, file_size_mb))
                        file_target = os.path.join(directory, file_basename)

                        t0 = time.time()
                        # TODO: pass custom chunk_size in kwargs (default 5MB, here 100MB)
                        # From testing it seems the chunk_size parameter is not used for downloads
                        # despite what the documentation says. Maybe that is an optional feature
                        # of some implementations, but unlike chunking upload generally should
                        # also not really be necessary.
                        client.files.download2stream(file_name, file_target, chunk_size=100*1024*1024)
                        t1 = time.time()
                        dt = t1 - t0
                        size_mb = float(os.path.getsize(file_target)) / float(1024 * 1024)
                        print("`%s': OK (%.2f MB/s)" % (file_basename, size_mb / dt))
        else:
            print("`%s': invalid local directory" % directory)
            exit_status = 1
    sys.exit(exit_status)


def ls(argparse_args):
    """List directory on OwnCloud server."""
    args = vars(argparse_args)
    directory_list = args['dir']
    if (len(directory_list) == 0):
        directory_list.append('/')
    client = _client()
    for directory in directory_list:
        try:
            lst = client.files.listdir(directory)
        except nc_py_api.NextcloudException as e:
            if e.status_code == 404:
                print("`%s': remote directory does not exist" % directory)
                exit_status = 1
            else:
                raise e
        else:
            _print_file_list(lst)


def rm(argparse_args):
    """Remove remote object from OwnCloud server."""
    exit_status = 0
    if (argparse_args.yes):
        confirm = False
    else:
        confirm = True
    args = vars(argparse_args)
    file_list = args['file']
    if (len(file_list) > 0):
        client = _client()
        for file_name in file_list:
            dir_node = None
            try:
                dir_node = client.files.by_path(file_name)
            except nc_py_api.NextcloudException as e:
                if e.status_code == 404:
                    print("`%s': no such remote object" % file_name)
                    exit_status = 1
                else:
                    raise e
            else:
                if dir_node.is_dir:
                    if (dir_node.info.size > 0):
                        print("`%s': skipping non-empty directory" % file_name)
                        continue
                if confirm:
                    answer = _query_yes_no("`%s': remove remote object?" % file_name)
                    if answer == "yes":
                        client.files.delete(file_name)
                else:
                    client.files.delete(file_name)
    sys.exit(exit_status)


def mkdir(argparse_args):
    """Make directory on OwnCloud server."""
    args = vars(argparse_args)
    directory_list = args['dir']
    if (len(directory_list) > 0):
        client = _client()
        for directory in directory_list:
            client.files.mkdir(directory)


def check(argparse_args):
    """Try to establish a connection to the OwnCloud server configured in the
    configuration file."""
    try:
        client =_client()
    except nc_py_api.NextcloudException as e:
        if e.status_code == 401:
            print("Authentication failure: please check credentials", file=sys.stderr)
        else:
            raise
    except:
        raise
    else:
        print("OK!")
