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

import pgdb
#from pgdb import connect
import pg
import textwrap


msg0000 = "fetching configuration for: '{0}'"
msg0001 = "unknown SQL DB type: '{0}'"
msg0002 = "missing roundcube DB connection parameter: '{0}'"
msg0003 = "Removing roundcube user '{0}' from DB"
msg0004 = "Removed {0} user objects from DB"
msg0100 = "DB '{0}' connected"
msg0101 = "DB '{0}' disconnected"
msg0102 = "DB '{0}' connection error:\n{1}\n"
msg0103 = "DB '{0}' unable to reconnect to database"
msg0104 = "DB connected but no revision data received"
msg0105 = "RC revision data: {0}"
msg0106 = "DB error: {0}"

class Roundcube():

	def __init__(self, postcmd):

		# get roundcube module configuration
		self.rc_config = self.__getRoundcubeConfig()

		self.rc = self.rc_config['handleroundcube']
		if not self.rc: return

		self.db = None

		for opt in ('rchost', 'rcdb', 'rcdbuser', 'rcdbpwd'):
			if not self.rc_config[opt]:
				self.logger.error(msg0002.format(opt))
				return

		if self.rc_config['rcdbtype'] == 'psql':
			self.db = PostgresDB(self.rc_config, self.logger)
		elif self.rc_config['rcdbtype'] == 'mysql':
			self.db = None
			self.logger.info("SQL DB type 'mysql' not implemented yet")
		else:
			self.db = None
			self.logger.error(msg0001.format(self.rc_config.rcdbtype))


	def __getRoundcubeConfig(self):

		cfg_def = {
			'handleroundcube' : False,
			'rcdbtype' : 'psql',
			'rchost' : None,
			'rcdbport' : 5432,
			'rcdb' : None,
			'rcdbuser' : None,
			'rcdbpwd' : None,
		}

		cfg_ver = {
			'handleroundcube' : ('bool', ),
			'rcdbtype' : ('str', ),
			'rchost' : ('str', ),
			'rcdbport' : ('int', ),
			'rcdb' : ('str', ),
			'rcdbuser' : ('str', ),
			'rcdbpwd' : ('str', ),
		}

		# get roundcube connection configuration
		self.logger.debug(msg0000.format('Roundcube'))
		self.config.updateDefaults('Roundcube', cfg_def)
		self.config.updateVerify(cfg_ver)

		return self.config.getSection('Roundcube')


	def removeRoundcubeUser(self):

		if not self.rc or not self.db: return

		self.logger.info(msg0003.format(self.cn))
		res = self.db.removeRcUser(self.cn)
		self.logger.info(msg0004.format(str(res)))


class PostgresDB(object):

	def __init__(self, config, logger=None):

		# setup logger
		if logger:
			self.logger = logger
		else:
			self.logger = self.__dummyLogger()

		self.connected = False
		self.__dbhost = config['rchost']
		self.__dbport = config['rcdbport']
		self.__dbname = config['rcdb']
		self.__dbuser = config['rcdbuser']
		self.__dbpwd = config['rcdbpwd']

		if not self.connectDB(): return None

		try:
			self.psqlcur.execute(textwrap.dedent("""
				SELECT name, value
				  FROM system
				 WHERE name = 'roundcube-version'
				"""))
			self.psqlcon.commit()
			rc_rev = self.psqlcur.fetchone()
			if rc_rev == None:
				self.logger.error(msg0104)
				self.disconnectDB()
				return None
			self.logger.debug(msg0105.format(rc_rev))

		except pg.DatabaseError as e:

			self.psqlcon.rollback()
			if "relation \"system\" does not exist" in str(e) or \
					"no result." == str(e):

				self.logger.error(msg0104)
			else:
				self.logger.error(msg0106.format(repr(e)))

			self.disconnectDB()
			return None


	def __del__(self):

		if self.connected: self.disconnectDB()


	class __dummyLogger():

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


	def connectDB(self):

		try:
			self.psqlcon = pgdb.connect(
				host=self.__dbhost + ':' + str(self.__dbport),
				database=self.__dbname,
				user=self.__dbuser,
				password=self.__dbpwd)

		except pg.InternalError as e:

			self.logger.error(msg0102.format(self.__dbname, e))
			return False

		self.psqlcur = self.psqlcon.cursor()
		self.connected = True
		self.logger.debug(msg0100.format(self.__dbname))
		return True


	def disconnectDB(self):

		self.psqlcur.close()
		self.psqlcon.close()
		self.connected = False
		self.logger.debug(msg0101.format(self.__dbname))


	def query(self, sql):

		retry = 0

		while retry < 3:

			try:
				self.logger.debug("SQL: {0}".format(sql))
				self.psqlcur.execute(sql)
				self.psqlcon.commit()
				break

			except (pgdb.DatabaseError,  pgdb.InterfaceError,
				pgdb.OperationalError) as e:

				self.disconnectDB()
				self.connectDB()
				retry += 1

		else:
			self.logger.error(msg0103.format(self.__dbname))
			self.connected = False


	def removeRcUser(self, user):

		if not self.connected: return None

		self.query(textwrap.dedent("""
			DELETE
			  FROM users
			 WHERE lower(username) = lower('{0}')
		""").format(user))

		return self.psqlcur.rowcount

