import subprocess
import re
import json
import sys

from flask import current_app
from time import sleep
from os.path import join
from subprocess import run

from capsulflask.db import get_model

from capsulflask.shared import VirtualizationInterface, VirtualMachine, validate_capsul_id, my_exec_info_message


class MockSpoke(VirtualizationInterface):
  def capacity_avaliable(self, additional_ram_bytes):
    return True

  def get(self, id, get_ssh_host_keys):
    validate_capsul_id(id)

    if get_ssh_host_keys:
      ssh_host_keys = json.loads("""[
        {"key_type":"ED25519", "content":"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN8cna0zeKSKl/r8whdn/KmDWhdzuWRVV0GaKIM+eshh", "sha256":"V4X2apAF6btGAfS45gmpldknoDX0ipJ5c6DLfZR2ttQ"},
        {"key_type":"RSA", "content":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCvotgzgEP65JUQ8S8OoNKy1uEEPEAcFetSp7QpONe6hj4wPgyFNgVtdoWdNcU19dX3hpdse0G8OlaMUTnNVuRlbIZXuifXQ2jTtCFUA2mmJ5bF+XjGm3TXKMNGh9PN+wEPUeWd14vZL+QPUMev5LmA8cawPiU5+vVMLid93HRBj118aCJFQxLgrdP48VPfKHFRfCR6TIjg1ii3dH4acdJAvlmJ3GFB6ICT42EmBqskz2MPe0rIFxH8YohCBbAbrbWYcptHt4e48h4UdpZdYOhEdv89GrT8BF2C5cbQ5i9qVpI57bXKrj8hPZU5of48UHLSpXG8mbH0YDiOQOfKX/Mt", "sha256":"ghee6KzRnBJhND2kEUZSaouk7CD6o6z2aAc8GPkV+GQ"},
        {"key_type":"ECDSA", "content":"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBLLgOoATz9R4aS2kk7vWoxX+lshK63t9+5BIHdzZeFE1o+shlcf0Wji8cN/L1+m3bi0uSETZDOAWMP3rHLJj9Hk=", "sha256":"aCYG1aD8cv/TjzJL0bi9jdabMGksdkfa7R8dCGm1yYs"}
      ]""")
      return VirtualMachine(id, current_app.config["SPOKE_HOST_ID"], ipv4="1.1.1.1", state="running", ssh_host_keys=ssh_host_keys)

    return VirtualMachine(id, current_app.config["SPOKE_HOST_ID"], ipv4="1.1.1.1", state="running")

  def list_ids(self) -> list:
    return get_model().all_non_deleted_vm_ids()

  def create(self, email: str, id: str, template_image_file_name: str, vcpus: int, memory_mb: int, ssh_authorized_keys: list):
    validate_capsul_id(id)
    current_app.logger.info(f"mock create: {id} for {email}")
    sleep(1)

  def destroy(self, email: str, id: str):
    current_app.logger.info(f"mock destroy: {id} for {email}")

  def vm_state_command(self, email: str, id: str, command: str):
    current_app.logger.info(f"mock {command}: {id} for {email}")

class ShellScriptSpoke(VirtualizationInterface):

  def validate_completed_process(self, completedProcess, email=None):
    emailPart = ""
    if email != None:
      emailPart = f"for {email}"

    if completedProcess.returncode != 0:
      raise RuntimeError(f"""{" ".join(completedProcess.args)} failed {emailPart} with exit code {completedProcess.returncode}
        stdout:
        {completedProcess.stdout}
        stderr:
        {completedProcess.stderr}
      """)

  def capacity_avaliable(self, additional_ram_bytes):
    my_args=[join(current_app.root_path, 'shell_scripts/capacity-avaliable.sh'), str(additional_ram_bytes)]
    completedProcess = run(my_args, capture_output=True)

    if completedProcess.returncode != 0:
      current_app.logger.error(f"""
      capacity-avaliable.sh exited {completedProcess.returncode} with
        stdout:
        {completedProcess.stdout}
        stderr:
        {completedProcess.stderr}
      """)
      return False

    lines = completedProcess.stdout.splitlines()
    output = lines[len(lines)-1]
    if not output == b"yes":
      current_app.logger.error(f"capacity-avaliable.sh exited 0 and returned {output} but did not return \"yes\" ")
      return False

    return True

  def get(self, id, get_ssh_host_keys):
    validate_capsul_id(id)
    completedProcess = run([join(current_app.root_path, 'shell_scripts/get.sh'), id], capture_output=True)
    self.validate_completed_process(completedProcess)
    lines = completedProcess.stdout.splitlines()
    if len(lines) == 0:
      current_app.logger.warning("shell_scripts/get.sh returned zero lines!")
      return None
    
    result_string = lines[0].decode("utf-8")

    fields = result_string.split(" ")
    if fields[0] != "true":
      current_app.logger.warning(f"shell_scripts/get.sh was called for {id} which libvirt says does not exist.")
      return None

    if len(fields) < 2:
      return VirtualMachine(id, current_app.config["SPOKE_HOST_ID"])

    state = fields[1]

    if len(fields) < 3:
      return VirtualMachine(id, current_app.config["SPOKE_HOST_ID"], state=state)

    ipaddr = fields[2]

    if not re.match(r"^([0-9]{1,3}\.){3}[0-9]{1,3}$", ipaddr):
      return VirtualMachine(id, current_app.config["SPOKE_HOST_ID"], state=state)

    if get_ssh_host_keys:
      try:
        completedProcess2 = run([join(current_app.root_path, 'shell_scripts/ssh-keyscan.sh'), ipaddr], capture_output=True)
        self.validate_completed_process(completedProcess2)
        ssh_host_keys = json.loads(completedProcess2.stdout.decode("utf-8"))
        return VirtualMachine(id, current_app.config["SPOKE_HOST_ID"], state=state, ipv4=ipaddr, ssh_host_keys=ssh_host_keys)
      except:
        current_app.logger.warning(f"""
          failed to ssh-keyscan {id} at {ipaddr}:
          {my_exec_info_message(sys.exc_info())}"""
        )

    return VirtualMachine(id, current_app.config["SPOKE_HOST_ID"], state=state, ipv4=ipaddr)

  def list_ids(self) -> list:
    completedProcess = run([join(current_app.root_path, 'shell_scripts/list-ids.sh')], capture_output=True)
    self.validate_completed_process(completedProcess)
    return list(map(lambda x: x.decode("utf-8"), completedProcess.stdout.splitlines() ))

  def create(self, email: str, id: str, template_image_file_name: str, vcpus: int, memory_mb: int, ssh_authorized_keys: list):
    validate_capsul_id(id)

    if not re.match(r"^[a-zA-Z0-9/_.-]+$", template_image_file_name):
      raise ValueError(f"template_image_file_name \"{template_image_file_name}\" must match \"^[a-zA-Z0-9/_.-]+$\"")

    for ssh_authorized_key in ssh_authorized_keys:
      if not re.match(r"^(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$", ssh_authorized_key):
        raise ValueError(f"ssh_authorized_key \"{ssh_authorized_key}\" must match \"^(ssh|ecdsa)-[0-9A-Za-z+/_=@. -]+$\"")

    if vcpus < 1 or vcpus > 8:
      raise ValueError(f"vcpus \"{vcpus}\" must match 1 <= vcpus <= 8")

    if memory_mb < 512 or memory_mb > 16384:
      raise ValueError(f"memory_mb \"{memory_mb}\" must match 512 <= memory_mb <= 16384")

    ssh_keys_string = "\n".join(ssh_authorized_keys)

    completedProcess = run([
      join(current_app.root_path, 'shell_scripts/create.sh'),
      id,
      template_image_file_name,
      str(vcpus),
      str(memory_mb),
      ssh_keys_string
    ], capture_output=True)

    self.validate_completed_process(completedProcess, email)
    lines = completedProcess.stdout.splitlines()
    status = lines[len(lines)-1].decode("utf-8")

    vmSettings = f"""
      id={id}
      template_image_file_name={template_image_file_name}
      vcpus={str(vcpus)}
      memory={str(memory_mb)}
      ssh_authorized_keys={ssh_keys_string}
    """

    if not status == "success":
      raise ValueError(f"""failed to create vm for {email} with:
        {vmSettings}
        stdout:
        {completedProcess.stdout}
        stderr:
        {completedProcess.stderr}
      """)

  def destroy(self, email: str, id: str):
    validate_capsul_id(id)
    completedProcess = run([join(current_app.root_path, 'shell_scripts/destroy.sh'), id], capture_output=True)
    self.validate_completed_process(completedProcess, email)
    lines = completedProcess.stdout.splitlines()
    status = lines[len(lines)-1].decode("utf-8")
    if not status == "success":
      raise ValueError(f"""failed to destroy vm {id} for {email} on {current_app.config["SPOKE_HOST_ID"]}:
        stdout:
        {completedProcess.stdout}
        stderr:
        {completedProcess.stderr}
      """)

  def vm_state_command(self, email: str, id: str, command: str):
    validate_capsul_id(id)
    if command not in ["stop", "force-stop", "start", "restart"]:
      raise ValueError(f"command ({command}) must be one of stop, force-stop, start, or restart")

    completedProcess = run([join(current_app.root_path, f"shell_scripts/{command}.sh"), id], capture_output=True)
    self.validate_completed_process(completedProcess, email)
    returned_string = completedProcess.stdout.decode("utf-8")
    current_app.logger.info(f"{command} vm {id} for {email} returned: {returned_string}")