import datetime
import sys
import Database
from helpers import read_master_key, CsfleHelper
from bson.codec_options import CodecOptions
from bson.binary import Binary, STANDARD, UUID_SUBTYPE
from uuid import UUID
import datetime
from pymongo.encryption import (Algorithm, ClientEncryption)
from pprint import pprint

# connection_string = "mongodb://localhost:27017"
connection_string = "mongodb+srv://nshept:nshept123@cluster0.ampjh.mongodb.net/voice-assistant?retryWrites=true&w=majority" 

# Voice Assistant Database name
db_name = 'voice-assistant'

# Collection names
email_collection_name = 'email'
message_collection_name = 'message'
weather_collection_name = 'weather'
notes_collection_name = 'notes'

# Key database
keyDb = "encryption"
# Key collection
keyColl = "__keyVault"


master_key = read_master_key()

# KMS Providers 
# Read here - https://aws.amazon.com/kms/
# We are not using any KMS, so local key provider is used(making use of local master key)
kms_provider_name = "local"
kms_provider = {
    "local": {
        "key": master_key,
    },
}

# Object of CsfleHelper class, to access Methods like find_or_create_data_key() and get_csfle_enabled_client()
csfle_helper = CsfleHelper(connection_string = connection_string,kms_provider_name=kms_provider_name,
                               kms_provider=kms_provider, master_key=master_key, key_db=keyDb, key_coll=keyColl)

# Insert your key generated by make_data_key.py here.
# Or comment this out if you already have a data key for your provider stored.
data_key = CsfleHelper.key_from_base64("vDR/R6dpQTu4Ogh1mpAjCg==")

# if you already have a data key or are using a remote KMS, uncomment the line below
# data_key = csfle_helper.find_or_create_data_key()

# Encrypted Client specified by autoencryptoptions
# auto_encryption_opts = AutoEncryptionOpts
# (
#   self.kms_provider,
#   self.key_vault_namespace,
#   bypass_auto_encryption = True
# )
encrypted_client = csfle_helper.get_csfle_enabled_client()

# Helper method to encrypt any field of our document
# Making use of ClientEncryption from 
# pymongo.encryption(MongoDB internal)
def get_encrypted_field(field):
    # An instance of ClientEncryption
    client_encryption = ClientEncryption(
        kms_provider,
        f"{keyDb}.{keyColl}",
        encrypted_client,
        CodecOptions(uuid_representation=STANDARD))
    
    # Explicitly encrypt a field:
    encrypted_field = client_encryption.encrypt(field,
        Algorithm.AEAD_AES_256_CBC_HMAC_SHA_512_Deterministic,
        key_alt_name='demo-data-key')
    return encrypted_field


# Database insert/read methods
# In all add/insert methods, we are explicitly encrypting the required fields
# MongoDB free tier supports manual encryption on client side and
# automatic decryption
# We will make use of encrypted client to automatically decrypt the data

def addEmail(sender,reciever,subject,content):

    """
        Method to insert email in the email collection
    """

    # Object to insert
    # A python dictionary with some fields encrypted
    object_to_insert = {
            "timeStamp": datetime.datetime.utcnow(),
            "sender" : get_encrypted_field(sender),
            "reciever": get_encrypted_field(reciever),
            "subject": subject,
            "content": get_encrypted_field(content)
        }
    
    try:
        insert_id = encrypted_client.get_database(db_name)[email_collection_name].insert_one(object_to_insert)
        return insert_id
    except:
        print("exc:", sys.exc_info()) 


def getEmail():

    """
        Method to return most recent entry in the collection
        You can change the limit to specify the number of entries you want to retrieve
    """
    
    try:
        search =  list(encrypted_client.get_database(db_name)[email_collection_name].find().sort('timeStamp', -1).limit(3))
        pprint(search)
        return search
    except:
        print("exc:", sys.exc_info()) 


def addMessage(name,number,content):

    """
        Method to insert message in the message collection
    """
    # Object to insert
    # A python dictionary with some fields encrypted
    object_to_insert = {
            "timeStamp": datetime.datetime.utcnow(),
            "reciever": name,
            "number":get_encrypted_field(number),
            "content":get_encrypted_field(content)
        }
    
    try:
        insert_id = encrypted_client.get_database(db_name)[message_collection_name].insert_one(object_to_insert)
        pprint(insert_id)
        return insert_id
    except:
        print("exc:", sys.exc_info())

def getMessage():
    
    """
        Method to return most recent entry in the collection
        You can change the limit to specify the number of entries you want to retrieve
    """

    try:
        search =  list(encrypted_client.get_database(db_name)[message_collection_name].find().sort('timeStamp', -1).limit(3))
        pprint(search)
        return search
    except:
        print("exc:", sys.exc_info()) 

def addNote(title,content,todo):

    """
        Method to add note in the notes collection
    """

    # Object to insert
    # A python dictionary with some fields encrypted
    object_to_insert = {
            "timeStamp": datetime.datetime.utcnow(),
            "title": title,
            "todo":todo,
            "content":get_encrypted_field(content)
        }    
    
    try:
        insert_id = encrypted_client.get_database(db_name)[notes_collection_name].insert_one(object_to_insert)
        return insert_id
    except:
        print("exc:", sys.exc_info())

def getNote(search_tag):
    
    """
        Method to return most recent entry in the collection
        You can change the limit to specify the number of entries you want to retrieve
    """

    # search tag will define the query
    # True or False if it is a todo or not
    # Returns list of 3 recent todo tasks
    try:
        search =  list(encrypted_client.get_database(db_name)[notes_collection_name].find({"todo":search_tag}).sort('timeStamp', -1).limit(3))
        pprint(search)
        return search
    except:
        print("exc:", sys.exc_info()) 

def getAllNotes():
    
    """
        Method to return most recent entry in the collection
        You can change the limit to specify the number of entries you want to retrieve
    """

    # search tag will define the query
    # True or False if it is a todo or not
    # Returns list of 3 recent todo tasks
    try:
        search =  list(encrypted_client.get_database(db_name)[notes_collection_name].find().sort('timeStamp', -1).limit(3))
        pprint(search)
        return search
    except:
        print("exc:", sys.exc_info())
        
def addWeather(location, weather, temp, desc):

    """
        Method to insert weather in the weather collection
    """

    # Object to insert
    # A python dictionary with some fields encrypted
    object_to_insert = {
            "timeStamp": datetime.datetime.utcnow(),
            "weather": weather,
            "temp": temp,
            "desc": desc,
            "location":get_encrypted_field(location)
        }
        
    try:
        insert_id = encrypted_client.get_database(db_name)[weather_collection_name].insert_one(object_to_insert)
        pprint(insert_id)
        return insert_id
    except:
        print("exc:", sys.exc_info()) 


def getWeather():

    """
        Method to return most recent entry in the collection
        You can change the limit to specify the number of entries you want to retrieve
    """
    try:
        search =  list(encrypted_client.get_database(db_name)[weather_collection_name].find().sort('timeStamp', -1).limit(3))
        pprint(search)
        return search
    except:
        print("exc:", sys.exc_info())


# if __name__ == "__main__":
#     addMessage("a", 123456789, "Hey there!")
#     getMessage("a")
#     addNote("one", "one one", True)
#     addNote("two", "two two", True)
#     addNote("three", "three three", False)
#     getAllNotes()
#     getNote(True)
#     getNote("false")
#     addWeather("Bangkok", "Too hot")
#     getWeather()