#!python

from __future__ import print_function
import os, argparse, json, dotenv, sys
import inquirer as inq
from ruamel.yaml import YAML

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)
  if object_lower == "worker":
    create_worker(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),
    inq.List("init_app", message="Initialize app after creation ?", choices=["Yes", "No"]),
  ]
  
  answers = inq.prompt(questions)
  app_path = answers['app_path']
  mapped_path = answers['mapped_path']
  app_name = answers['app_name']
  initapp = answers['init_app']

  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)

  if initapp == "Yes":
    init_app(args, app_name)

def create_worker(args):
  questions = [
    inq.Text("worker_name", message="Enter worker name"),
    inq.Path("worker_path", message="Enter workers folder", exists=True, path_type=inq.Path.DIRECTORY, normalize_to_absolute_path=True),
    inq.Text("worker_base_image", message="Enter worker base image", default="python:3.7"),
    inq.Text("worker_description", message="Enter worker description"),
  ]

  answers = inq.prompt(questions)
  worker_name = answers['worker_name']
  base_image = answers["worker_base_image"]

  fullpath = os.path.join(answers['worker_path'], answers['worker_name'])
  os.system(f"mkdir -p {fullpath}")
  
  os.system(f"touch {os.path.join(fullpath, 'requirements.txt')}")

  files = {
    "Dockerfile": [
      f"FROM {base_image}\n"
      "\n"
      "WORKDIR /app\n"
      "\n"
      "# Copy script and requirement files\n"
      "COPY requirements.txt ./\n"
      "COPY entrypoint.sh ./\n"
      "COPY requirements.sh ./\n"
      "COPY script.sh ./\n"
      "\n"
      "# Install all required dependencies\n"
      "\n"
      "RUN apt-get update -y && \\ \n"
          "\tpython -m pip install click==7.1.2 rq watchdog argh pyyaml --trusted-host=pypi.python.org --trusted-host=pypi.org --trusted-host=files.pythonhosted.org --default-timeout=100 && \\ \n"
          "\tpython -m pip install -r requirements.txt --trusted-host=pypi.python.org --trusted-host=pypi.org --trusted-host=files.pythonhosted.org --default-timeout=100\n"
      "\n"
      'CMD ["bash", "entrypoint.sh"]\n'
    ],
    "entrypoint.sh": [
      "#!/bin/bash\n"
      "\n"
      'REQUIREMENTS="$MODULES/*/requirements.txt"\n'
      'SLOGS="$LOGS/shell.txt"\n'
      'PLOGS="$LOGS/pip.txt"\n'
      'SCRIPTS="$MODULES/*/script.sh"\n'
      "\n"
      "\n"
      "for SCRIPT in $(ls $SCRIPTS)\n"
      "do\n"
        '\tbash "$SCRIPT"\n'
      "done\n"
      "\n"
      "for REQUIREMENT in $(ls $REQUIREMENTS)\n"
      "do\n"
        '\tpython -m pip install -r "$REQUIREMENT" --trusted-host=pypi.python.org --trusted-host=pypi.org --trusted-host=files.pythonhosted.org --default-timeout=100\n'
      "done\n"
      "\n"
      'watchmedo shell-command -w -p="$REQUIREMENTS" -R -D -c \' requirements.sh "${watch_event_type} ${watch_src_path}" >> $PLOGS \' &\n'
      "\n"
      'watchmedo shell-command -w -p="$SCRIPTS" -R -D -c \' script.sh "${watch_event_type} ${watch_src_path}" >> $SLOGS \' &\n'
      "\n"
      'rq worker --path "$MODULES" -u "redis://:$REDIS_PSWD@$REDIS_HOST:$REDIS_PORT"  $JOBS\n'
    ],
    "requirements.sh": [
      "#!/bin/bash\n"
      "\n"
      'if [ $1 = "closed" ]\n'
      "then\n"
        '\tpython -m pip install -r "$2" --trusted-host=pypi.python.org --trusted-host=pypi.org --trusted-host=files.pythonhosted.org --default-timeout=100\n'
      "fi\n"
    ],
    "script.sh": [
      "#!/bin/bash\n"
      "\n"
      'if [ $1 = "closed" ]\n'
      "then\n"
        '\tbash "$2"\n'
      "fi\n"
    ]
  }

  for fn in files:
    with open(os.path.join(fullpath, fn), 'w') as txt:
      txt.writelines(files[fn])

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)
  if object_lower == "worker":
    install_worker(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 to", 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 install_worker(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 worker to", choices=list(existing_apps.keys())),
    inq.Text("worker_name", message="Enter worker name"),
    inq.Path("worker_path", message="Enter worker folder", exists=True, path_type=inq.Path.DIRECTORY, normalize_to_absolute_path=True),
    inq.Text("worker_priority", message="Enter worker job priority (comman separated)"),
    inq.List("config_gpu", message="Configure GPU Device Reservation ?", choices=["Yes", "No"], default="No"),
  ]

  answers = inq.prompt(questions)
  app_name = answers['app_name']
  worker_name = answers['worker_name']
  worker_path = answers['worker_path']
  worker_priority = answers['worker_priority']
  config_gpu = answers['config_gpu']
  device_config = {}

  if config_gpu == "Yes":
    device_answers = inq.prompt([
      inq.Text("device_cap", message="Enter device capabilities (comman separated list)", default="gpu"),
      inq.Text("driver", message="Enter device driver", default="nvidia"),
      inq.List("is_deviceid_or_count", message="How would you want to configure the reservation ?", choices=["Device Count", "Device ID"], default="Device Count"),
    ])

    caps = device_answers["device_cap"]
    driv = device_answers["driver"]
    is_dev_or_count = device_answers['is_deviceid_or_count']

    caps = str(caps).split(",")

    if len(caps) > 0:
      device_config["capabilities"] = caps

    if driv != "":
      device_config["driver"] = driv  

    if is_dev_or_count == "Device Count":
      count_answers = inq.prompt([
        inq.Text("count", message="Enter Device Count (integer) leave empty to use all devices"),
        inq.Text("device_options", message="Enter driver specific options (comma seperated key:value pairs)"),
      ])

      cnt = count_answers["count"]
      opts = count_answers["device_options"]

      if cnt != "":
        device_config["count"] = int(cnt)
      else:
        device_config["count"] = "all"
        
      if opts != "":
        opts = str(opts).split(",")         
        for opt in opts:
          keyval = opt.split(":")
          if len(keyval) > 1:

            if not "options" in device_config:
              device_config["options"] = {}
            
            device_config[keyval[0]] = keyval[1]

    else:
      deviceid_answers = inq.prompt([
        inq.Text("deviceids", message="Enter Device IDs (comman separated list)"),
        inq.Text("device_options", message="Enter driver specific options (comma seperated key:value pairs)"),
      ])

      devids = count_answers["deviceids"]
      opts = count_answers["device_options"]

      if devids != "":
        device_config["device_ids"] = str(devids).split(",")
        
      if opts != "":
        opts = str(opts).split(",")         
        for opt in opts:
          keyval = opt.split(":")
          if len(keyval) > 1:

            if not "options" in device_config:
              device_config["options"] = {}
            
            device_config[keyval[0]] = keyval[1]

  worker_name = str(worker_name).lower()

  if worker_name == "worker":
    print("Worker name cannot be 'worker'")
    return

  config = existing_apps.get(app_name)

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

  existing_workers = config.get("workers")

  if existing_workers is None:
    existing_workers = {}

  if worker_name in existing_workers:
    print(f"Worker with name {worker_name} already exist remove worker first!")
    return

  base_dir = config.get("base_dir")

  workers_dir = os.path.join(base_dir, "workers")
  os.system(f"mkdir -p {workers_dir}")

  # check required files
  dockerfile = os.path.join(worker_path, "Dockerfile")

  if not os.path.isfile(dockerfile):
    print("Worker missing Dockerfile!")
    return

  os.system(f"cp -r {os.path.join(worker_path)} {os.path.join(workers_dir, worker_name)}")

  docker_compose_config = {
    'build': f"./workers/{worker_name}",
    'depends_on': ['redis_server'],
    'environment': {
      'REDIS_PSWD': '${REDIS_PSWD}',
      'REDIS_HOST': '${REDIS_HOST}',
      'REDIS_PORT': '${REDIS_PORT}',
      'JOBS': f"{worker_priority if worker_priority != '' else 'default'}",
      'ORTHANC_REST_USERNAME': '${ORTHANC_REST_USERNAME}',
      'ORTHANC_REST_PASSWORD': '${ORTHANC_REST_PASSWORD}',
      'ORTHANC_REST_URL': '${ORTHANC_REST_URL}',
      'ORTHANC_DEFUALT_STORE': '${ORTHANC_DEFUALT_STORE}',
      'CLEAN_ORTHANC': '${CLEAN_ORTHANC}',
      'DATA': '/data',
      'MODULES': '/modules',
      'LOGS': '/logs'
    },
    'volumes': [
      f"./workers/{worker_name}:/app",
      '${BASEDIR}${MODULES}:/modules:cached',
      '${BASEDIR}${DATA}:/data:rw',
      '${BASEDIR}${LOGS}:/logs:rw'
    ]
  }

  if config_gpu == "Yes" and "capabilities" in device_config:
    docker_compose_config["deploy"] = {
      "resources": {
        "reservations": {
          "devices": [device_config]
        }
      }
    }


  yaml = YAML(typ="safe", pure=True)
  yaml.default_flow_style = False
  data = {}
  with open(os.path.join(base_dir, "docker-compose.yml"), 'r') as f: 
    data = yaml.load(f)
  
  with open(os.path.join(base_dir, "docker-compose.yml"), 'w') as f: 
    data["services"][str(worker_name)] = docker_compose_config
    yaml.dump(data, f)

  existing_workers[worker_name] = {
    "base_dir": worker_name,
    "priority": worker_priority
  }


  config["workers"] = existing_workers
  existing_apps[app_name] = config
  update_config("apps", existing_apps)
  print(f"Worker successfully install as {worker_name}")



def handle_remove(args):
  object_lower = str(args.object).lower()
  if object_lower == "service":
    remove_service(args)
  if object_lower == "app":
    remove_app(args)
  if object_lower == "worker":
    remove_worker(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 remove", 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 remove service from", 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 remove_worker(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 remove worker from", choices=list(existing_apps.keys())),
    inq.Text("worker_name", message="Enter worker name"),
    inq.Text("backup_path", message="Enter backup folder (leave empty to skip backup)")
  ]

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

  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

  existing_workers = config.get("workers")
  if not worker_name in existing_workers:
    print(f"Worker with name {worker_name} does not exist!")
    return


  yaml = YAML(typ="safe", pure=True)
  yaml.default_flow_style = False
  data = {}
  with open(os.path.join(base_dir, "docker-compose.yml"), 'r') as f: 
    data = yaml.load(f)
  
  with open(os.path.join(base_dir, "docker-compose.yml"), 'w') as f: 
    del data["services"][worker_name]
    yaml.dump(data, f)

  worker_dir = existing_workers[worker_name]["base_dir"]

  del existing_workers[worker_name]
  config["workers"] = existing_workers
  existing_apps[app_name] = config
  update_config("apps", existing_apps)

  workers_base_dir = os.path.join(base_dir, "workers")

  if backup_path != "":
    os.system(f"mv {os.path.join(workers_base_dir, worker_dir)} {os.path.abspath(os.path.join(backup_path, worker_name))}")
  else:
    os.system(f"rm -rf {os.path.join(workers_base_dir, worker_dir)}")

  print(f"Worker successfully removed!")



def handle_backup(args):
  object_lower = str(args.object).lower()
  if object_lower == "service":
    backup_service(args)
  if object_lower == "app":
    backup_app(args)
  if object_lower == "worker":
    backup_worker(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 backup 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_worker(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 backup worker", choices=list(existing_apps.keys())),
    inq.Text("worker_name", message="Enter worker name"),
    inq.Text("backup_path", message="Enter backup folder")
  ]

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

  worker_name = str(worker_name).lower()

  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

  existing_workers = config.get("workers")

  if not worker_name in existing_workers:
    print(f"Worker with name {worker_name} does not exist!")
    return

  worker_dir = existing_workers[worker_name]["base_dir"]

  backup_path_full = os.path.abspath(os.path.join(backup_path, worker_name))

  os.system(f"cp -r {os.path.join(base_dir, 'workers', worker_dir)} {backup_path_full}")

  yaml = YAML(typ="safe", pure=True)
  yaml.default_flow_style = False
  data = {}

  with open(os.path.join(base_dir, "docker-compose.yml"), 'r') as f: 
    data = yaml.load(f)

  try:
    docker_config = data["services"][worker_name]
    write_json(docker_config, os.path.join(backup_path_full, "docker-compose.json"))
  except Exception as e:
    print(e)
    

  print("Worker successfully backed up!")

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 backup", 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()