#!/usr/bin/env python3
# coding:utf-8

import sys
import argparse
import getpass

import logging
from logging.handlers import SysLogHandler

#from .mods import gosa_postfix
from .mods import gosa_account
from .mods import gosa_group
from .mods import gosa_groupmail
from .mods import gosa_mailaccount
from .mods import gosa_netatalk_account
from .mods import gosa_ou
from .mods import gosa_password
from .mods import gosa_posix_account
from .mods import gosa_samba_account
from .mods import provision_samba_ad
from .mods.plugin import ModList
from cplLdaplib import Ldap
import cplConfig

msg0001 = 'known modules: {0}'
msg0002 = 'configuration error: {0}'
msg0101 = 'argument {0}: \'{1}\''
msg0102 = 'argument assignment failed for: \'{0}\''
msg0103 = 'passing \'{0}\' bevore assignment'
msg0200 = 'missing action for this module'
msg0201 = 'calling module: \'{0}\''
msg0202 = 'module \'{0}\' not implemented jet'
msg0203 = 'connecting to \'{0}\' as \'{1}\''
msg0204 = 'fetching configuration for: \'{0}\''
msg0205 = 'failed to connect to ldap: \'{0}\''
msg0206 = 'discarding action \'{0}\' for module \'{1}\''

class DummyLogger():

	def debug(self, *args): pass
	info = debug
	warn = debug
	warning = debug
	error = debug
	crit = debug
	critical = debug


# TODO: create --action parameter dependant on --module
# to prevent lock/unlock on other modules than password
# and remove create, remove from password module
def getArgs(arg_store):

	parser = argparse.ArgumentParser(
		formatter_class=argparse.RawDescriptionHelpFormatter,
		description='''
PostCmd is used to post execute commands after gosa is done. Add a
post(create|modify|remove) directive in the apropriate tab section
in gosa.conf.''',
		epilog='''
Example:
  <usertabs>
    ...
    <tab class="posixAccount" name="Unix"
      postcreate="postcmd.py -c add -m usr_posix %uid"
      postmodify="postcmd.py -c mod -m usr_posix %uid"
      postremove="postcmd.py -c del -m usr_posix %uid"
    />
    ...
  </usertabs>''',
		)

	parser.add_argument(
		'-m', '--module',
		dest = 'module',
		nargs = 1,
		required = True,
		choices = ModList.getModules(),
		help = 'The command module to be called',
		)

	parser.add_argument(
		'--pre',
		action = 'store_true',
		dest = 'pre',
		required = False,
		default = False,
		help = 'call pre-exec code routines',
		)

	parser.add_argument(
		'-a', '--action',
		dest = 'action',
		nargs = 1,
		required = False,
		choices = ['create', 'add', 'modify', 'mod', 'remove', 'del', 'lock', 'unlock', ],
		help = 'The action to perform',
		)

	parser.add_argument(
		dest = 'data',
		metavar = 'process data',
		nargs='*',
		help = 'The data parsed to the module, i.e. a username'
		)

	parser.add_argument(
		'-c', '--config',
		dest = 'cfile',
		nargs = 1,
		required = False,
		default = ['/etc/gosa/postcmd.conf', ],
		metavar = '<config file>',
		help = 'the configuration to use',
		)

	parser.add_argument(
		'-l', '--logfile',
		dest = 'lfile',
		nargs = 1,
		required = False,
		default = None,
		metavar = '<log file>',
		help = 'a log file to use',
		)

	parser.add_argument(
		'-v', '--verbose',
		action = 'store_true',
		dest = 'verbose',
		required = False,
		default = False,
		help = 'get verbose output from the logger',
		)

	parser.add_argument(
		'-d', '--debug',
		action = 'store_true',
		dest = 'debug',
		required = False,
		default = False,
		help = 'get debug console logging output',
		)

	parser.add_argument(
		'--parameters',
		action = 'store_true',
		dest = 'param',
		required = False,
		default = False,
		help = 'dump cli parameters and exit',
		)

	re_vars = parser.parse_args(namespace = arg_store)
	arg_store.initDone()
	return vars(re_vars)



class ArgsParse(object):

	__initdone = False

	def __init__(self, logger=None):

		if logger:
			ArgsParse.logger = logger
		else:
			ArgsParse.logger = DummyLogger()


	def initDone(self): ArgsParse.__initdone = True

	def getArg(self, name):

		try:
			return getattr(ArgsParse, '__' + name)

		except AttributeError as e:

			if not ArgsParse.__initdone:
				raise AttributeError(e)

			return None


	def setArg(self, name, val, keeplist=False):

		if isinstance(val, type(None)):
			self.logger.debug(msg0103.format(name))
			return False

		if isinstance(val, list) and not keeplist:
			val = val[0]

		setattr(ArgsParse, '__' + name, val)
		self.logger.debug(msg0101.format(name, val))

		return True


	def getAction(self):

		val = self.getArg('action')
		if self.pre: return 'pre' + val
		return val


	def setAction(self, val):

		if not self.setArg('action', val): return

		setattr(ArgsParse, '__action', {
			'create' : 'create',
			'modify' : 'modify',
			'remove' : 'remove',
			'add' : 'create',
			'mod' : 'modify',
			'del' : 'remove',
			'lock' : 'lock',
			'unlock' : 'unlock',
			'call' : 'call',
			}[self.getArg('action').lower()])

		self.logger.debug(msg0101.format('action', self.getArg('action')))


	module = property(
		lambda self: self.getArg('module'),
		lambda self, val: self.setArg('module', val))
	pre = property(
		lambda self: self.getArg('pre'),
		lambda self, val: self.setArg('pre', val))
	action = property(
		getAction,
		setAction)
	data = property(
		lambda self: self.getArg('data'),
		lambda self, val: self.setArg('data', val, True))
	cfile = property(
		lambda self: self.getArg('cfile'),
		lambda self, val: self.setArg('cfile', val))
	lfile = property(
		lambda self: self.getArg('lfile'),
		lambda self, val: self.setArg('lfile', val))
	verbose = property(
		lambda self: self.getArg('verbose'),
		lambda self, val: self.setArg('verbose', val))
	debug = property(
		lambda self: self.getArg('debug'),
		lambda self, val: self.setArg('debug', val))
	param = property(
		lambda self: self.getArg('param'),
		lambda self, val: self.setArg('param', val))



class ModuleCall(ArgsParse):

	def __init__(self):

		# set up config
		self.config = cplConfig.Config(self.cfile, self.logger)

		# get ldap configuration
		self.__getGosaLdapConfig()

		# connect to ldap
		self.gosa_ldap = Ldap(self.logger)
		if not self.connectToLdap(self.gosa_ldap, self.gosa_ldap_cfg):
			self.__moduleExit(1, msg0205.format(self.gosa_ldap_cfg['host']))


	def __getGosaLdapConfig(self):

		gosa_ldap_cfg_def = {
			'host' : 'localhost',
			'binddn' : 'cn=admin,cn=config',
			'bindpwd' : '',
			'searchbase' : '',
			}

		gosa_ldap_cfg_ver = {
			'host' : ('str', ),
			'binddn' : ('str', ),
			'bindpwd' : ('str', ),
			'searchbase' : ('str', ),
			}

		# get Gosa Ldap config
		self.logger.debug(msg0204.format('GosaLdap'))
		self.config.updateDefaults('GosaLdap', gosa_ldap_cfg_def)
		self.config.updateVerify(gosa_ldap_cfg_ver)
		self.gosa_ldap_cfg = self.config.getSection('GosaLdap')


	def connectToLdap(self, ldap, ldap_cfg):

		# connect to ldap
		h = ldap_cfg['host']
		b = ldap_cfg['binddn']
		p = ldap_cfg['bindpwd']
		self.logger.debug(msg0203.format(h, b))

		if not ldap.connect(h, b, p):
			return False

		return True


	def __moduleExit(self, val, msg):
		"""normal termination
		  val: 0, msg: stdout, log: info
		error termination
		  val: >0, msg: stderr, log: error
		 special termination
		  val: 255, msg: stdout, log: warn, exit: 0"""

		# TODO: record calling class/method in log
		if msg:
			if val > 0 and val < 255:
				sys.stderr.write(msg)
				self.logger.error(msg)
			else:
				sys.stdout.write(msg)
				if val == 0:
					self.logger.info(msg)
				else:
					self.logger.warn(msg)
		if val == 255:
			val = 0

		sys.exit(val)


	def __str__(self):

		return self.msg



	def getLogger(self):

		return self.logger


	def getConfig(self):

		return self.config


	def getGosaLdap(self):

		return self.gosa_ldap


	def getExit(self):

		return self.__moduleExit


	def dumpParameters(self):

		param_lst = (
			'module',
			'action',
			'pre',
			'data',
			'cfile',
			'lfile',
			'verbose',
			'debug',
			'param',
			)

		for p in param_lst:

			self.logger.info('value of {0}: {1}'.format(
				p, getattr(self, p)))

		#print(ArgsParse.__dict__.keys())
		#for key, val in ArgsParse.__dict__.iteritems():
		#	if not isinstance(val, (bool, str, type(None))): continue
		#	print('{0} : {1}'.format(key, val))


	def __call__(self):

		if self.param:
			self.dumpParameters()
			return

		if self.module in ('ProvisionSambaAD', ):

			if self.action != None:
				self.logger.info(msg0206.format(self.action, self.module))

			self.action = 'call'
		else:
			if self.action == None:
				self.__moduleExit(1, msg0200)

		if self.module in ModList.getModules():

			self.logger.debug(msg0201.format(self.module))
			mod = ModList.getModule(self.module)(self)
			getattr(mod, self.action)()

		else:
			self.logger.debug(msg0202.format(self.module))



def main():

	# TODO: close stuff like ldap or logfiles on exit
	# TODO: disclose password in log

	# setup logger
	log_encoding='utf-8'

	logger = logging.getLogger('pygosa')
	logger.setLevel(logging.DEBUG)

	# syslog
	if getpass.getuser() == 'root':
		s_handler = logging.handlers.SysLogHandler(
			address='/dev/log',
			facility=SysLogHandler.LOG_SYSLOG)
	else:
		s_handler = logging.handlers.SysLogHandler(
			address='/dev/log',
			facility=SysLogHandler.LOG_USER)
	s_formatter = logging.Formatter('%(name)s[%(process)d]: ' +
		'%(levelname)s - %(message)s')
	s_handler.setFormatter(s_formatter)
	s_handler.setLevel(logging.INFO)
	# to debug argument parsing
	#s_handler.setLevel(logging.DEBUG)
	logger.addHandler(s_handler)

	# parse command line aruments
	args_parse = ArgsParse(logger)
	getArgs(args_parse)

	if args_parse.verbose:
		s_handler.setLevel(logging.DEBUG)

	# console
	if args_parse.debug:
		c_handler = logging.StreamHandler()
		c_formatter = logging.Formatter('%(asctime)s - %(levelname)s ' + 
			'[%(module)s/%(funcName)s/%(lineno)d] %(message)s')
		c_handler.setFormatter(c_formatter)
		if args_parse.verbose:
			c_handler.setLevel(logging.DEBUG)
		else:
			c_handler.setLevel(logging.INFO)
		logger.addHandler(c_handler)

	# log to file if logfile param is given
	if args_parse.lfile:
		f_handler = ownedFileHandler(args_parse.lfile, mode='w', encoding=log_encoding)
		f_formatter = logging.Formatter('%(asctime)s - %(levelname)s ' +
			'[%(module)s/%(funcName)s/%(lineno)d] %(message)s')
		f_handler.setFormatter(f_formatter)
		if args_parse.verbose:
			f_handler.setLevel(logging.DEBUG)
		else:
			f_handler.setLevel(logging.INFO)
		logger.addHandler(f_handler)

	# debug modules
	logger.debug(msg0001.format(ModList.getModules()))

	# init and call main post call
	modulecall = ModuleCall()

	try:
		modulecall()

	except cplConfig.ConfigError as e:
		logger.error(msg0002.format(str(e)))
		sys.stderr.write(msg0002.format(str(e)))
		sys.exit(254)



if __name__ == '__main__':

	main()

