import requests
import urllib3
import sys
import time
import threading
import logging
import os
import errno

from concurrent.futures import ThreadPoolExecutor
from dotenv import load_dotenv
from vmware.vapi.vsphere.client import create_vsphere_client
from com.vmware.vcenter_client import VM
from com.vmware.vcenter.vm_client import Power as PowerHardware
from com.vmware.vcenter.vm.guest_client import Power as PowerGuest

# Variables
wait_power_on = 60  # Tiempo de espera entre grupos de máquinas
wait_power_off = 60  # Tiempo de espera entre grupos de máquinas
wait_tools_on = 120  # Tiempo de espera para que las tools esten activas
wait_tools_off = 120  # Tiempo de espera para que las tools esten apagadas
timeout_on = 1200  # Tiempo de espera para que se de la máquina por encendida
timeout_off = 1200  # Tiempo de espera hasta forzar el apagado de la maquina
operation = "none"  # Modo de operación: encendido o apagado
# Grupo de máquinas sobre las que se aplicará el modo de energía (NOMBRE)
group_selected = "none"
vms = "none"  # Máquinas que se van a utilizar en la ejecución del script

# TEST
vms_test_g1 = ["test1", "test2"]
vms_test_g2 = ["test3", "test4", "test5"]
vms_test_g3 = ["test6", "test7", "test8", "test9", "test10"]

# TEST2
vms_test_g4 = ["test1", "test2", "test3", "test4",
               "test5", "test6", "test7", "test8", "test9", "test10"]

# PRODUCCIÓN
vm_sin_grupo = ["alarmas_ayto", "alarmas_cpd", "cita_previa", "daloradius_biblioteca", "deep_freeze2", "editrans2", "edu_latorre", "gestion_switches", "impresoras_ricoh", "inattend2", "lucia", "micollab-mbg", "micollab9.4", "museo_domus", "ofimatico2007", "ofimatico2019",
                "pulse_secure", "tarificador", "tc_aplicaciones", "tc_balanceador", "tc_bbdd", "tc_wserver", "untangle_biblioteca", "web_pre", "webdmz", "wifimovi", "tao_consultores1", "tao_consultores2", "tao_consultores3", "temporizador_pleno", "wazuh4.3.6"]
vm_apagadas = ["adnperros", "autocad2010", "control_biblio", "gisabsis", "glpi", "gwdmz15", "helpdesk",
               "inventario_igs", "oes2018_pruebas", "open_erp", "pacmer", "presencia", "severino", "verticales_telefonica_camaras"]
vm_grupo_0 = ["datacore-cpd", "datacore-pol", "vcsa7"]
vm_grupo_1 = ["sacrista", "paco", "synology", "hp1440"]
vm_grupo_2 = ["torero", "oraserver19", "oraserver19_pre",
              "ndsmaster", "storeonce1", "storeonce2"]
vm_grupo_3 = ["ayto_web_int", "documentum2016", "geoserver_lan", "peseta2",
              "taoactuate", "abaco", "intranet2016", "intranet2_pre", "petrolio2"]
vm_grupo_4 = ["ayto_web_ext2", "geoserver_pre",
              "tao1", "tao1_pre", "tirisiti2"]
vm_grupo_5 = ["clasico", "gw01-19", "gw02-19", "geoserver_pro", "otrs", "taoapps1_wildfly", "taoapps2_wildfly", "taoapps3_wildfly", "taoapps_pre_wildfly",
              "tereseta2", "fortianalyzer", "fortimail", "veeam", "taobalanceador_wildfly", "taosede_9", "taosede_9_pre", "filr4"]
vm_grupo_6 = ["datasync_gms15", "gwdmz"]
vm_grupo_7 = ["centreon2", "zabbix"]

# ORACLE
vm_oracle_0 = ["oraserver19", "oraserver19_pre"]
vm_oracle_1 = ["ayto_web_int", "documentum2016", "geoserver_lan", "peseta2",
               "taoactuate", "abaco", "intranet2016", "intranet2_pre"]
vm_oracle_2 = ["ayto_web_ext2", "geoserver_pre", "tao1", "tao1_pre"]
vm_oracle_3 = ["geoserver_pro", "taoapps1_wildfly", "taoapps2_wildfly", "taoapps3_wildfly",
               "taoapps_pre_wildfly", "taobalanceador_wildfly", "taosede_9", "taosede_9_pre"]

# GEOSERVERS
g1 = ["geoserver_lan"]
g2 = ["geoserver_pre"]
g3 = ["geoserver_pro"]

# Orden de encendido de las máquinas de test
vms_test1 = [vms_test_g1, vms_test_g2, vms_test_g3]
vms_test2 = [vms_test_g4]
vms_oracle = [vm_oracle_0, vm_oracle_1, vm_oracle_2, vm_oracle_3]
vms_produccion = [vm_grupo_2, vm_grupo_3, vm_grupo_4,
                  vm_grupo_5, vm_grupo_6, vm_sin_grupo, vm_grupo_7]
vms_geo = [g1, g2, g3]

# Listados con los diferentes grupos de máquinas
groups = [vms_test1, vms_test2, vms_oracle, vms_produccion, vms_geo]
group_names = ["TEST1", "TEST2", "ORACLE", "PRODUCCION", "GEOSERVERS"]

def get_vm(client, vm_name):
    # Return the identifier of a vm
    # Note: The method assumes that there is only one vm with the mentioned name.
    names = set([vm_name])
    vms = client.vcenter.VM.list(VM.FilterSpec(names=names))

    if len(vms) == 0:
        return None

    vm = vms[0].vm
    return vm


def power_off_vm(client, vm, name):
    # Si la maquina existe
    if vm != None:
        # Obtiene el estado (encendida o apagada)
        status = client.vcenter.vm.Power.get(vm)
        timeout_shutdown = time.time() + timeout_off

        # Repite hasta que se apague o se acabe el tiempo
        while status.state == PowerHardware.State.POWERED_ON:
            status = client.vcenter.vm.Power.get(vm)
            guest_status = client.vcenter.vm.guest.Power.get(vm).state
            # El sistema operativo está funcionando
            if guest_status == PowerGuest.State.RUNNING:
                tools = client.vcenter.vm.guest.Power.get(vm).operations_ready
                # Las tools estan operativas
                if tools == True:
                    logging.info("%s: Enviada señal de apagado", name)
                    client.vcenter.vm.guest.Power.shutdown(vm)
                    # Espera hasta que las tools ya no esten disponibles o salte el time out
                    timeout_tools = time.time() + wait_tools_off
                    while tools == True:
                        tools = client.vcenter.vm.guest.Power.get(
                            vm).operations_ready
                        time.sleep(1)
                        if time.time() > timeout_tools:
                            logging.info("%s: Timeout tools", name)
                            break
                    logging.info("%s: Apagandose ...", name)
            time.sleep(5)
            if time.time() > timeout_shutdown:
                logging.info("%s: Forzando apagado ...", name)
                client.vcenter.vm.power.stop(vm)
                break
        logging.info("%s: Apagada", name)
        return True

    # Si la máquina no existe
    else:
        logging.info("%s: No existe", name)
        return False


def power_on_vm(client, vm, name):
    # Si la maquina existe
    if vm != None:
        # Obtiene el estado (encendida o apagada)
        status = client.vcenter.vm.Power.get(vm)

        # Repite hasta que se encienda
        while status.state == PowerHardware.State.POWERED_OFF:
            client.vcenter.vm.Power.start(vm)
            logging.info("%s: Enviada señal de encendido", name)

            # Espera hasta que las tools esten disponibles o salte el time out
            timeout_tools = time.time() + wait_tools_on
            tools = client.vcenter.vm.guest.Power.get(vm).operations_ready
            while tools == False:
                time.sleep(5)
                tools = client.vcenter.vm.guest.Power.get(vm).operations_ready
                logging.info("%s: Encendiendose", name)
                if time.time() > timeout_tools:
                    logging.info("%s: Timeout tools", name)
                    break

            # Comprueba el estado
            status = client.vcenter.vm.Power.get(vm)

        # La máquina ya está encendida
        logging.info("%s: Encendida", name)
        return True

    # Si la máquina no existe
    else:
        logging.warning("%s: no existe", name)
        return False


def printHelp(prog):
    print("\nFUNCIONAMIENTO")
    print("\tpython3 " + prog + " OPCIONES\n")

    print("OPCIONES")
    print("\t -h, --help:")
    print("\t\tMuestra esta ayuda\n")

    print("\t -l, --list:")
    print("\t\tLista los posibles grupos de maquinas virtuales para operar\n")

    print("\t -l, --list NOMBRE_GRUPO:")
    print("\t\tLista los nombres de las máquinas virtuales que pertenecen al grupo NOMBRE_GRUPO\n")

    print("\t -g, --group NOMBRE_GRUPO:")
    print("\t\tEstablece el grupo de maquinas virtuales sobre el cual se aplicará el modo de energía\n")

    print("\t -p, --power on|off:")
    print("\t\tIndica el modo de operación de energía que se aplicará a las máquians virtuales")
    print("\t\t--power on: Enciende las máquinas virtuales del grupo seleccionado")
    print("\t\t--power off: Apaga las máquinas virtuales del grupo seleccionado\n")

    print("EJEMPLO")
    print("\tpython3 " + prog + " --power on --group ORACLE\n")


def checkArgs(args):
    global operation
    operation = "none"
    global group_selected
    group_selected = "none"
    global vms
    vms = "none"

    for index in range(0, len(args)):
        # POWER
        if args[index] == "--power" or args[index] == "-p":
            if index < len(args)-1:
                if args[index+1] == "on":
                    operation = "on"
                elif args[index+1] == "off":
                    operation = "off"

        # LIST
        if args[index] == "--list" or args[index] == "-l":
            operation = "none"
            # Comprueba que el argumento list es el último o no contiene un nombre de grupo válido detras
            if index == len(args)-1 or (index <= len(args)-1 and args[index+1] not in group_names):
                print("Los grupos de máquinas disponibles son los siguientes:")
                for group in group_names:
                    print("- "+group)

            # Comprueba que el argumento posterior pertenece a un grupo
            if index < len(args)-1:
                if args[index+1] in group_names:
                    num = group_names.index(args[index+1])
                    num_group = 1
                    for group in groups[num]:
                        print("Grupo", num_group)
                        num_group += 1
                        for mv in group:
                            print("\t" + mv)

        # GROUP
        if args[index] == "--group" or args[index] == "-g":
            group_selected = "none"
            # Comprueba que el argumento posterior pertenece a un grupo
            if index < len(args)-1:
                if args[index+1] in group_names:
                    group_selected = args[index+1]
                    num = group_names.index(group_selected)
                    vms = groups[num]

        # HELP
        if args[index] == "--help" or args[index] == "-h":
            operation = "none"
            printHelp(args[0])

    if len(args) == 1:
        operation = "none"
        printHelp(args[0])


def main():
    # Comprueba los argumentos
    checkArgs(sys.argv)
    print("operation", operation)
    vms = vms_test1
    print("vms", vms)

    # ~Si no tiene los parametros necesarios, sale del programa
    if (operation == "none" or vms == "none"):
        sys.exit()

    # Crea el directorio para los logs
    try:
        os.mkdir('log')
    except OSError as e:
        if e.errno != errno.EEXIST:
            raise

    # Obtiene la fecha y hora de inicio
    ini_timestr = time.strftime("%Y%m%d-%H%M%S")

    # Inicia el log
    logging.basicConfig(level=logging.INFO,
                        format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
                        encoding='utf-8',
                        datefmt='%m-%d %H:%M',
                        filename='log/' + ini_timestr + '.log',
                        filemode='w')

    # Define a Handler which writes INFO messages or higher to the sys.stderr
    console = logging.StreamHandler()
    console.setLevel(logging.INFO)

    # Set a format which is simpler for console use
    formatter = logging.Formatter('%(levelname)-8s>> %(message)s')

    # Tell the handler to use this format
    console.setFormatter(formatter)

    # Add the handler to the root logger
    logging.getLogger().addHandler(console)
    # logging.info("Empieza el programa\n")

    session = requests.session()

    # Disable cert verification for demo purpose.
    # This is not recommended in a production environment.
    session.verify = False

    # Disable the secure connection warning for demo purpose.
    # This is not recommended in a production environment.
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

    # Obtiene la información desde el fichero .env
    load_dotenv()
    vcenter_server = os.getenv('VCENTER_SERVER')
    vcenter_username = os.getenv('VCENTER_USERNAME')
    vcenter_password = os.getenv('VCENTER_PASSWORD')

    print("vcenter_server", vcenter_server)

    # Connect to a vCenter Server using username and password
    client = create_vsphere_client(
        server=vcenter_server, username=vcenter_username, password=vcenter_password, session=session)

    # Bucle principal
    main_loop(client, operation)
    # logging.info("Acaba el programa")


def main_loop(client, operation):

    # Enciende máquinas
    if operation == "on":
        logging.info("Modo de operación: ENCENDER MÁQUINAS\n")
        group = 0

        while group < len(vms):
            logging.info("Grupo %s: PROCESANDO", group)

            # Create the thread pool
            n_threads = len(vms[group])
            with ThreadPoolExecutor(n_threads) as executor:
                _ = [executor.submit(power_on_vm, client, get_vm(
                    client, vm_name), vm_name) for vm_name in vms[group]]

            # Terminado el grupo de máquinas
            logging.info("Grupo %s: TERMINADO\n", group)

            # Resumen
            logging.info("Grupo %s: RESUMEN", group)
            num_mv = 0
            for vm_name in vms[group]:
                vm = get_vm(client, vm_name)
                if power_on_vm(client, vm, vm_name):
                    num_mv += 1
            logging.info("%s de %s máquinas encendidas\n",
                         num_mv, len(vms[group]))

            group += 1
            if group < len(vms):
                logging.info("Esperando %s segundos ...\n", wait_power_on)
                time.sleep(wait_power_on)

    # Apaga máquinas
    if operation == "off":
        logging.info("Modo de operación: APAGAR MÁQUINAS\n")
        group = len(vms) - 1

        while group >= 0:
            logging.info("Grupo %s: PROCESANDO", group)

            # Create the thread pool
            n_threads = len(vms[group])
            with ThreadPoolExecutor(n_threads) as executor:
                _ = [executor.submit(power_off_vm, client, get_vm(
                    client, vm_name), vm_name) for vm_name in vms[group]]

            # Terminado el grupo de máquinas
            logging.info("Grupo %s: TERMINADO\n", group)

            # Resumen
            logging.info("Grupo %s: RESUMEN", group)
            num_mv = 0
            for vm_name in vms[group]:
                vm = get_vm(client, vm_name)
                if power_off_vm(client, vm, vm_name):
                    num_mv += 1
            logging.info("%s de %s máquinas apagadas\n",
                         num_mv, len(vms[group]))

            group -= 1
            if group >= 0:
                logging.info("Esperando %s segundos ...\n", wait_power_off)
                time.sleep(wait_power_off)


if __name__ == "__main__":
    main()
