#!/usr/bin/python

import subprocess
import os
import logging
import re
import gpgme
import base64
import tempfile
import json
from getpass import getpass
from io      import BytesIO
from lxml    import etree


FILE_SUFFIX = '.json.asc'


class Account(object):

	@classmethod
	def decipher(self, filename):
		logging.debug("About to decipher %s:", filename)
		password = getpass("Enter passphrase: ")
		cmd = [
			'gpg',
			'--decrypt',
			'--batch',
			'--passphrase-fd', '0',
			filename
		]
		logging.debug("Running command %s", cmd)
		p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
		stdout, stderr = p.communicate(password)
		if p.returncode != 0:
			logging.warning("Error from GPG")
			for line in stderr.split('\n'):
				logging.warning("STDERR: %s", line)
			return None
		account_data = json.loads(stdout)
		return Account(account_data)

	def __init__(self, account_data):
		self._account_data = account_data

	def __getitem__(self, key):
		return self.get_account_data()[key]

	def __setitem__(self, key, value):
		self._account_data[key] = value

	def get_account_data(self):
		result = {}
		result.update(self._account_data)
		return result

	def encipher(self, key):
		ctx        = gpgme.Context()
		ctx.armor  = True
		target_key = ctx.get_key(key)
		json_data  = json.dumps(self.get_account_data())
		plaintext  = BytesIO(json_data)
		ciphertext = BytesIO()
		ctx.encrypt([target_key], gpgme.ENCRYPT_ALWAYS_TRUST, plaintext, ciphertext)
		enciphered = ciphertext.getvalue()
		logging.debug("Enciphered data:")
		logging.debug(enciphered)
		return enciphered


class PasswordManager(object):

	def __init__(self, enc_dir):
		self._enc_dir = enc_dir
		if not os.path.isdir(self._enc_dir):
			logging.warning("Password Manager directory does not exist: %s", self._enc_dir)
			raise Exception('PASSWORD_MANAGER_DIR_NOT_FOUND')

	def _get_file_path(self, filename):
		return os.path.join(self._enc_dir, filename)

	def get_account_by_file(self, filename):
		logging.info("File:     %s", filename[0:0-len(FILE_SUFFIX)])
		file_path = self._get_file_path(filename)
		account = Account.decipher(file_path)
		return account

	def find_account(self, substring):
		for filename in sorted(os.listdir(self._enc_dir)):
			logging.debug("Checking %s", filename)
			if substring.upper() in filename.upper():
				return self.get_account_by_file(filename)
		return None

	def save_account(self, filename, account, key):
		file_path = self._get_file_path(filename+FILE_SUFFIX)
		if os.path.exists(file_path):
			logging.warning("File already exists in specified location: %s", file_path)
			raise Exception("ACCOUNT_FILE_EXISTS")
		else:
			enc_data = account.encipher(key=key)
			f = open(file_path, 'w')
			f.write(enc_data)
			f.truncate()
			f.close()

def run():

	import optparse
	from Dairylogic import Logging

	Logging.initLogger(logLevel='INFO')

	home_dir = os.environ.get('HOME', None)
	if home_dir == None:
		default_password_file = '/etc/password_manager/passwords.xml'
		default_password_dir  = '/etc/password_manager/'
	else:
		default_password_file = os.path.join(home_dir, '.password_manager/passwords.xml')
		default_password_dir  = os.path.join(home_dir, '.password_manager/')

	usage = "usage: %prog [options] <password fragment>"
	parser = optparse.OptionParser(usage)

	parser.add_option(
		'-f', '--password-file',
		dest    = 'password_file',
		default = default_password_file,
		help    = 'default: %default',
	)

	parser.add_option(
		'-d', '--password-dir',
		dest    = 'password_dir',
		default = default_password_dir,
		help    = 'default: %default',
	)

	parser.add_option(
		'-s', '--set',
		dest    = 'set_account',
		action  = "store_true",
		help    = 'set account',
	)

	parser.add_option(
		'-m', '--migrate',
		dest    = 'migrate',
		action  = "store_true",
		help    = 'migrate to new format',
	)

	parser.add_option(
		'-k', '--key',
		dest    = 'key',
		help    = 'default: %default',
	)

	(options, args) = parser.parse_args()

	if len(args) < 1:
		parser.error('Password fragment is required')

	password_fragment = args[0]

	mgr = PasswordManager(options.password_dir)

	if options.set_account:
		result = getpass("Enter password for %s: " % (password_fragment,))
		account = Account({'password':result})
		mgr.save_account(password_fragment, account, options.key)
	else:
		account = mgr.find_account(password_fragment)
		if account == None:
			logging.warning("Password could not be opened")
		else:
			logging.info("Password: %s", account['password'])

if __name__ == '__main__':
	run()

