#!/usr/bin/env python

import pika
import uuid
import json
import os
import sys

from tabulate import tabulate


class AMQPClient(object):
  
    def __init__(self):
        self.rpcQueue = "storage_queue"
        self.connection = pika.BlockingConnection(pika.ConnectionParameters(host = "rabbitmq"))
        self.channel = self.connection.channel()
        result = self.channel.queue_declare(queue = '', exclusive = True)
        self.callback_queue = result.method.queue
        self.channel.basic_consume(queue = self.callback_queue, on_message_callback = self.on_response, auto_ack = True)

    def on_response(self, ch, method, props, body):
        if self.corr_id == props.correlation_id:
            self.response = json.loads(body)

    def call(self, msg):
        self.response = None
        self.corr_id = str(uuid.uuid4())
        self.channel.basic_publish(exchange = '',
                                   routing_key = self.rpcQueue,
                                   properties = pika.BasicProperties(reply_to = self.callback_queue,
                                                                     correlation_id = self.corr_id,
                                                                    ),
                                   body = json.dumps(msg))

        while self.response is None:
            self.connection.process_data_events()
        return self.response

    def add(self):
        storageType = None
        storageBasePath = None
        storageHostname = None
        while not storageType in ("cold", "hot", "portal"):
                try:
                    storageType = input("\nStorage type [cold, hot or portal]: ")
                except ValueError:
                    print("Input type is not valid!")
                except EOFError:
                    print("\nPlease, use CTRL+C to quit.")
                except KeyboardInterrupt:
                    sys.exit("\nCTRL+C detected. Exiting...")
        try:
            storageBasePath = input("\nStorage base path: ")
        except ValueError:
            print("Input type is not valid!")
        except EOFError:
            print("\nPlease, use CTRL+C to quit.")
        except KeyboardInterrupt:
            sys.exit("\nCTRL+C detected. Exiting...")
        try:
            storageHostname = input("\nStorage hostname: ")
        except ValueError:
            print("Input type is not valid!")
        except EOFError:
            print("\nPlease, use CTRL+C to quit.")
        except KeyboardInterrupt:
            sys.exit("\nCTRL+C detected. Exiting...")
        storageRequest = { "requestType": "STORAGE_ADD", 
                           "storageType": storageType, 
                           "basePath": storageBasePath,
                           "hostname": storageHostname }
        storageResponse = self.call(storageRequest)
        
        if "responseType" not in storageResponse:
            sys.exit("FATAL: Malformed response, storage acknowledge expected.\n")
        elif storageResponse["responseType"] == "STORAGE_ADD_DONE":
            print("\nStorage point added successfully!\n")
        elif storageResponse["responseType"] == "ERROR":
            errorCode = storageResponse["errorCode"]
            errorMsg = storageResponse["errorMsg"]
            sys.exit(f"\nError code: {errorCode}\nError message: {errorMsg}\n")
        else:
            sys.exit("\nFATAL: Unknown response type.\n")

    def delete(self):
        storageRequest = { "requestType": "STORAGE_DEL_REQ" }
        storageResponse = self.call(storageRequest)
        
        if "responseType" not in storageResponse:
            sys.exit("FATAL: Malformed response, storage acknowledge expected.\n")
        elif storageResponse["responseType"] == "STORAGE_DEL_ACK":
            storageList = storageResponse["storageList"]
            if not storageList:
                sys.exit("No storage point found. Please add at least one storage point.\n")
            print("\nSelect the storage location to remove:\n")
            print(tabulate(storageList, headers = "keys", tablefmt = "pretty"))
            print()
            storageIdList = []
            for st in storageList:
                storageIdList.append(st["storage_id"])
            storageId = None
            while not storageId in storageIdList:
                try:
                    storageId = input("Please, insert a storage id: ")
                    storageId = int(storageId)
                except ValueError:
                    print("Input type is not valid!")
                except EOFError:
                    print("\nPlease, use CTRL+C to quit.")
                except KeyboardInterrupt:
                    sys.exit("\nCTRL+C detected. Exiting...")
            print()
            print("!!!!!!!!!!!!!!!!!!!!!!!!!!WARNING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
            print("! This operation will remove the selected storage location only !")
            print("! from the database.                                            !")
            print("! The mount point on the transfer node will not be removed, you !")
            print("! must do it manually.                                          !")
            print("! Anyway, you MUST BE AWARE that all the VOSpace nodes affected !")
            print("! by this operation will not be accessible anymore from now on. !")
            print("! We strongly recommend to move all the data to another storage !")
            print("! location and update the database accordingly.                 !")
            print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!")
            print()
            confirm = None
            while not confirm in ( "yes", "no" ):
                try:
                    confirm = input("Are you sure to proceed? [yes/no]: ")
                except KeyboardInterrupt:
                    sys.exit("\nCTRL+C detected. Exiting...")
                except EOFError:
                    print("\nPlease, use CTRL+C to quit.")
            if confirm == "yes":
                confirmRequest = { "requestType": "STORAGE_DEL_CON", "storageId": storageId }
                confirmResponse = self.call(confirmRequest)
                if "responseType" not in confirmResponse:
                    sys.exit("\nFATAL: Malformed response, storage confirmation expected.\n")
                elif confirmResponse["responseType"] == "STORAGE_DEL_DONE":
                    print("\nStorage location deleted successfully!\n")
                else:
                    sys.exit("FATAL: Unknown response type.\n")
        elif storeResponse["responseType"] == "ERROR":
            errorCode = storeResponse["errorCode"]
            errorMsg = storeResponse["errorMsg"]
            sys.exit(f"Error code: {errorCode}\nError message: {errorMsg}\n")
        else:
            sys.exit("\nFATAL: Unknown response type.\n")
            
    def list(self):
        storageRequest = { "requestType": "STORAGE_LST" }
        storageResponse = self.call(storageRequest)
        
        if "responseType" not in storageResponse:
            sys.exit("FATAL: Malformed response, storage acknowledge expected.\n")
        elif storageResponse["responseType"] == "STORAGE_LST_DONE":
            print()
            print(tabulate(storageResponse["storageList"], headers = "keys", tablefmt = "pretty"))
            print()
        elif storageResponse["responseType"] == "ERROR":
            errorCode = storageResponse["errorCode"]
            errorMsg = storageResponse["errorMsg"]
            sys.exit(f"\nError code: {errorCode}\nError message: {errorMsg}\n")
        else:
            sys.exit("\nFATAL: Unknown response type.\n")
            
    def help(self):
        sys.exit("""
NAME
       vos_storage

SYNOPSYS
       vos_storage COMMAND

DESCRIPTION
       Client tool to manage VOSpace storage points.
       
       The client accepts only one (mandatory) command at a time.
       A list of supported commands is shown here below:

       add
           adds a storage point to the database.
       
       del
           deletes a storage point from the database.
       
       list
           prints the full storage point list.
    """)

# Create new AMQPClient object
vosStorageCli = AMQPClient()

# Check the number of input args
if len(sys.argv) == 2:
    script, cmd = sys.argv
else:
    vosStorageCli.help()

if cmd == "add":
    vosStorageCli.add()
elif cmd == "list":
    vosStorageCli.list()
elif cmd == "del":
    vosStorageCli.delete()
else:
    vosStorageCli.help()
