import logging
from datetime import date,time,datetime
from orangescape.model.engine.Component import Component
from orangescape.model.engine.exception.Error import InstanceNotFoundError
from orangescape.model.engine.util.DateUtil import xldate_from_datetime_tuple
from orangescape.model.server.util.MetadataSync import moveApproleToNextState
import appconfig as config

def gettimeVal():
    from orangescape.model.engine.execution.DateandTime import NOW
    return NOW([])

GLOBAL_ID = 'global'

class UserAuthentication:
    userSheetMetadata=None
    companySheet=None
    application=None
    userloginSheet=None
    loginId=None

    def __init__(self,session,loginId=None):
        if loginId:
            loginId=loginId.lower().strip()
        self.loginId=loginId
        self.application = session.getApplication()
        self.userSheetMetadata = self.application.getSheetMetadata('User')
        self.companySheet= self.application.getSheetMetadata('Company')
        self.appRole=self.application.getSheetMetadata('AppRole')
        self.userloginSheet=self.application.getSheetMetadata('UserLoginModel')
        self.guestsession = session

    def authenticateUser(self):
          loggedintime=gettimeVal()
          companyRole=None
          self.initialize()
          isNewUser = False
          #global instance creation
          globalInst = None
          companyRole = None
          newSheetDict = dict()
          if self.application.getSheetMetadataDict().__contains__('Global'):
            globalSheet=self.application.getSheetMetadata('Global')
            globalInst=self.getGlobalInst(globalSheet)
            if globalInst==None:
                globalInst=self.createGlobalInst(globalSheet)
#          if self.needMetadataSync(globalInst):
#              if config.env != config.GAESQL:
#                  from orangescape.model.server.util.MetadataSync import MetadataSync
#                  newSheetDict = MetadataSync(self.component, globalInst).createObjects(globalInst)

          if self.loginId:
              companyId=self.getCompanyId()
              companyInst=self.getCompany(companyId,globalInst)

              companyRole=self.getRoleForCompany()
              if not companyInst:
                  companyInst = self.createCompany(companyId,globalInst)
                  userins = None
              else:
                  userins = self.getUser(self.loginId)
              if not userins:
                  isNewUser = True
                  if self.application.getSheetMetadataDict().has_key('UserChanges'):
                      userins = self.createUserViaUserChange(companyInst)
                  else:
                      userins= self.createUser(companyInst)
                      self.guestsession.setUser(userins)
                      #Moves the user to active state if exist
                      self.moveUsertoActiveState(userins, companyInst)
    #            else:
                  #update company ref
                  #usercompanyid=userins.getValue('Company')
                  #if not(usercompanyid and usercompanyid== companyins.getSheetId()):
                     #self.component.update(userins,{'Company':companyins.getSheetId()}

              self.guestsession.setUser(userins)

              if config.env == config.GAESQL and self.needMetadataSync(globalInst):
                  from google.appengine.api.labs import taskqueue
                  logging.info('Adding taskque for metadata sync')
                  url = '/%s/1/metadatasync?userid=%s'%(self.application.getSheetId(), userins.getSheetId())
                  taskqueue.add(url=url, method='GET')
              elif config.env != config.GAESQL and self.needMetadataSync(globalInst):
                  from orangescape.model.server.util.MetadataSync import MetadataSync
                  newSheetDict = MetadataSync(self.component, globalInst).createObjects(globalInst)
              if newSheetDict.has_key(self.appRole.getSheetId()):
                  newApproleDict = newSheetDict[self.appRole.getSheetId()]
                  for appRoleInst in newApproleDict.itervalues():
                      moveApproleToNextState(self.component, appRoleInst, companyInst)
              self.updateUserProcessInstance(userins, companyInst)
              if not companyRole:
                  companyRole=self.createRoleForCompany(globalInst)
              if isNewUser:
                  self.createAppRoleSubscriptions(companyRole,companyInst,userins)
              moveApproleToNextState(self.component, companyRole, companyInst)
              self.updateLoginTime(userins, loggedintime)
              self.guestsession.commit()
              return userins.getSheetId()

    def initialize(self):
        self.component=Component(self.guestsession)
        userins = self.guestsession.getIfExists('guest', self.userSheetMetadata)
        if not userins:
            logging.warn("User Instance for guest user does not exist")
            companyins = self.component.new(self.companySheet, {'SheetName':'OS'},'OS')
            if self.application.getSheetMetadataDict().has_key('UserChanges'):
    #              referenceCell = self.userSheetMetadata.getPublicCellMetadata('Company')
    #              userins = self.component.create(companyins,referenceCell, {},'guest')
    #              userins.set(userins.getCellMetadata('FirstName'), 'guest')
    #              userins.set(userins.getCellMetadata('UserId'), 'guest')
                #'FirstName':'guest','UserId':'guest'

                usrChgMetadata = self.guestsession.getApplication().getSheetMetadata('UserChanges')
                referenceCell = usrChgMetadata.getPublicCellMetadata('Company')
                userChgIns = self.component.create(companyins, referenceCell, {'FirstName':'guest','UserId':'guest'},'guest_chg')
                userins = self.component.new(self.userSheetMetadata, {'Company':companyins.getSheetId(),'Current':userChgIns.getSheetId()},'guest')
            else:
                referenceCell = self.userSheetMetadata.getPublicCellMetadata('Company')
                userins=self.component.create(companyins,referenceCell, {'FirstName':'guest','UserId':'guest'},'guest')
            self.guestsession.commit()
        self.guestsession.setUser(userins)

    def updateUserProcessInstance(self, userIns, companyIns):
        processInstanceSheet=self.application.getSheetMetadata('ProcessInstance')
        metadataSheet=self.application.getSheetMetadata('Metadata')
        branchList = list()
        try:
            params = {'Tenant':companyIns.getSheetId()}
            conditionList = list()
            conditionList.append((processInstanceSheet.getPublicCellMetadata('InstanceId').getSheetId(), '=', userIns.getSheetId()))
            baseQuery= ('and' , conditionList)
            processInstanceList = self.guestsession.advQuery(processInstanceSheet, 1000,baseQuery)
            userMetadataInstance = self.guestsession.getIfExists('M_'+self.userSheetMetadata.getSheetId(),metadataSheet)
            for processinst in processInstanceList:
                if not processinst.getValue('InstanceMetadata'):
                    if userMetadataInstance:
                        params["InstanceMetadata"] = userMetadataInstance.getSheetId()
                if not processinst.getValue('InstanceActivity'):
                    if branchList.__len__()==0:
                        branchList = self.getBranchIdListForSheet(self.userSheetMetadata)
                    for branchId in branchList:
                        if self.userSheetMetadata.getPublicCellMetadataDict().has_key(branchId+'_Status') and self.userSheetMetadata.getPublicCellMetadataDict().has_key(branchId+'_ProcessInstance') and  userIns.getValue(branchId+'_ProcessInstance') == processinst.getSheetId():
                            params['InstanceActivity'] =  userIns.getValue(branchId+'_Status')
                    if userIns.getValue('ProcessInstance') == processinst.getSheetId():
                            params['InstanceActivity'] =  userIns.getValue('Status')
                self.component.update(processinst, params)
        except InstanceNotFoundError, e:
              logging.warn("ProcessInstance/Metadata Instance for everyone does not exist")

    def getBranchIdListForActivity(self, sheet, branch, branchList):
        from orangescape.model.engine.engineconstants import AND_PROCESS, REAL_OR_PROCESS
        if branch.getValue('NodeType')==AND_PROCESS or branch.getValue('NodeType')==REAL_OR_PROCESS:
            branchList.append(branch.getSheetId())
        for activity in branch.getActivityDefinitionList():
            for childBranch in activity.getBranchList():
                self.getBranchIdListForActivity(sheet, childBranch, branchList)

    def getBranchIdListForSheet(self, sheet):
        from orangescape.model.engine.engineconstants import AND_PROCESS, REAL_OR_PROCESS
        branchSheetIdList= list()
        workFlowRootbranch = self.userSheetMetadata.getWorkFlowRoot()
        if workFlowRootbranch:
            actList = workFlowRootbranch.getActivityDefinitionList()
            for act in actList:
                parentBranch =act.getBranch()
                if act.getValue('NodeType')==AND_PROCESS or act.getValue('NodeType')==REAL_OR_PROCESS:
                    #delete public cells if activity type is parallel
                    branchList = act.getBranchList()
                    sheetObj=parentBranch.getSheetMetadata()
                    for branch in branchList:
                        self.getBranchIdListForActivity(sheetObj, branch, branchSheetIdList)
        return branchSheetIdList

    def needMetadataSync(self, globalins):
        return (globalins.getValue('DeployedAt') != config.deployedat or config.env == config.SL)

    def createAppRoleSubscriptions(self,appRoleInst, companyInst,userins):
        roleSubscription = self.application.getSheetMetadata('RoleSubscription')
        tenantCell = roleSubscription.getPublicCellMetadata('Tenant')
        everyoneSub = self.component.create(companyInst,tenantCell,{'PrimaryUser':userins.getSheetId(),'AppRole':appRoleInst.getSheetId()})
        userSub = self.component.create(companyInst,tenantCell,{'PrimaryUser':userins.getSheetId()})
        workFlowRootbranch = roleSubscription.getWorkFlowRoot()
        if workFlowRootbranch:
              actList = workFlowRootbranch.getActivityDefinitionList()
              if len(actList) > 2 :
                  self.component.submit(everyoneSub, actList[0])
                  self.component.submit(userSub, actList[0])

    def getRoleForCompany(self):
        try:
            conditionList =  list()
            conditionList.append((self.appRole.getPublicCellMetadata('SheetName').getSheetId(), '=', 'everyone'))
            baseQuery= ('and' , conditionList)
            appRoleList = self.guestsession.advQuery(self.appRole, 1000,baseQuery)
            if appRoleList.__len__()>0:
              return appRoleList.pop()
        except InstanceNotFoundError, e:
              logging.warn("AppRole Instance for everyone does not exist")

    def createRoleForCompany(self,GlobalInst):
         return self.component.new(self.appRole, {'SheetName':'everyone','Global':GlobalInst.getSheetId()})

    def getGlobalInst(self, globalSheet):
        return self.guestsession.getIfExists(GLOBAL_ID, globalSheet)

    def getCompany(self,companyId,globalInst):
         #Checks weather company instance exist
         companyins = self.guestsession.getIfExists('Company_'+companyId,self.companySheet)
         if companyins:
             if self.hasGlobalRef():
                 if companyins.getValue('Global') != globalInst.getSheetId():
                     self.updateGlobalInstForCompany(companyins,globalInst)
             return companyins
         else:
            logging.warn("Company Instance for "+companyId+" does not exist")
            return None

    def hasGlobalRef(self):
        if self.companySheet.getPublicCellMetadataDict().__contains__('Global'):
            if self.companySheet.getPublicCellMetadata('Global').getReferredSheetMetadata().getSheetName()=='Global':
                return True
        return False

    def getUser(self,loginId):
         try:
            conditionList = list()
            conditionList.append((self.userSheetMetadata.getPublicCellMetadata('UserId').getSheetId(), '=', str(loginId)))
            baseQuery= ('and', conditionList)
            userList = self.guestsession.advQuery(self.userSheetMetadata, 1,baseQuery)
            if userList.__len__()>0:
              return userList.pop()
         except InstanceNotFoundError, e:
            logging.warn("User Instance for "+loginId+" does not exist")

    def createGlobalInst(self, globalSheet):
         import appconfig
         celldict=globalSheet.getPublicCellMetadataDict()
         params={'SheetName':'Global', 'SheetId': GLOBAL_ID}
         appconfigDict = appconfig.__dict__
         for cellName in celldict:
              if appconfigDict.has_key(cellName):
                  params[cellName]=appconfigDict[cellName]
         globalInst=self.component.new(globalSheet, params)
         return globalInst

    def createCompany(self, companyid, globalInst):
         #create Company instance
         params={'SheetName':companyid}
         if self.hasGlobalRef():
                 params['Global']=globalInst.getSheetId()
         companyins=self.component.new(self.companySheet, params, 'Company_'+companyid)
         return companyins

    def createUserViaUserChange(self, companyIns):
        from orangescape.model.engine.execution.ProcessUtil import getActionByName
        usrChangeMetadata = self.guestsession.getApplication().getSheetMetadata('UserChanges')
        refCell = usrChangeMetadata.getPublicCellMetadata('Company')
        usrChgIns = self.component.create(companyIns, refCell, {'Tenant':companyIns.getSheetId(),'FirstName':self.loginId,'UserId':self.loginId,'SheetName':self.loginId})
        saveAction = None
        try:
            saveAction = getActionByName(usrChangeMetadata, 'Save')
        except Exception, e:
            raise Exception('Could not create your account %s. Action "Save" not found in UserChanges. You mighted deleted or renamed it. Please fix it and redeploy'%(self.loginId))
        self.component.executeAction(usrChgIns, saveAction)
        self.component.update(usrChgIns, {'AssignedTo':self.loginId.split('@')[0]})
        userIns = self.guestsession.get(usrChgIns.getValue('UserContextCell'), self.userSheetMetadata)
        self.component.update(userIns, {'Company':companyIns.getSheetId(), 'Tenant':companyIns.getSheetId()})
        return userIns

    def createUser(self,companyins):
        #Creates User
        referenceCell = self.userSheetMetadata.getPublicCellMetadata('Company')
        userins=self.component.create(companyins,referenceCell, {'Tenant':companyins.getSheetId(),'FirstName':self.loginId,'UserId':self.loginId,'SheetName':self.loginId})
        return userins

    def moveUsertoActiveState(self,userins,companyInst):
        #Moves the user to active state if activity exist
        workFlowRootbranch = self.userSheetMetadata.getWorkFlowRoot()
        if workFlowRootbranch:
              actList = workFlowRootbranch.getActivityDefinitionList()
              if len(actList) > 2 :
                  if actList[1].getNodeType()=="Workflow" and actList[1].getAssignedFor()=="User":
                      from orangescape.model.engine.execution.ProcessUtil import getAssignedUser
                      userId = getAssignedUser(self.guestsession, userins, actList[1])
                      emailId = userId+"@"+companyInst.getValue('SheetName')
                      if not self.getUser(emailId):
                          referenceCell=self.userSheetMetadata.getPublicCellMetadata('Company')
                          submitUserins=self.component.create(companyInst,referenceCell, {'FirstName':emailId,'UserId':emailId,'SheetName':emailId})
                          self.guestsession.commit()
                          self.component.submit(submitUserins, actList[0])
                  self.component.submit(userins, actList[0])
                  self.component.update(userins, {'Company':companyInst.getSheetId(), 'Tenant':companyInst.getSheetId()})

    def getCompanyId(self):
        #company id is got from appconfig if configured else companyid is got from user EmailId
        from appconfig import COMPANYID
        companyid=None
        if COMPANYID:
              return COMPANYID
        if companyid==None:
            if self.loginId.find('@')>=0:
                startindex=  self.loginId.index('@')
                return  self.loginId[self.loginId.index('@')+1:]
            else:
                raise NameError('Companyid not specified')

    def updateLoginTime(self,userins,loggedintime):
        #has to be removed after implementing http session in Enterprise
         userloginCell=self.userSheetMetadata.getPublicCellMetadata('UserLoginModel')
         userlogininsval=userins.get(userloginCell)
         if userlogininsval:
             #block is to check whether valid userlogin instance is available for user.Required for the consequent url after login
             userloginins=self.guestsession.get(userlogininsval,self.userloginSheet)
             loginCell=self.userloginSheet.getPublicCellMetadata('LoginDate')
             logoutCell=self.userloginSheet.getPublicCellMetadata('LoggoutDate')
             loginTime=userloginins.get(loginCell)
             logoutTime=userloginins.get(logoutCell)
             if logoutTime and loginTime<logoutTime:
                 referenceCell=self.userloginSheet.getPublicCellMetadata('UserLoggedIn')
                 userloginIns=self.component.create(userins,referenceCell, {'UserLoggedIn':userins.getSheetId(),'LoginDate':loggedintime})
                 self.component.update(userins, {'LastLoggedinTime':loggedintime,'UserLoginModel':userloginIns.getSheetId()})
         else:
              self.createUserLoginInstance(userins,loggedintime)

    def updateGlobalInstForCompany(self,companyins,globalInst):
        self.component.update(companyins, {'Global':globalInst.getSheetId()})

    def createUserLoginInstance(self,userins,loggedintime):
        referenceCell=self.userloginSheet.getPublicCellMetadata('UserLoggedIn')
        userloginIns=self.component.create(userins,referenceCell, {'UserLoggedIn':userins.getSheetId(),'LoginDate':loggedintime})
        self.component.update(userins, {'UserLoginModel':userloginIns.getSheetId()})

    def logOutUser(self,loginId):
      self.initialize()
      if loginId:
          userins=None
          try:
             userins = self.guestsession.get(loginId, self.userSheetMetadata)
          except InstanceNotFoundError, e:
             logging.warn("User Instance for "+loginId+" does not exist")
          if userins:
             userloginId=userins.get(self.userSheetMetadata.getPublicCellMetadata('UserLoginModel'))
             userloginins=self.guestsession.get(userloginId,self.userloginSheet)
             logouttime=gettimeVal()
             self.component.update(userloginins, {'LoggoutDate':logouttime})
             self.guestsession.commit()
