from ..core import InstanceKey, InstanceReference, TenantReference
from ..definitions import KnownClassGuids, KnownInstanceGuids, KnownAttributeGuids, KnownRelationshipGuids

from framework import Guid

from .OmsResult import OmsResult

class Oms:
	def __init__(self):
		pass

	def get_user_by_username(self, username : str):
		
		ik_User = self.get_instance_by_global_identifier(KnownClassGuids.User)
		if ik_User is None:
			print ("error: class `User` is not defined")
			return None
		
		users = self.get_instances(ik_User)
		for user in users:
			un = self.get_attribute_value(user, self.get_instance_by_global_identifier(KnownAttributeGuids.UserName))
			if un == username:
				return user
			
		return None

	def create_class(self, class_id : int, inst_id : int, name : str, global_identifier : Guid):
		pass

	def create_relationship(self, src_class : InstanceReference, type : str, dst_class : InstanceReference, singular : bool = False, sibling_relationship : InstanceReference = None, global_identifier : Guid = None):
		
		ik_current = self.create_instance_of(self.get_instance_by_global_identifier(KnownClassGuids.Relationship), global_identifier)
		
		self.assign_relationship(ik_current, self.get_instance_by_global_identifier(KnownRelationshipGuids.Relationship__has_source__Class), src_class)
		self.assign_attribute(ik_current, self.get_instance_by_global_identifier(KnownAttributeGuids.RelationshipType), type)
		self.assign_relationship(ik_current, self.get_instance_by_global_identifier(KnownRelationshipGuids.Relationship__has_destination__Class), dst_class)
		self.assign_attribute(ik_current, self.get_instance_by_global_identifier(KnownAttributeGuids.Singular), singular)
		
		if sibling_relationship is not None:
			self.assign_relationship(ik_current, self.get_instance_by_global_identifier(KnownRelationshipGuids.Relationship__has_sibling__Relationship), sibling_relationship)
		
		return ik_current

	def get_parent_class(self, instance : InstanceReference) -> InstanceReference:
		ik = InstanceKey(1, instance.get_instance_key().get_class_index())
		return self.get_instance_by_key(ik)
	
	def assign_attribute(self, instance : InstanceReference, attribute : InstanceReference, value : str):
		pass

	def assign_relationship(self, instance : InstanceReference, relationship : InstanceReference, related_instances : InstanceReference|list):
		pass
	
	def get_attribute_value_internal(self, inst : InstanceReference, att : InstanceReference, default_value = None):
		pass

	def get_attribute_value(self, inst : InstanceReference, att : InstanceReference, default_value = None):
		value = self.get_attribute_value_internal(inst, att, default_value)
		if value is None:
			return default_value

		pclass = self.get_parent_class(att)
		if pclass is not None:
			if pclass.get_global_identifier() == KnownClassGuids.NumericAttribute:
				return float(value)
		
		return value

	def set_attribute_value_internal(self, inst : InstanceReference, att : InstanceReference, value):
		pass

	def set_attribute_value(self, inst : InstanceReference, att : InstanceReference, value) -> OmsResult:
		
		if inst is None or att is None:
			return OmsResult(False, "no value specified for instance reference: inst | att")

		pclass = self.get_parent_class(att)
		if pclass is not None:
			if pclass.get_global_identifier() == KnownClassGuids.NumericAttribute:
				try:
					val = float(value)
					max_val = self.get_attribute_value(att, self.get_instance_by_global_identifier(KnownAttributeGuids.MaximumValue))
					min_val = self.get_attribute_value(att, self.get_instance_by_global_identifier(KnownAttributeGuids.MinimumValue))
					
					if (min_val is not None and val < min_val) or (max_val is not None and val > max_val):
						return OmsResult(False, "parameter out of range: value")

				except ValueError:
					return OmsResult(False, "parameter type mismatch: expected int, got " + str(type(value)))

		self.set_attribute_value_internal(inst, att, value)
		return OmsResult.SUCCESS

	def get_instance_by_key(self, key : InstanceKey):
		pass

	def get_instance_by_global_identifier(self, global_identifier : Guid):
		pass

	def get_instances(self, of_class : InstanceReference = None):
		pass

	def get_tenant_by_name(self, tenant_name : str) -> TenantReference:
		pass

	def get_related_instances(self, inst : InstanceReference, rel : InstanceReference) -> list[InstanceReference]:
		pass

	def get_related_instance(self, inst : InstanceReference, rel : InstanceReference) -> InstanceReference | None:
		rels = self.get_related_instances(inst, rel)
		if len(rels) == 0:
			return None
			
		return rels[0]

	def assign_attribute(self, instance : InstanceReference, attribute : InstanceReference, value : str):
		pass
	
	def assign_relationship(self, instance : InstanceReference, relationship : InstanceReference, related_instances : InstanceReference|list):
		pass

	def get_instance_text(self, inst : InstanceReference) -> str:
		# first see if we have a parent class with `Class.instance labeled by Executable returning Attribute`
		pclasz = self.get_parent_class(inst)
		if pclasz is not None:
			rel = self.get_instance_by_global_identifier(KnownRelationshipGuids.Class__instances_labeled_by__Executable_returning_Attribute)
			relinst = self.get_related_instance(pclasz, rel)
			if relinst is not None:
				text = "EXEC:" + str(relinst.get_instance_key()) # self.execute(relinst)
				return text


		# if all else fails, try getting the `Name` attribute
		attr = self.get_instance_by_global_identifier(KnownAttributeGuids.Name)
		text = self.get_attribute_value(inst, attr)
		
		if text is None:
			text = ""
			
		return text

	def _create_instance_of(self, class_inst : InstanceReference, global_identifier : Guid = None):
		pass

	def create_instance_of(self, class_inst : InstanceReference, global_identifier : Guid = None):

		inst = self._create_instance_of(class_inst, global_identifier)

		rel_Class__has__Instance = self.get_instance_by_global_identifier(KnownRelationshipGuids.Class__has__Instance)
		if rel_Class__has__Instance is None:
			return inst
		rel_Instance__for__Class = self.get_instance_by_global_identifier(KnownRelationshipGuids.Instance__for__Class)
		if rel_Instance__for__Class is None:
			return inst

		self.assign_relationship(class_inst, rel_Class__has__Instance, inst)
		self.assign_relationship(inst, rel_Instance__for__Class, class_inst)

		return inst

	def set_user_password(self, user : InstanceReference, password : str):
		
		ik_UserName = self.get_instance_by_global_identifier(KnownAttributeGuids.UserName)
		ik_PasswordHash = self.get_instance_by_global_identifier(KnownAttributeGuids.PasswordHash)
		ik_PasswordSalt = self.get_instance_by_global_identifier(KnownAttributeGuids.PasswordSalt)
		
		username = self.get_attribute_value(user, self.get_instance_by_global_identifier(KnownAttributeGuids.UserName))
		salt = Guid.create().strip()

		passsalt = str(password + salt).encode("utf-8")
		import hashlib
		passhash = hashlib.sha512(passsalt).hexdigest()

		self.assign_attribute(user, ik_UserName, username)
		self.assign_attribute(user, ik_PasswordHash, passhash)
		self.assign_attribute(user, ik_PasswordSalt, salt)

	def create_user(self, username : str, password : str, global_identifier : Guid = None):
		user = self.create_instance_of(self.get_instance_by_global_identifier(KnownClassGuids.User), global_identifier)
		
		if password is not None:
			self.set_user_password(user, password)
		
	def before_init(self):
		pass

	def _init(self):
		ik_Class = self.create_class(1, 1, 'Class', KnownClassGuids.Class)
		self.create_class(1, 2, 'Attribute', KnownClassGuids.Attribute)
		ik_Relationship = self.create_class(1, 3, 'Relationship', KnownClassGuids.Relationship)

		ik_TextAttribute = self.create_class(1, 4, 'Text Attribute', KnownClassGuids.TextAttribute)
		ik_BooleanAttribute = self.create_class(1, 5, 'Boolean Attribute', KnownClassGuids.BooleanAttribute)
		self.create_class(1, 6, 'Element', KnownClassGuids.Element)
		ik_NumericAttribute = self.create_class(1, 20, 'Numeric Attribute', KnownClassGuids.NumericAttribute)
		ik_RichTextAttribute = self.create_class(1, 2737, 'Rich Text Attribute', KnownClassGuids.RichTextAttribute)
		
		att_Name = self.create_instance_of(ik_TextAttribute, KnownAttributeGuids.Name)
		att_Value = self.create_instance_of(ik_TextAttribute, KnownAttributeGuids.Value)
		att_Token = self.create_instance_of(ik_TextAttribute, KnownAttributeGuids.Token)
		
		rel_Relationship__has_source__Class = self.create_instance_of(self.get_instance_by_global_identifier(KnownClassGuids.Relationship), KnownRelationshipGuids.Relationship__has_source__Class)
		rel_Relationship__has_destination__Class = self.create_instance_of(self.get_instance_by_global_identifier(KnownClassGuids.Relationship), KnownRelationshipGuids.Relationship__has_destination__Class)
		rel_Relationship__has_sibling__Relationship = self.create_instance_of(self.get_instance_by_global_identifier(KnownClassGuids.Relationship), KnownRelationshipGuids.Relationship__has_sibling__Relationship)
		
		ik_Singular = self.create_instance_of(self.get_instance_by_global_identifier(KnownClassGuids.BooleanAttribute), KnownAttributeGuids.Singular)
		ik_RelationshipType = self.create_instance_of(self.get_instance_by_global_identifier(KnownClassGuids.TextAttribute), KnownAttributeGuids.RelationshipType)
		

		ik_Instance = self.create_class(1, 17, "Instance", KnownClassGuids.Instance)
		ik_User = self.create_class(1, 39, 'System User', KnownClassGuids.User)
		ik_UserLogin = self.create_class(1, 4328, 'System Account Signon', KnownClassGuids.UserLogin)

		ik_UserName = self.create_instance_of(self.get_instance_by_global_identifier(KnownClassGuids.TextAttribute), KnownAttributeGuids.UserName)
		self.assign_attribute(ik_UserName, self.get_instance_by_global_identifier(KnownAttributeGuids.Name), "User Name")

		ik_PasswordHash = self.create_instance_of(self.get_instance_by_global_identifier(KnownClassGuids.TextAttribute), KnownAttributeGuids.PasswordHash)
		self.assign_attribute(ik_PasswordHash, self.get_instance_by_global_identifier(KnownAttributeGuids.Name), "Password Hash")

		ik_PasswordSalt = self.create_instance_of(self.get_instance_by_global_identifier(KnownClassGuids.TextAttribute), KnownAttributeGuids.PasswordSalt)
		self.assign_attribute(ik_PasswordSalt, self.get_instance_by_global_identifier(KnownAttributeGuids.Name), "Password Salt")

		superuser = self.create_instance_of(self.get_instance_by_global_identifier(KnownClassGuids.User), KnownInstanceGuids.User.superuser)
		self.assign_attribute(superuser, ik_UserName, "superuser")
		self.assign_attribute(superuser, ik_PasswordHash, "0bdbeeecd24e93a0d84ff2946c519761d4db2c52d9449d2de5bf3b74a8bcdfbee9d72bb88121a63700d09554983e7f881021ecf493f83dec19e8f7e465529c34")
		self.assign_attribute(superuser, ik_PasswordSalt, "5e28b86839ed4ca9838c112723aeaa27")

		self.create_user("zq-configurator", "Florida407!", KnownInstanceGuids.User.zq_configurator)
		self.create_user("zq-developer", "Florida407!", KnownInstanceGuids.User.zq_developer)
		self.create_user("zq-environments", "Florida407!", KnownInstanceGuids.User.zq_environments)
		self.create_user("zq-implementer", "Florida407!", KnownInstanceGuids.User.zq_implementer)
		self.create_user("zq-support", "Florida407!", KnownInstanceGuids.User.zq_support)

		self.assign_attribute(self.get_instance_by_key(InstanceKey(1, 2)), self.get_instance_by_key(InstanceKey(4, 1)), 'Attribute')
		self.assign_attribute(self.get_instance_by_key(InstanceKey(1, 3)), self.get_instance_by_key(InstanceKey(4, 1)), 'Relationship')
		self.assign_attribute(self.get_instance_by_key(InstanceKey(1, 4)), self.get_instance_by_key(InstanceKey(4, 1)), 'Text Attribute')
		self.assign_attribute(self.get_instance_by_key(InstanceKey(4, 1)), self.get_instance_by_key(InstanceKey(4, 1)), 'Name')
		self.assign_attribute(self.get_instance_by_key(InstanceKey(4, 2)), self.get_instance_by_key(InstanceKey(4, 1)), 'Value')
		self.assign_attribute(self.get_instance_by_key(InstanceKey(4, 3)), self.get_instance_by_key(InstanceKey(4, 1)), 'Token')

		ik_User_Login__for__User = self.create_relationship(ik_UserLogin, "for", ik_User, False, None, KnownRelationshipGuids.User_Login__has__User)
		# self.create_relationship(ik_User, "has", ik_UserLogin, False, ik_User_Login__for__User, KnownRelationshipGuids.User__has__User_Login)

		self.update_relationship(rel_Relationship__has_source__Class, ik_Relationship, "has source", ik_Class, True, None)
		self.update_relationship(rel_Relationship__has_destination__Class, ik_Relationship, "has destination", ik_Class, True, None)
		self.update_relationship(rel_Relationship__has_sibling__Relationship, ik_Relationship, "has sibling", ik_Relationship, True, None)

		rel_Class__has__Instance = self.create_relationship(ik_Class, "has", ik_Instance, False, None, KnownRelationshipGuids.Class__has__Instance)
		rel_Instance__for__Class = self.create_relationship(ik_Instance, "for", ik_Class, True, None, KnownRelationshipGuids.Instance__for__Class)
		self.update_relationship(rel_Class__has__Instance, None, None, None, None, rel_Instance__for__Class)
		self.update_relationship(rel_Instance__for__Class, None, None, None, None, rel_Class__has__Instance)

		ik_MaximumLength = self.create_instance_of(ik_NumericAttribute, KnownAttributeGuids.MaximumLength)
		self.set_attribute_value(ik_MaximumLength, att_Name, "Maximum Length")

		ik_MinimumValue = self.create_instance_of(ik_NumericAttribute, KnownAttributeGuids.MinimumValue)
		self.set_attribute_value(ik_MinimumValue, att_Name, "Minimum Value")
		ik_MaximumValue = self.create_instance_of(ik_NumericAttribute, KnownAttributeGuids.MaximumValue)
		self.set_attribute_value(ik_MaximumValue, att_Name, "Maximum Value")

		self.set_attribute_value(ik_Class, att_Name, "Class")
		self.update_class_instances()
		
	def update_relationship(self, relationship : InstanceReference, src_class : InstanceReference, type : str, dst_class : InstanceReference, singular: bool, sibling_relationship : InstanceReference):
		if src_class is not None:
			self.assign_relationship(relationship, self.get_instance_by_global_identifier(KnownRelationshipGuids.Relationship__has_source__Class), src_class)
		if type is not None:
			self.assign_attribute(relationship, self.get_instance_by_global_identifier(KnownAttributeGuids.RelationshipType), type)
		if dst_class is not None:
			self.assign_relationship(relationship, self.get_instance_by_global_identifier(KnownRelationshipGuids.Relationship__has_destination__Class), dst_class)
		if singular is not None:
			self.assign_attribute(relationship, self.get_instance_by_global_identifier(KnownAttributeGuids.Singular), singular)
		if sibling_relationship is not None:
			self.assign_relationship(relationship, self.get_instance_by_global_identifier(KnownRelationshipGuids.Relationship__has_sibling__Relationship), sibling_relationship)

	def update_class_instances(self):

		ik_Class = self.get_instance_by_global_identifier(KnownClassGuids.Class)
		rel_Class__has__Instance = self.get_instance_by_global_identifier(KnownRelationshipGuids.Class__has__Instance)
		rel_Instance__for__Class = self.get_instance_by_global_identifier(KnownRelationshipGuids.Instance__for__Class)
		
		insts = self.get_instances()
		for inst in insts:
			clasz = self.get_instance_by_key(InstanceKey(1, inst.get_instance_key().get_class_index() ))
			self.assign_relationship(clasz, rel_Class__has__Instance, inst)
			self.assign_relationship(inst, rel_Instance__for__Class, clasz)
	
	def after_init(self):
		pass

	def init(self):
		self.before_init()
		self._init()
		self.after_init()