#!/usr/bin/python2.7
# -*- coding: utf-8 -*-

"""
install/remove/update apps via umc
"""

import sys
import time
import optparse
import univention.config_registry

oldlib = False
try:
	import univention.lib.umc
except ImportError as e:
	# can be removed after 4.1 is out of maintanance
	import univention.lib.umc_connection
	import httplib
	import socket
	oldlib = True


def get_default_values(app):
	from univention.appcenter.ini_parser import IniSectionAttribute
	from univention.appcenter.settings import StringSetting, IntSetting, BoolSetting, ListSetting, UDMListSetting, FileSetting, PasswordFileSetting, PasswordSetting, StatusSetting
	for kls in [StringSetting, IntSetting, BoolSetting, ListSetting, UDMListSetting, FileSetting, PasswordFileSetting, PasswordSetting, StatusSetting]:
		if 'default_for_testing' not in kls._attrs:
			attr = IniSectionAttribute()
			attr.name = 'default_for_testing'
			kls._attrs['default_for_testing'] = attr
	from univention.appcenter.app_cache import Apps as FindApps
	app = FindApps().find(app)
	app = FindApps().find_candidate(app) or app
	ret = {}
	for setting in app.get_settings():
		value = setting.default_for_testing
		if value is not None:
			ret[setting.name] = setting.sanitize_value(app, value)
	return ret


class Apps(object):

	def __init__(self):
		usage = '''%prog [options] '''
		description = sys.modules[__name__].__doc__
		parser = optparse.OptionParser(usage=usage, description=description)
		parser.add_option("-U", "--username", action="store", dest="username", help="username")
		parser.add_option("-p", "--password", action="store", dest="password", help="password")
		parser.add_option("-a", "--app", action="store", dest="app", help="app id")
		parser.add_option("-r", "--remove", action="store_true", dest="remove", default=False, help="remove app")
		parser.add_option("-u", "--update", action="store_true", dest="update", default=False, help="upgrade app")
		(self.options, self.args) = parser.parse_args()
		assert self.options.username is not None
		assert self.options.password is not None
		assert self.options.app is not None
		self.client = None
		self.ucr = univention.config_registry.ConfigRegistry()
		self.ucr.load()
		print(self.options)

	def umc_old(self, path, data):
		if self.client is None:
			self.client = univention.lib.umc_connection.UMCConnection('localhost', username=self.options.username, password=self.options.password)
		try:
			result = self.client.request(path, data)
		except httplib.BadStatusLine as err:
			print('got httplib.BadStatusLine error: {err}'.format(err=str(err)))
			result = dict()
		except socket.error as err:
			print('got socket.error error: {err}'.format(err=str(err)))
			result = dict()
		print('<- {res}'.format(res=result))
		return result

	def umc(self, path, data):
		print('-> invoke {path} with options {data}'.format(path=path, data=data))
		if oldlib:
			return self.umc_old(path, data)
		if self.client is None:
			self.client = univention.lib.umc.Client(username=self.options.username, password=self.options.password)
		print('-> headers: {headers}'.format(headers=self.client._headers))
		resp = self.client.umc_command(path, data)
		assert resp.status == 200
		result = resp.result
		print('<- {res}'.format(res=result))
		return result

	def wait(self, result):
		pid = result['id'] if self.docker else None
		path = 'appcenter/docker/progress' if self.docker else 'appcenter/progress'
		data = dict(progress_id=pid) if pid else None
		waited = 0
		while waited <= 720:
			time.sleep(10)
			waited += 1
			try:
				result = self.umc(path, data)
			except univention.lib.umc.ConnectionError:
				print('... Apache down? Ignoring...')
				continue
			for message in result.get('intermediate', []):
				print('   {msg}'.format(msg=message.get('message')))
			if result.get('finished', False):
				break
		else:
			raise Exception("wait timeout")
		print(result)
		if self.docker:
			assert result['result']['success'] is True
			assert result['result']['can_continue'] is True
		else:
			assert not result['errors']

	def install(self):
		path = 'appcenter/docker/invoke' if self.docker else 'appcenter/invoke'
		values = get_default_values(self.options.app)
		data = dict(function='install', application=self.options.app, app=self.options.app, force=True, values=values)
		self.umc('appcenter/keep_alive', None)
		resp = self.umc(path, data)
		self.wait(resp)

	def uninstall(self):
		path = 'appcenter/docker/invoke' if self.docker else 'appcenter/invoke'
		values = get_default_values(self.options.app)
		data = dict(function='uninstall', application=self.options.app, app=self.options.app, force=True, values=values)
		self.umc('appcenter/keep_alive', None)
		resp = self.umc(path, data)
		self.wait(resp)

	def update(self):
		path = 'appcenter/get'
		data = dict(application=self.options.app)
		result = self.umc(path, data)
		update_available = False
		for host in result.get('installations'):
			if host == self.ucr['hostname']:
				print('-> installations: {inst}'.format(inst=result['installations']))
				if result['installations'][host]['update_available']:
					update_available = True
					break
		assert update_available is True
		path = 'appcenter/docker/invoke' if self.docker else 'appcenter/invoke'
		values = get_default_values(self.options.app)
		data = dict(function='update', application=self.options.app, app=self.options.app, force=True, values=values)
		self.umc('appcenter/keep_alive', None)
		resp = self.umc(path, data)
		self.wait(resp)

	def main(self):
		data = dict(application=self.options.app)
		path = 'appcenter/get'
		result = self.umc(path, data)
		self.docker = True if result.get('docker_image') else False
		path = 'appcenter/docker/invoke' if self.docker else 'appcenter/invoke'
		if self.options.remove:
			self.uninstall()
		elif self.options.update:
			self.update()
		else:
			self.install()


if __name__ == '__main__':
	apps = Apps()
	apps.main()
