from __future__ import unicode_literals, absolute_import
import click
import hashlib, os, sys
import frappe
from frappe.commands import pass_context, get_site
from frappe.commands.scheduler import _is_scheduler_enabled
from frappe.limits import update_limits, get_limits
from frappe.installer import update_site_config
from frappe.utils import touch_file, get_site_path

@click.command('new-site')
@click.argument('site')
@click.option('--db-name', help='Database name')
@click.option('--mariadb-root-username', default='root', help='Root username for MariaDB')
@click.option('--mariadb-root-password', help='Root password for MariaDB')
@click.option('--admin-password', help='Administrator password for new site', default=None)
@click.option('--verbose', is_flag=True, default=False, help='Verbose')
@click.option('--force', help='Force restore if site/database already exists', is_flag=True, default=False)
@click.option('--source_sql', help='Initiate database with a SQL file')
@click.option('--install-app', multiple=True, help='Install app after installation')
def new_site(site, mariadb_root_username=None, mariadb_root_password=None, admin_password=None, verbose=False, install_apps=None, source_sql=None, force=None, install_app=None, db_name=None):
	"Create a new site"
	if not db_name:
		db_name = hashlib.sha1(site).hexdigest()[:10]

	frappe.init(site=site, new_site=True)

	_new_site(db_name, site, mariadb_root_username=mariadb_root_username, mariadb_root_password=mariadb_root_password, admin_password=admin_password,
			verbose=verbose, install_apps=install_app, source_sql=source_sql, force=force)

	if len(frappe.utils.get_sites()) == 1:
		use(site)

def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=None, admin_password=None,
	verbose=False, install_apps=None, source_sql=None,force=False, reinstall=False):
	"""Install a new Frappe site"""

	from frappe.installer import install_db, make_site_dirs
	from frappe.installer import install_app as _install_app
	import frappe.utils.scheduler

	frappe.init(site=site)

	try:
		# enable scheduler post install?
		enable_scheduler = _is_scheduler_enabled()
	except:
		enable_scheduler = False

	make_site_dirs()

	try:
		installing = touch_file(get_site_path('locks', 'installing.lock'))

		install_db(root_login=mariadb_root_username, root_password=mariadb_root_password, db_name=db_name,
			admin_password=admin_password, verbose=verbose, source_sql=source_sql,force=force, reinstall=reinstall)

		apps_to_install = ['frappe'] + (frappe.conf.get("install_apps") or []) + (list(install_apps) or [])
		for app in apps_to_install:
			_install_app(app, verbose=verbose, set_as_patched=not source_sql)

		frappe.utils.scheduler.toggle_scheduler(enable_scheduler)
		frappe.db.commit()

		scheduler_status = "disabled" if frappe.utils.scheduler.is_scheduler_disabled() else "enabled"
		print "*** Scheduler is", scheduler_status, "***"

	finally:
		os.remove(installing)
		frappe.destroy()

@click.command('restore')
@click.argument('sql-file-path')
@click.option('--mariadb-root-username', default='root', help='Root username for MariaDB')
@click.option('--mariadb-root-password', help='Root password for MariaDB')
@click.option('--db-name', help='Database name for site in case it is a new one')
@click.option('--admin-password', help='Administrator password for new site')
@click.option('--install-app', multiple=True, help='Install app after installation')
@click.option('--with-public-files', help='Restores the public files of the site, given path to its tar file')
@click.option('--with-private-files', help='Restores the private files of the site, given path to its tar file')
@pass_context
def restore(context, sql_file_path, mariadb_root_username=None, mariadb_root_password=None, db_name=None, verbose=None, install_app=None, admin_password=None, force=None, with_public_files=None, with_private_files=None):
	"Restore site database from an sql file"
	from frappe.installer import extract_sql_gzip, extract_tar_files
	# Extract the gzip file if user has passed *.sql.gz file instead of *.sql file

	if not os.path.exists(sql_file_path):
		sql_file_path = '../' + sql_file_path
		if not os.path.exists(sql_file_path):
			print 'Invalid path {0}' + sql_file_path[3:]
			sys.exit(1)

	if sql_file_path.endswith('sql.gz'):
		sql_file_path = extract_sql_gzip(os.path.abspath(sql_file_path))

	site = get_site(context)
	frappe.init(site=site)
	db_name = db_name or frappe.conf.db_name or hashlib.sha1(site).hexdigest()[:10]
	_new_site(db_name, site, mariadb_root_username=mariadb_root_username, mariadb_root_password=mariadb_root_password, admin_password=admin_password, verbose=context.verbose, install_apps=install_app, source_sql=sql_file_path, force=context.force)

	# Extract public and/or private files to the restored site, if user has given the path
	if with_public_files:
		public = extract_tar_files(site, with_public_files, 'public')
		os.remove(public)

	if with_private_files:
		private = extract_tar_files(site, with_private_files, 'private')
		os.remove(private)

@click.command('reinstall')
@click.option('--yes', is_flag=True, default=False, help='Pass --yes to skip confirmation')
@pass_context
def reinstall(context, yes=False):
	"Reinstall site ie. wipe all data and start over"

	if not yes:
		click.confirm('This will wipe your database. Are you sure you want to reinstall?', abort=True)

	site = get_site(context)
	try:
		frappe.init(site=site)
		frappe.connect()
		frappe.clear_cache()
		installed = frappe.get_installed_apps()
		frappe.clear_cache()
	except Exception:
		installed = []
	finally:
		if frappe.db:
			frappe.db.close()
		frappe.destroy()

	frappe.init(site=site)
	_new_site(frappe.conf.db_name, site, verbose=context.verbose, force=True, reinstall=True, install_apps=installed)

@click.command('install-app')
@click.argument('app')
@pass_context
def install_app(context, app):
	"Install a new app to site"
	from frappe.installer import install_app as _install_app
	for site in context.sites:
		frappe.init(site=site)
		frappe.connect()
		try:
			_install_app(app, verbose=context.verbose)
		finally:
			frappe.destroy()

@click.command('list-apps')
@pass_context
def list_apps(context):
	"List apps in site"
	site = get_site(context)
	frappe.init(site=site)
	frappe.connect()
	print "\n".join(frappe.get_installed_apps())
	frappe.destroy()

@click.command('add-system-manager')
@click.argument('email')
@click.option('--first-name')
@click.option('--last-name')
@click.option('--send-welcome-email', default=False, is_flag=True)
@pass_context
def add_system_manager(context, email, first_name, last_name, send_welcome_email):
	"Add a new system manager to a site"
	import frappe.utils.user
	for site in context.sites:
		frappe.connect(site=site)
		try:
			frappe.utils.user.add_system_manager(email, first_name, last_name, send_welcome_email)
			frappe.db.commit()
		finally:
			frappe.destroy()


@click.command('disable-user')
@click.argument('email')
@pass_context
def disable_user(context, email):
	site = get_site(context)
	with frappe.init_site(site):
		frappe.connect()
		user = frappe.get_doc("User", email)
		user.enabled = 0
		user.save(ignore_permissions=True)
		frappe.db.commit()


@click.command('migrate')
@click.option('--rebuild-website', help="Rebuild webpages after migration")
@pass_context
def migrate(context, rebuild_website=False):
	"Run patches, sync schema and rebuild files/translations"
	from frappe.migrate import migrate

	for site in context.sites:
		print 'Migrating', site
		frappe.init(site=site)
		frappe.connect()
		try:
			migrate(context.verbose, rebuild_website=rebuild_website)
		finally:
			frappe.destroy()

@click.command('run-patch')
@click.argument('module')
@pass_context
def run_patch(context, module):
	"Run a particular patch"
	import frappe.modules.patch_handler
	for site in context.sites:
		frappe.init(site=site)
		try:
			frappe.connect()
			frappe.modules.patch_handler.run_single(module, force=context.force)
		finally:
			frappe.destroy()

@click.command('reload-doc')
@click.argument('module')
@click.argument('doctype')
@click.argument('docname')
@pass_context
def reload_doc(context, module, doctype, docname):
	"Reload schema for a DocType"
	for site in context.sites:
		try:
			frappe.init(site=site)
			frappe.connect()
			frappe.reload_doc(module, doctype, docname, force=context.force)
			frappe.db.commit()
		finally:
			frappe.destroy()


@click.command('use')
@click.argument('site')
def _use(site, sites_path='.'):
	"Set a default site"
	use(site, sites_path=sites_path)

def use(site, sites_path='.'):
	with open(os.path.join(sites_path,  "currentsite.txt"), "w") as sitefile:
		sitefile.write(site)

@click.command('backup')
@click.option('--with-files', default=False, is_flag=True, help="Take backup with files")
@pass_context
def backup(context, with_files=False, backup_path_db=None, backup_path_files=None,
	backup_path_private_files=None, quiet=False):
	"Backup"
	from frappe.utils.backups import scheduled_backup
	verbose = context.verbose
	for site in context.sites:
		frappe.init(site=site)
		frappe.connect()
		odb = scheduled_backup(ignore_files=not with_files, backup_path_db=backup_path_db, backup_path_files=backup_path_files, backup_path_private_files=backup_path_private_files, force=True)
		if verbose:
			from frappe.utils import now
			print "database backup taken -", odb.backup_path_db, "- on", now()
			if with_files:
				print "files backup taken -", odb.backup_path_files, "- on", now()
				print "private files backup taken -", odb.backup_path_private_files, "- on", now()

		frappe.destroy()

@click.command('remove-from-installed-apps')
@click.argument('app')
@pass_context
def remove_from_installed_apps(context, app):
	"Remove app from site's installed-apps list"
	from frappe.installer import remove_from_installed_apps
	for site in context.sites:
		try:
			frappe.init(site=site)
			frappe.connect()
			remove_from_installed_apps(app)
		finally:
			frappe.destroy()

@click.command('uninstall-app')
@click.argument('app')
@click.option('--dry-run', help='List all doctypes that will be deleted', is_flag=True, default=False)
@pass_context
def uninstall(context, app, dry_run=False):
	"Remove app and linked modules from site"
	from frappe.installer import remove_app
	for site in context.sites:
		try:
			frappe.init(site=site)
			frappe.connect()
			remove_app(app, dry_run)
		finally:
			frappe.destroy()


@click.command('drop-site')
@click.argument('site')
@click.option('--root-login', default='root')
@click.option('--root-password')
@click.option('--archived-sites-path')
def drop_site(site, root_login='root', root_password=None, archived_sites_path=None):
	"Remove site from database and filesystem"
	from frappe.installer import get_current_host, make_connection
	from frappe.model.db_schema import DbManager
	from frappe.utils.backups import scheduled_backup

	frappe.init(site=site)
	frappe.connect()
	scheduled_backup(ignore_files=False, force=True)

	db_name = frappe.local.conf.db_name
	frappe.local.db = make_connection(root_login, root_password)
	dbman = DbManager(frappe.local.db)
	dbman.delete_user(db_name, get_current_host())
	dbman.drop_database(db_name)

	if not archived_sites_path:
		archived_sites_path = os.path.join(frappe.get_app_path('frappe'), '..', '..', '..', 'archived_sites')

	if not os.path.exists(archived_sites_path):
		os.mkdir(archived_sites_path)

	move(archived_sites_path, site)

def move(dest_dir, site):
	import os
	if not os.path.isdir(dest_dir):
		raise Exception, "destination is not a directory or does not exist"

	frappe.init(site)
	old_path = frappe.utils.get_site_path()
	new_path = os.path.join(dest_dir, site)

	# check if site dump of same name already exists
	site_dump_exists = True
	count = 0
	while site_dump_exists:
		final_new_path = new_path + (count and str(count) or "")
		site_dump_exists = os.path.exists(final_new_path)
		count = int(count or 0) + 1

	os.rename(old_path, final_new_path)
	frappe.destroy()
	return final_new_path


@click.command('set-admin-password')
@click.argument('admin-password')
@pass_context
def set_admin_password(context, admin_password):
	"Set Administrator password for a site"
	import getpass
	from frappe.utils.password import update_password

	for site in context.sites:
		try:
			frappe.init(site=site)

			while not admin_password:
				admin_password = getpass.getpass("Administrator's password for {0}: ".format(site))

			frappe.connect()
			update_password('Administrator', admin_password)
			frappe.db.commit()
			admin_password = None
		finally:
			frappe.destroy()

@click.command('set-limit')
@click.option('--site', help='site name')
@click.argument('limit')
@click.argument('value')
@pass_context
def set_limit(context, site, limit, value):
	"""Sets user / space / email limit for a site"""
	_set_limits(context, site, ((limit, value),))

@click.command('set-limits')
@click.option('--site', help='site name')
@click.option('--limit', 'limits', type=(unicode, unicode), multiple=True)
@pass_context
def set_limits(context, site, limits):
	_set_limits(context, site, limits)

def _set_limits(context, site, limits):
	import datetime

	if not limits:
		return

	if not site:
		site = get_site(context)

	with frappe.init_site(site):
		new_limits = {}
		for limit, value in limits:
			if limit not in ('emails', 'space', 'users', 'email_group',
				'expiry', 'support_email', 'support_chat', 'upgrade_url'):
				frappe.throw('Invalid limit {0}'.format(limit))

			if limit=='expiry':
				try:
					datetime.datetime.strptime(value, '%Y-%m-%d')
				except ValueError:
					raise ValueError("Incorrect data format, should be YYYY-MM-DD")

			elif limit=='space':
				value = float(value)

			elif limit in ('users', 'emails', 'email_group'):
				value = int(value)

			new_limits[limit] = value

		update_limits(new_limits)

@click.command('clear-limits')
@click.option('--site', help='site name')
@click.argument('limits', nargs=-1, type=click.Choice(['emails', 'space', 'users', 'email_group',
	'expiry', 'support_email', 'support_chat', 'upgrade_url']))
@pass_context
def clear_limits(context, site, limits):
	"""Clears given limit from the site config, and removes limit from site config if its empty"""
	from frappe.limits import clear_limit as _clear_limit
	if not limits:
		return

	if not site:
		site = get_site(context)

	with frappe.init_site(site):
		_clear_limit(limits)

		# Remove limits from the site_config, if it's empty
		limits = get_limits()
		if not limits:
			update_site_config('limits', 'None', validate=False)


commands = [
	add_system_manager,
	backup,
	drop_site,
	install_app,
	list_apps,
	migrate,
	new_site,
	reinstall,
	reload_doc,
	remove_from_installed_apps,
	restore,
	run_patch,
	set_admin_password,
	uninstall,
	set_limit,
	set_limits,
	clear_limits,
	disable_user,
	_use,
]
