# AUTOGENERATED! DO NOT EDIT! File to edit: 00_cli.ipynb (unless otherwise specified).

__all__ = ['ProjectTemplatesManager', 'trigger_prompt', 'wizard_items', 'BaseServiceCLI', 'ProjectsCLI', 'VersionsCLI',
           'LocalServerCLI', 'ServiceCLI', 'get_release', 'Pipeline', 'main']

# Cell
import tightai
from .auth import login as tightai_login, logout as tightai_logout
from .conf import TIGHTAI_LOCAL_DIRECTORY
from .services import Service
from .projects import Project, Version
from .local import local_server
from .utils import print_dict, resolve_path

# Cell
import datetime
import json
import fire
import pathlib
import time
from slugify import slugify
import requests
import io
import tempfile
import os
import tarfile

# Cell
class ProjectTemplatesManager(object):
    download_path = "."
    templates = {}
    template_names = []

    def __init__(self, path=".", *args, **kwargs):
        if path == ".":
            path = resolve_path(path)
        self._path = path
        # dest_dir = tempfile.mkdtemp()
        self.download_path = TIGHTAI_LOCAL_DIRECTORY
        self.path_repo = os.path.join(self.download_path, "project-templates-master")
        self.download_master()
        templates = self.get_templates()
        self.templates = templates
        self.template_names = list(templates.keys())


    def download_master(self):
        download_path = self.download_path
        url = "https://codeload.github.com/tightai/project-templates/tar.gz/master"
        project_templates_tar = pathlib.Path(download_path) / 'project.tar.gz'
        r = requests.get(url)
        with open(project_templates_tar, 'wb') as f:
            f.write(r.content)
        tar = tarfile.open(project_templates_tar)
        tar.extractall(download_path)
        tar.close()
        if project_templates_tar.exists():
            project_templates_tar.unlink()
        return

    def get_templates(self):
        templates = {}
        for (dirpath, dirnames, filenames) in os.walk(self.path_repo):
            for fname in filenames:
                path = os.path.join(dirpath, fname)
                if fname == 'entry.py':
                    parent_name = os.path.basename(os.path.dirname(dirpath))
                    name = os.path.basename(dirpath)
                    if parent_name != "project-templates-master":
                        name = f"{parent_name}-{name}"
                    templates[name] = dirpath
        return templates

    def list_templates(self):
        template_options = self.template_names
        for i, tp in enumerate(template_options):
            x = i + 1
            print(f"{x} - {tp}")

    def select_template(self):
        template_options = self.template_names
        templates = self.templates
        default_option = "empty_project"
        print("\nGithub reference: https://github.com/tightai/project-templates")
        print("\nSelect template:")
        option_count = len(template_options)
        self.list_templates()
        while True:
            option_selected = input("Enter a number: ")
            option_invalid = False
            try:
                option_selected = int(option_selected)
            except:
                option_invalid = True
            if isinstance(option_selected, int):
                if option_selected not in range(1, option_count + 1):
                    option_invalid = True
            if option_invalid == True:
                print(f"Select option between 1 and {option_count}")
                continue
            break
        selected_template_name = template_options[option_selected - 1]
        return selected_template_name

    def download(self, template_name, destination=None):
        dest_path = None
        if destination != None:
            dest_path = resolve_path(destination)
            if not dest_path.exists():
                print(f"{destination} does not exist. Please try again.")
                return
        else:
            dest_path = self._path
        selected_template_path = self.templates[template_name]
        from distutils.dir_util import copy_tree
        print("Adding template...")
        copy_tree(selected_template_path, str(dest_path), preserve_mode=0)
        print("Done. Template downloaded.")

# Cell

wizard_items = [
    {
        "arg_name": "path",
        "prompt": "Path",
        "required": False,
        "slug": False,
        "is_path": True,
        "default": ".",
        "choices": []
    },
    {
        "arg_name": "name",
        "prompt": "Name (optional): ",
        "required": False,
        "slug": False,
        "default": None,
        "choices": []
    },
    {
        "arg_name": "project_id",
        "prompt": "Project id (optional): ",
        "required": False,
        "slug": True,
        "default": None,
        "choices": []
    },
    {
        "arg_name": "version",
        "prompt": "Starting Version",
        "required": False,
        "slug": False,
        "default": 1,
        "dtype": int
    },
    {
        "arg_name": "auto_increment",
        "prompt": "Auto increment version pushes? `Yes/no`",
        "required": False,
        "slug": False,
        "default": "no",
        "choices": ["Yes", "no", 'Y', 'n']
    },
    {
        "arg_name": "use_template",
        "prompt": "Use a project template for setup? `Yes/no`",
        "required": False,
        "slug": False,
        "default": "no",
        "choices": ["Yes", "no", 'Y', 'n']
    },
    {
        "arg_name": "create_entry_py",
        "pre_prompt": "\n\nDownload `entry.py`?\n\t- Default python module for running your serverless project.\n\t- Available on https://www.tight.ai/entry.py (redirects to github.com)\n\t- Run locally with `tight local run`\n",
        "prompt": "Type `Yes/no` to download `entry.py`.",
        "required": True,
        "default": "Yes",
        "choices": ["Yes", "no", 'Y', 'n']
    },

]


def trigger_prompt(data):
    input_result = None
    required = data.get('required') or False
    choices = data.get('choices') or []
    arg_name = data['arg_name']
    dtype = data.get('dtype') or False
    is_path = data.get('is_path') or False
    slug = data.get('slug') or False
    use_template = data.get("use_template") or False
    has_choices = len(choices) > 0
    input_prompt = f"{data['prompt']}"
    default = data.get('default') or None
    if default != None:
        input_prompt = f"{input_prompt} (default: `{default}`): "
    input_prompt = input_prompt.strip()
    pre_prompt = data.get("pre_prompt") or None
    if pre_prompt != None:
        print(pre_prompt)
    input_result = input(f"{input_prompt} ")
    if default != None and (input_result == "" or input_result == None):
            input_result = default
    if slug:
        if input_result:
            slugified_input_result = slugify(input_result)
            if input_result != slugified_input_result:
                print(f"`{input_result}` must be a slugified string such as `{slugified_input_result}`")
                return trigger_prompt(data)
    if required and default == None:
        if len(input_result) == 0:
            print(f"{arg_name} is required.")
            return trigger_prompt(data)
    if has_choices:
        if input_result not in choices:
            if len(choices) == 1:
                choices_string = "".join(choices)
            else:
                choices_part_1 = ", ".join(choices[:-1])
                choices_part_2 = f"or {choices[-1]}"
                choices_string = f"{choices_part_1} {choices_part_2}"
            print(f"`{input_result}` is an invalid choice. Choose from `{choices_string}`")
            return trigger_prompt(data)
    if is_path:
        if input_result.startswith("~/"):
            user_home = pathlib.Path.home()
            input_result = input_result.replace("~", str(user_home))
        input_path = pathlib.Path(input_result)
        path_str = str(input_path.absolute())
        if not input_path.exists():
            print(f"Error: The path `{path_str}` does not exist on your system")
            return trigger_prompt(data)
        config_path = input_path / "tight.json"
        if config_path.exists():
            print(f"Error: `tight.json` already exists in `{path_str}`. Please use a different directory.")
            return trigger_prompt(data)
        print(f"\t- Using `{path_str}` as the project root.")
        return path_str
    if dtype != False:
        try:
            input_result = dtype(input_result)
        except:
            pass
        if not isinstance(input_result, dtype):
            print(f"{input_result} is not a valid {dtype.__name__}")
            return trigger_prompt(data)
        return input_result
    return input_result

# Cell
class BaseServiceCLI(object):
    def __init__(self, p=None, path="."):
        self._project_id = p
        if path == ".":
            path = resolve_path(path)
        self._path = path

    def get_config(self):
        conf = pathlib.Path(self._path) / "tight.json"
        if not conf.exists():
            print(f"tight.json (project details) does not exist in {self._path}")
            return {}
        data = {}
        with open(conf, 'r') as f:
            data = json.loads(f.read())
        return data

    def get_local_project_id(self):
        local_config = self.get_config()
        project_id = local_config.get('project_id')
        return project_id or None

    def get_local_version(self):
        local_config = self.get_config()
        version = local_config.get('version')
        return version or None

    def generate_entry(self, force=False, path=None):
        '''
        Add Tight.ai entry.py to your path. Reference file
        on https://www.tight.ai
        '''
        if path != None:
            path = resolve_path(path)
        else:
            path = self._path
        local_path = pathlib.Path(path)
        entry_py = local_path / "entry.py"
        writable = not entry_py.exists()
        if entry_py.exists() and force == True:
            confirm = input("Are you sure you want to overwrite `entry.py`? This process is irreversible. \n\nType `yes` to confirm or any key to cancel.\n")
            if confirm == "yes":
                print("Overwriting...")
                writable = True
        if writable:
            url = "https://www.tight.ai/entry.py"
            print(f"Downloading `entry.py` from {url}")
            r = requests.get(url)
            if r.status_code not in range(200, 299):
                print("Invalid download. Please try manually")
                return
            with open(entry_py, 'wb') as f:
                f.write(r.content)
                print("\n- entry.py saved.\n\nNow try:\n\ntight local run")
        return

    def save_config(self, data={}, init=False):
        local_path = data.get('path')
        local_path = pathlib.Path(local_path)
        config_path = local_path / "tight.json"
        version = data.get('version') or None
        try:
            version = int(version)
        except:
            pass
        if version == None and init == False:
            print("Invalid version")
            return
        if version != None:
            data["version"] = version
        if not local_path.exists():
            print(f"Unable to save project config. {local_path.absolute()} does not exist.")
            return
        with open(config_path, 'w') as f:
            f.write(json.dumps(data, indent=4))
        return str(config_path.absolute())

# Cell

# def select_template():
#     proj_templates = ProjectTemplatesCLI()
#     template_options = proj_templates.template_names
#     templates = proj_templates.templates
#     default_option = "empty_project"
#     print("\nSelect template:")
#     option_count = len(template_options)
#     for i, tp in enumerate(template_options):
#         x = i + 1
#         print(f"{x} - {tp}")
#     while True:
#         option_selected = input("Enter a number: ")
#         option_invalid = False
#         try:
#             option_selected = int(option_selected)
#         except:
#             option_invalid = True
#         if isinstance(option_selected, int):
#             if option_selected not in range(1, option_count + 1):
#                 option_invalid = True
#         if option_invalid == True:
#             print(f"Select option between 1 and {option_count}")
#             continue
#         break
#     selected_template_name = template_options[option_selected - 1]
#     selected_template_path = templates[selected_template_name]
#     return selected_template_name, selected_template_path

# def download_template(destination, template_name):
#     from distutils.dir_util import copy_tree
#     print("Adding template...")
#     copy_tree(selected_template_path, output_data.get("path"), preserve_mode=0)

# Cell
class ProjectsCLI(BaseServiceCLI):
    """API calls to tight.ai projects"""

    def templates(self, download=False, overwrite=False):
        template_manager = ProjectTemplatesManager(path=self._path)
        if download:
            selected_template = template_manager.select_template()
            destination_path = None
            while True:
                destination = input("Download path (default: '.'): ")
                if destination == '' or destination == None:
                    destination = '.'
                destination_path = pathlib.Path(destination)
                destination_entry = destination_path / 'entry.py'
                if not destination_path.exists():
                    print(f"{destination_path} does not exist on your system")
                    continue
                elif destination_entry.exists() and overwrite == False:
                    print(f"It appears {destination_path} already has a `entry.py` Use `--overwrite` to force download")
                    continue
                elif destination_entry.exists() and overwrite == True:
                    break
                else:
                    break
            if destination_path != None:
                template_manager.download(selected_template, destination=destination_path)

        else:
            template_manager.list_templates()


    def create(self, *args, **kwargs):
        data = {}
        for wizard in wizard_items:
            arg_name = wizard['arg_name']
            use_template = data.get("use_template") or "skip"
            if use_template.lower() == "yes" and arg_name == "create_entry_py":
                continue
            try:
                arg_value = trigger_prompt(wizard)
            except KeyboardInterrupt:
                print(f"\n\nProject create cancelled")
                return
            data[arg_name] = arg_value

        use_template = data.get('use_template') or "no"
        create_entry_py = data.get('create_entry_py') or "no"
        selected_template_name = None
        template_manager = None
        if use_template.lower() == "yes":
            del data['use_template']
            template_manager = ProjectTemplatesManager(path=data.get("path") or ".")
            selected_template_name = template_manager.select_template()
            data['template'] = selected_template_name
        print("\n\nNew project config:\n")
        for k,v in data.items():
            if v == None or v == "":
                v = "----"
            print(f"{k}:", v)
        output_data = {}
        for k, v in data.items():
            v = f"{v}"
            if v.lower() == "no" or v.lower() == "n" or v.lower() == "false":
                v = False
            elif v.lower() == "yes" or v.lower() == "y" or v.lower() == "True":
                v = True
            output_data[k] = v

        while True:
            proceed = input("\n\nProceed? `Yes/no` (default: `Yes`) ")
            if proceed == None or proceed == "":
                proceed = "Yes"
            try:
                proceed = f"{proceed}".lower()
            except:
                pass
            if proceed not in ['yes', 'no']:
                continue
            else:
                break
        proceed = proceed == "yes" or proceed == "y" or proceed == "true" or proceed == ""
        if proceed != True:
            print(f"\n\nProject create cancelled")
            return
        print("Creating..")
        '''
        Create project on API
        '''
        project_id = output_data.get("project_id") or ""
        project_name = output_data.get("name") or ""
        print('project', project_id, project_name)
        new_project = Project.objects.create(id=project_id, name=project_name)
        proj_details = new_project.details()
        new_project_id = proj_details.get('id')
        if new_project_id != None:
            output_data['project_id'] = new_project_id
            del proj_details['id']
        output_data.update(proj_details)
        if output_data.get('create_entry_py') == True:
            self.generate_entry(path=output_data['path'])
        if template_manager != None and selected_template_name != None:
            template_manager.download(selected_template_name)
        config_path_str = self.save_config(data=output_data)
        print(f"{config_path_str} created.")
        return


    def get(self, project_id=None, save=False, path="."):
        '''
        Get project details.
        '''
        if project_id == ".":
            project_id = None
        if project_id == None:
            project_id = self.get_local_project_id()
        if project_id == None:
            print(f"\n\n project_id not found (or supplied). A few options:\n\t - tight project get --project_id <your-project-id>\n\t - tight project get --project_id <your-project-id> --save\n\t - tight projects create\n\t - tight projects list")
            return
        project = Project.objects.get(project_id=project_id)
        proj_data = project.details()
        if save:
            proj_data['path'] = path
            proj_data['project_id'] = proj_data.get('id')
            try:
                del proj_data['id']
            except:
                pass
            config_path_str = self.save_config(data=proj_data, init=True)
            print(f"Project saved. Details are here:\n{config_path_str}")

    def list(self):
        '''
        List all available projects.
        '''
        projects = Project.objects.all()
        for p in projects:
            p.summary(verbose=1)
            print('\n')

    def search(self, query):
        '''
        Search all available projects.
        '''
        print(f"Searching... {query}")

    def delete(self):
        '''
        Delete projects.
        '''
        project_id = self.get_local_project_id()
        confirm = input(f"Delete {project_id}?\n\nThis cannot be reversed.\n\nEnter the following to confirm:\n\n{project_id}\n\n")
        if confirm != project_id:
            print(f"\nError:\n`{confirm}` is does not match.\n\nPlease try again.")
            return


class VersionsCLI(BaseServiceCLI):
    def get(self, project_id=None, v=None, lookup=True, show_details=True):
        '''
        Get current version details.
        '''
        version = None
        config = self.get_config()
        if project_id == ".":
            project_id = None
        if v != None:
            try:
                version = int(v)
            except:
                print("Invalid version")
                return
            config["version"] = version
        if project_id == None:
            project_id = config['project_id']
        if version == None:
            if 'version' in config:
                version = config['version']
            else:
                version = 1
        if lookup == False:
            return config, project_id, version
        version_obj = Version.objects.get(project_id=project_id, version=version)
        if show_details:
            version_obj.details()
        return config, version_obj

    def open(self, project_id=None, v=None):
        _, version_obj = self.get(project_id=project_id, v=v, lookup=True, show_details=False)
        version_obj.open()

    def create(self, project_id=None, v=None):
        config, project_id, version = self.get(project_id=project_id, v=v, lookup=False, show_details=False)
        new_version, created = Version.objects.get_or_create(version=version, project_id=project_id)
        if not created:
            print(f"Version {version} already exists for project `{project_id}`.")
        new_version.details()
        prev_versions = config.get('versions') or []
        if version not in prev_versions:
            prev_versions.append(version)
        prev_versions = sorted(prev_versions)
        config['versions'] = prev_versions
        config["version_details"] = {
                "version": version,
                "status": "building",
        }
        config_path_str = self.save_config(data=config)
        print(f"{config_path_str} updated.")
        print(f"{project_id} v{version} created.")

    def status(self, project_id=None, v=None):
        '''
        Refresh version deployment status.
        '''
        config, version_obj = self.get(project_id=project_id, v=v, lookup=True, show_details=False)
        version = version_obj.version
        print("Checking status...")
        status_data = version_obj.status(display=False)
        url = status_data.get('url') or "false"
        if f"{url}".lower() == "false":
            status_data["url"] = None
        version_details = config.get("version_details") or {}
        version_details.update(status_data)
        version_details['version'] = version
        version_details["last_checked"] = time.time()
        config["version_details"] = version_details
        print_dict(version_details)
        config_path_str = self.save_config(data=config)
        print(f"{config_path_str} Updated.")

    def refresh(self, project_id=None, v=None):
        '''
        Refresh version deployment status.
        '''
        return self.status(project_id=project_id, v=v)

    def download(self,project_id=None, v=None, path=None, overwrite=False):
        config, version_obj = self.get(project_id=project_id, v=v, lookup=True, show_details=False)
        if path != None:
            path = resolve_path(path)
        else:
            path = self._path
        version_obj.download(path=path, overwrite=overwrite)

    def pull(self, project_id=None, v=None, path=None):
        return self.download(project_id=None, v=None, path=None)

    def push(self, project_id=None, v=None, path=None, increment=None):
        '''
        Get project details.
        '''
        config, project_id, version = self.get(project_id=project_id, v=v, lookup=False)
        increment = config.get('auto_increment') or increment or False
        old_version = -1
        if increment == True:
            old_version = version
            version = version
            try:
                version = int(version) + 1
            except:
                print("There was an error automatically incrementing this version")

        if path != None:
            path = resolve_path(path)
        else:
            path = self._path
        print(f"Pushing {project_id} v{version} from {path}")
        new_version, _ = Version.objects.get_or_create(version=version, project_id=project_id)
        new_version.push(pathlib.Path(path))
        prev_versions = config.get('versions') or []
        if increment and old_version != -1:
            config["version"] = version
            config['last_version'] = old_version
            prev_versions.append(old_version)
        if version not in prev_versions:
            prev_versions.append(version)
        prev_versions = sorted(prev_versions)
        config['versions'] = prev_versions
        config["version_details"] = {
                "version": version,
                "status": "building",
                "last_push": time.time(),
                "last_checked": time.time(),
        }
        config_path_str = self.save_config(data=config)
        print(f"{config_path_str} Updated.")



class LocalServerCLI(object):
    def __init__(self, path="."):
        if path == ".":
            path = resolve_path(path)
        self._path = path

    def run(self, path=None, host='localhost', port=5007):
        if path != None:
            path = resolve_path(path)
        else:
            path = self._path
        print(f"Local tight.ai development server* starting on:\n\nhttp://{host}:{port}/\n\n*Dev server is not recommend for production.")
        local_server(path=path, port=port)

# Cell

class ServiceCLI(object):
    def list(self, *args, **kwargs):
        print("Please go to https://tight.ai/services to view and enable all available services")

    def get(self, *args, **kwargs):
        print("Please go to https://tight.ai/services to view and enable all available services")

    def predict(self, slug, data=None, file=None, is_json=True, **kwargs):
        import json as jsonlib
        if slug == None:
            print("You must include a service slug")
            return
        json_data = {}
        raw_data = {}
        if is_json:
            json_data = data
        else:
            raw_data = data
        service_obj = Service.objects.get(slug=slug)
        files = {}
        if file != None:
            files['file'] = open(file, 'rb')
        response = service_obj.predict(json=json_data, files=files, data=raw_data)
        if response.status_code not in range(200, 299):
            print("Prediction failed. Please check the docs or try again later")
            return
        response_data = None
        try:
            response_data = response.json()
        except:
            response_data = response.body
        return response_data

# Cell

def get_release():
    """
    Returns current version release of the Tight.ai library.
    """
    return tightai.__version__

# Cell
class Pipeline(object):
    def __init__(self, path="."):
        destination_path = resolve_path(path)
        self.destination_path = destination_path
        self.projects = ProjectsCLI(path=destination_path)
        self.project = ProjectsCLI(path=destination_path)
        self.versions = VersionsCLI(path=destination_path)
        self.version = VersionsCLI(path=destination_path)
        self.predict = ServiceCLI().predict
        self.services = ServiceCLI()
        self.service = ServiceCLI
        self.local = LocalServerCLI(path=destination_path)
        self.login = tightai_login
        self.logout = tightai_logout
        self.release= get_release()


# Cell
def main():
    fire.Fire(Pipeline)