#!python

from __future__ import print_function
import os, argparse, json, dotenv
import inquirer as inq

GIT_CODE_URL="https://github.com/giesekow/dcm-processor.git"


def parse_args():
  parent_parser = argparse.ArgumentParser(description='A Command line tool for the dicom processor library', add_help=False)
  parent_parser.add_argument('action', metavar='action', type=str, help='action to be performed, options [create, init, install, remove, backup, build]')
  parent_parser.add_argument('object', metavar='object', type=str, help='action object, options [service, app]')
  

  args = parent_parser.parse_args()
  return args

def run():
  args = parse_args()
  action = args.action

  action_lower = str(action).lower()

  if action_lower == "create":
    handle_create(args)
  elif action_lower == "init":
    handle_init(args)
  elif action_lower == "install":
    handle_install(args)
  elif action_lower == "remove":
    handle_remove(args)
  elif action_lower == "backup":
    handle_backup(args)
  elif action_lower == "set":
    handle_set(args)
  elif action_lower == "start":
    handle_start(args)
  elif action_lower == "stop":
    handle_stop(args)
  elif action_lower == "restart":
    handle_restart(args)
  else:
    print("Command not implemented!")

def write_json(data, filename):
  with open(filename, "w") as fp:
    json.dump(data, fp, indent=2)

def read_json(filename):
  with open(filename, "r") as fp:
    return json.load(fp)

  return None

def create_default_config():
  home_dir = os.path.expanduser("~")
  config_path = os.path.join(home_dir, ".dcm-processor", "config.json")
  os.system(f"mkdir -p {os.path.join(home_dir, '.dcm-processor')}")
  config = {
    "apps": {}
  }
  write_json(config, config_path)

def load_config(key=None):
  home_dir = os.path.expanduser("~")
  config_path = os.path.join(home_dir, ".dcm-processor", "config.json")
  config = {}

  if os.path.isfile(config_path):
    config = read_json(config_path)
  else:
    create_default_config()
    config = read_json(config_path)

  data = config

  if not key is None:
    if isinstance(key, str):
      ks = key.split(".")
      for k in ks:
        if not data is None:
          data = data.get(k)

  return data

def update_config(key, data):
  home_dir = os.path.expanduser("~")
  config_path = os.path.join(home_dir, ".dcm-processor", "config.json")
  config = {}

  if os.path.isfile(config_path):
    config = read_json(config_path)
  else:
    create_default_config()
    config = read_json(config_path)

  config[key] = data

  write_json(config, config_path)




def handle_create(args):
  object_lower = str(args.object).lower()
  if object_lower == "service":
    create_service(args)
  if object_lower == "app":
    create_app(args)

def create_app(args):
  questions = [
    inq.Text("app_name", message="Enter app name"),
    inq.Path("app_path", message="Enter app base folder", exists=True, path_type=inq.Path.DIRECTORY, normalize_to_absolute_path=True),
    inq.Path("mapped_path", message="Enter app mapped folder base", exists=False, path_type=inq.Path.DIRECTORY, normalize_to_absolute_path=True),
  ]
  
  answers = inq.prompt(questions)
  app_path = answers['app_path']
  mapped_path = answers['mapped_path']
  app_name = answers['app_name']

  existing_apps = load_config("apps")

  if app_name in existing_apps:
    print(f"There exist a configured app with name '{app_name}'")

  base_path = os.path.abspath(os.path.join(app_path, app_name))
  code_base_path = os.path.join(base_path)

  os.system(f"mkdir -p {os.path.join(base_path)}")
  os.system(f"git clone {GIT_CODE_URL} {code_base_path}")

  dotenv.set_key(os.path.join(code_base_path, ".env"), "BASEDIR", os.path.abspath(mapped_path))

  app_config = {
    "name": app_name,
    "base_dir": base_path
  }

  existing_apps[app_name] = app_config
  update_config("apps", existing_apps)

  init_app(args, app_name)

def create_service(args):
  questions = [
    inq.Text("service_name", message="Enter service name"),
    inq.Path("service_path", message="Enter service folder", exists=True, path_type=inq.Path.DIRECTORY, normalize_to_absolute_path=True),
    inq.Text("service_description", message="Enter service description"),
  ]

  answers = inq.prompt(questions)
  service_name = answers['service_name']

  fullpath = os.path.join(answers['service_path'], answers['service_name'])
  modulePath = os.path.join(fullpath, "module")
  registryPath = os.path.join(fullpath, "registry")

  os.system(f"mkdir -p {modulePath}")
  os.system(f"mkdir -p {registryPath}")

  os.system(f"echo 'from .main import callback' > {os.path.join(registryPath, '__init__.py')}")

  os.system(f"echo 'from .main import worker' > {os.path.join(modulePath, '__init__.py')}")
  os.system(f"touch {os.path.join(modulePath, 'requirements.txt')}")
  os.system(f"touch {os.path.join(modulePath, 'script.sh')}")

  callback_code = [
    '\n',
    'def callback(jobName, headers, params, added_params, **kwargs):\n',
    '\tinjected_params = {"custom-data": "Some new data"}\n',
    '\n',
    '\t# Check header information etc. to see if you have to execute job\n'
    '\t# If Yes return True with the additional injected params\n'
    '\t# If No return False with additional injected params\n'
    '\n'
    '\treturn True, injected_params'
  ]
  worker_code = [
    'import os\n',
    '\n',
    'DATA = os.getenv("DATA")\n',
    'LOGS = os.getenv("LOGS")\n',
    'MODULES = os.getenv("MODULE")\n',
    '\n',
    'def worker(jobName, headers, params, added_params, **kwargs):\n',
    '\tprint(f"{jobName} can be handled here")\n',
    '\tprint(f"I can log info into the {LOGS} folder")\n',
    '\tprint(f"I can write and read from the {DATA} folder")\n',
    '\tprint(f"I can access other service modules from the {MODULES} folder")\n'
  ]
  settings = [
    {
      "jobName": service_name,
      "worker": f"{service_name}.worker",
      "callback": f"{service_name}.callback",
      "dependsOn": None,
      "priority": "default",
      "timeout": "1h",
      "params": {},
      "sortPosition": 0,
      "description": answers['service_description']
    }
  ]

  with open(os.path.join(registryPath, 'main.py'), 'w') as txt:
    txt.writelines(callback_code)

  with open(os.path.join(registryPath, 'settings.json'), 'w') as txt:
    json.dump(settings, txt, indent=2)

  with open(os.path.join(modulePath, 'main.py'), 'w') as txt:
    txt.writelines(worker_code)




def handle_init(args):
  object_lower = str(args.object).lower()
  if object_lower == "app":
    init_app(args)

def init_app(args, app_name=None):
  existing_apps = load_config("apps")

  if len(existing_apps.keys()) == 0:
    print("There are no existing apps")
    return

  if app_name is None:
    questions = [
      inq.List("app_name", message="Select app to initial", choices=list(existing_apps.keys())),
    ]

    answers = inq.prompt(questions)
    app_name = answers['app_name']

  config = existing_apps.get(app_name)

  if config is None:
    print("Unable to load app configuration!")
    return

  base_dir = config.get("base_dir")
    
  if base_dir is None:
    print("Error in configuration filename")
    return

  build_script = os.path.join(base_dir, "build.sh")
  build_volumes_script = os.path.join(base_dir, "build_volumes.sh")
  run_script = os.path.join(base_dir, "run.sh")
  init_script = os.path.join(base_dir, "init.sh")
  stop_script = os.path.join(base_dir, "stop.sh")

  if not os.path.isfile(build_script):
    print("Missing build script!")
    return

  if not os.path.isfile(build_volumes_script):
    print("Missing build_volumes script!")
    return

  if not os.path.isfile(run_script):
    print("Missing run script!")
    return

  if not os.path.isfile(init_script):
    print("Missing init script!")
    return

  if not os.path.isfile(stop_script):
    print("Missing stop script!")
    return

  
  proxies = load_config("proxies")
  if not proxies is None:
    # Patch build script
    code_lines = ["#!/bin/bash\n","\n", "docker-compose build"] + [f" --build-arg {k}={proxies[k]}" for k in proxies.keys()] + [" --force-rm\n"] + ["docker-compose pull\n"]
    with open(build_script, "w") as sc:
      sc.writelines(code_lines)

  cwd = os.getcwd()

  os.chdir(os.path.join(base_dir))
  os.system(f"bash build_volumes.sh && bash build.sh && bash run.sh && bash init.sh && bash stop.sh")
  os.chdir(os.path.join(cwd))




def handle_install(args):
  object_lower = str(args.object).lower()
  if object_lower == "service":
    install_service(args)

def install_service(args):
  existing_apps = load_config("apps")
  if len(existing_apps.keys()) == 0:
    print("There are no existing apps")
    return

  questions = [
    inq.List("app_name", message="Select app to install service", choices=list(existing_apps.keys())),
    inq.Text("service_name", message="Enter service name"),
    inq.Path("service_path", message="Enter service folder", exists=True, path_type=inq.Path.DIRECTORY, normalize_to_absolute_path=True),
  ]

  answers = inq.prompt(questions)
  app_name = answers['app_name']
  service_name = answers['service_name']
  service_path = answers['service_path']

  config = existing_apps.get(app_name)

  if config is None:
    print("Unable to load app configuration!")
    return

  base_dir = config.get("base_dir")
    
  if base_dir is None:
    print("Error in configuration filename")
    return

  service_script = os.path.join(base_dir, "service.sh")

  if not os.path.isfile(service_script):
    print("Missing build script!")
    return

  service_dir = os.path.abspath(service_path)

  cwd = os.getcwd()

  os.chdir(os.path.abspath(os.path.join(base_dir)))
  os.system(f"bash service.sh install {service_name} -p {service_dir}")
  os.chdir(cwd)




def handle_remove(args):
  object_lower = str(args.object).lower()
  if object_lower == "service":
    remove_service(args)
  if object_lower == "app":
    remove_app(args)

def remove_app(args):
  existing_apps = load_config("apps")

  if len(existing_apps.keys()) == 0:
    print("There are no existing apps")
    return

  questions = [
    inq.List("app_name", message="Select app to install service", choices=list(existing_apps.keys())),
    inq.List("remove_base_dir", message="Remove app base directory ?", choices=["Yes", "No"]),
    inq.List("remove_mapped_dir", message="Remove app mapped folders directory ?", choices=["Yes", "No"]),
    inq.Text("backup_path", message="Enter backup folder (leave empty to skip backup)")
  ]

  answers = inq.prompt(questions)
  app_name = answers['app_name']
  backup_path = answers['backup_path']
  remove_base = answers['remove_base_dir']
  remove_mapped = answers['remove_mapped_dir']

  config = existing_apps.get(app_name)

  if config is None:
    print("Unable to load app configuration!")
    return

  base_dir = config.get("base_dir")

  del existing_apps[app_name]
  update_config("apps", existing_apps)
    
  if base_dir is None:
    print("Error in configuration filename")
    return

  clean_script = os.path.join(base_dir, "clean.sh")

  if not os.path.isfile(clean_script):
    print("Missing clean script!")
    return

  env_file = os.path.join(base_dir, ".env")
  if not os.path.isfile(env_file):
    print("Missing .env file!")
    return

  dotenv.load_dotenv(os.path.abspath(os.path.join(base_dir, '.env')))
  mapped_folder = os.environ.get("BASEDIR")

  if backup_path != "":
    backup_dir = os.path.abspath(backup_path)
    os.system(f"mkdir -p {backup_dir}")
    os.system(f"cp -r {os.path.abspath(os.path.join(base_dir))} {os.path.join(backup_dir, app_name)}")

    if not mapped_folder is None:
      os.system(f"cp -r {os.path.abspath(mapped_folder)} {os.path.join(backup_dir, app_name, 'mapped_folder')}")

  cwd = os.getcwd()

  os.chdir(os.path.abspath(os.path.join(base_dir)))
  os.system(f"bash clean.sh")
  os.chdir(cwd)

  if remove_base == "Yes":
    os.system(f"rm -rf {os.path.abspath(os.path.join(base_dir))}")
  
  if remove_mapped == "Yes":
    os.system(f"rm -rf {os.path.abspath(os.path.join(mapped_folder))}")

def remove_service(args):
  existing_apps = load_config("apps")
  if len(existing_apps.keys()) == 0:
    print("There are no existing apps")
    return

  questions = [
    inq.List("app_name", message="Select app to install service", choices=list(existing_apps.keys())),
    inq.Text("service_name", message="Enter service name"),
    inq.Text("backup_path", message="Enter backup folder (leave empty to skip backup)")
  ]

  answers = inq.prompt(questions)
  app_name = answers['app_name']
  service_name = answers['service_name']
  service_path = answers['backup_path']

  config = existing_apps.get(app_name)

  if config is None:
    print("Unable to load app configuration!")
    return

  base_dir = config.get("base_dir")
    
  if base_dir is None:
    print("Error in configuration filename")
    return

  service_script = os.path.join(base_dir, "service.sh")

  if not os.path.isfile(service_script):
    print("Missing service script!")
    return

  

  cwd = os.getcwd()
  os.chdir(os.path.abspath(os.path.join(base_dir)))

  if service_path != "":
    service_dir = os.path.abspath(service_path)
    os.system(f"bash service.sh remove {service_name} -b {service_dir}")
  else:
    os.system(f"bash service.sh remove {service_name}")

  os.chdir(cwd)




def handle_backup(args):
  object_lower = str(args.object).lower()
  if object_lower == "service":
    backup_service(args)
  if object_lower == "app":
    backup_app(args)

def backup_service(args):
  existing_apps = load_config("apps")

  if len(existing_apps.keys()) == 0:
    print("There are no existing apps")
    return

  questions = [
    inq.List("app_name", message="Select app to install service", choices=list(existing_apps.keys())),
    inq.Text("service_name", message="Enter service name"),
    inq.Text("backup_path", message="Enter backup folder")
  ]

  answers = inq.prompt(questions)
  app_name = answers['app_name']
  service_name = answers['service_name']
  service_path = answers['backup_path']

  config = existing_apps.get(app_name)

  if config is None:
    print("Unable to load app configuration!")
    return

  base_dir = config.get("base_dir")
    
  if base_dir is None:
    print("Error in configuration filename")
    return

  service_script = os.path.join(base_dir, "service.sh")

  if not os.path.isfile(service_script):
    print("Missing service script!")
    return

  if service_path != "":
    service_dir = os.path.abspath(service_path)
    cwd = os.getcwd()
    os.chdir(os.path.abspath(os.path.join(base_dir)))
    os.system(f"bash service.sh backup {service_name} -b {service_dir}")
    os.chdir(cwd)
  else:
    print("Backup path not provided!")
    return

def backup_app(args):
  existing_apps = load_config("apps")

  if len(existing_apps.keys()) == 0:
    print("There are no existing apps")
    return

  questions = [
    inq.List("app_name", message="Select app to install service", choices=list(existing_apps.keys())),
    inq.Text("backup_path", message="Enter backup folder")
  ]

  answers = inq.prompt(questions)
  app_name = answers['app_name']
  backup_path = answers['backup_path']

  config = existing_apps.get(app_name)

  if config is None:
    print("Unable to load app configuration!")
    return

  base_dir = config.get("base_dir")
    
  if base_dir is None:
    print("Error in configuration filename")
    return

  env_file = os.path.join(base_dir, ".env")
  if not os.path.isfile(env_file):
    print("Missing .env file!")
    return

  dotenv.load_dotenv(os.path.abspath(os.path.join(base_dir, '.env')))
  mapped_folder = os.environ.get("BASEDIR")

  if backup_path != "":
    backup_dir = os.path.abspath(backup_path)
    os.system(f"mkdir -p {backup_dir}")
    os.system(f"cp -r {os.path.abspath(os.path.join(base_dir))} {os.path.join(backup_dir, app_name)}")

    if not mapped_folder is None:
      os.system(f"cp -r {os.path.abspath(mapped_folder)} {os.path.join(backup_dir, app_name, 'mapped_folder')}")




def handle_start(args):
  object_lower = str(args.object).lower()
  if object_lower == "app":
    start_app(args)

def start_app(args, app_name=None):
  existing_apps = load_config("apps")

  if len(existing_apps.keys()) == 0:
    print("There are no existing apps")
    return

  if app_name is None:
    questions = [
      inq.List("app_name", message="Select app to start", choices=list(existing_apps.keys())),
    ]

    answers = inq.prompt(questions)
    app_name = answers['app_name']

  config = existing_apps.get(app_name)

  if config is None:
    print("Unable to load app configuration!")
    return

  base_dir = config.get("base_dir")
    
  if base_dir is None:
    print("Error in configuration filename")
    return

  run_script = os.path.join(base_dir, "run.sh")

  if not os.path.isfile(run_script):
    print("Missing run script!")
    return

  cwd = os.getcwd()

  os.chdir(os.path.join(base_dir))
  os.system(f"bash run.sh")
  os.chdir(os.path.join(cwd))




def handle_stop(args):
  object_lower = str(args.object).lower()
  if object_lower == "app":
    stop_app(args)

def stop_app(args, app_name=None):
  existing_apps = load_config("apps")

  if len(existing_apps.keys()) == 0:
    print("There are no existing apps")
    return

  if app_name is None:
    questions = [
      inq.List("app_name", message="Select app to start", choices=list(existing_apps.keys())),
    ]

    answers = inq.prompt(questions)
    app_name = answers['app_name']

  config = existing_apps.get(app_name)

  if config is None:
    print("Unable to load app configuration!")
    return

  base_dir = config.get("base_dir")
    
  if base_dir is None:
    print("Error in configuration filename")
    return

  stop_script = os.path.join(base_dir, "stop.sh")

  if not os.path.isfile(stop_script):
    print("Missing stop script!")
    return

  cwd = os.getcwd()

  os.chdir(os.path.join(base_dir))
  os.system(f"bash stop.sh")
  os.chdir(os.path.join(cwd))




def handle_restart(args):
  object_lower = str(args.object).lower()
  if object_lower == "app":
    restart_app(args)

def restart_app(args, app_name=None):
  existing_apps = load_config("apps")

  if len(existing_apps.keys()) == 0:
    print("There are no existing apps")
    return

  if app_name is None:
    questions = [
      inq.List("app_name", message="Select app to start", choices=list(existing_apps.keys())),
    ]

    answers = inq.prompt(questions)
    app_name = answers['app_name']

  config = existing_apps.get(app_name)

  if config is None:
    print("Unable to load app configuration!")
    return

  base_dir = config.get("base_dir")
    
  if base_dir is None:
    print("Error in configuration filename")
    return

  run_script = os.path.join(base_dir, "run.sh")

  if not os.path.isfile(run_script):
    print("Missing run script!")
    return

  cwd = os.getcwd()

  os.chdir(os.path.join(base_dir))
  os.system(f"bash run.sh")
  os.chdir(os.path.join(cwd))




def handle_set(args):
  object_lower = str(args.object).lower()
  if object_lower == "proxy":
    set_proxy(args)
  

def set_proxy(args):
  questions = [
    inq.Text("http_proxy", message="Enter http proxy"),
    inq.Text("https_proxy", message="Enter https proxy"),
    inq.Text("ftp_proxy", message="Enter ftp proxy"),
    inq.Text("no_proxy", message="Enter no proxy urls (comma separated)"),
  ]

  answers = inq.prompt(questions)
  
  p_settings = {
    "http_proxy": answers["http_proxy"],
    "https_proxy": answers["https_proxy"],
    "ftp_proxy": answers["ftp_proxy"],
    "no_proxy": answers["no_proxy"],
  }

  update_config("proxies", p_settings)
  print("Proxy settings updated!")
  
def set_trusted_hosts(args):
  questions = [
    inq.Text("trusted_hosts", message="Enter pip trusted hosts"),
  ]

  answers = inq.prompt(questions)
  
  th = answers["trusted_hosts"]
  th = str(th).split(",")
  
  update_config("trusted_hosts", th)
  print("Trusted hosts updated!")


if __name__ == "__main__":
  run()