#!/usr/bin/env python

__author__ = "Steffen Vogel"
__copyright__ = "Copyright 2018, Steffen Vogel"
__credits__ = ["Steffen Vogel"]
__license__ = "GPL-3.0"
__version__ = "0.1.0"
__maintainer__ = "Steffen Vogel"
__email__ = "post@steffenvogel.de"
__status__ = "Production"

import sys
import os.path
import logging
import yaml
import json
import time
import datetime
import urllib
import hashlib
import fints.client
import http.client
import paho.mqtt.publish as publish

logger = logging.getLogger('pushfin')

def hash_trx(trx):
	m = hashlib.sha1()

	m.update(trx['purpose'].encode('utf-8'))
	m.update(trx['date_fmt'].encode('utf-8'))
	m.update(trx['entry_date_fmt'].encode('utf-8'))
	m.update(str(trx['amount']).encode('utf-8'))
	m.update(trx['currency'].encode('utf-8'))
	m.update(trx['applicant_name'].encode('utf-8'))

	return m.hexdigest()

def get_telegram_chat_id(config):
	# Get chat id
	conn = http.client.HTTPSConnection("api.telegram.org:443")
	conn.request("POST", "/bot%s/getUpdates" % config['token'])

	resp = conn.getresponse()
	body = resp.read().decode("utf-8")
	updates = json.loads(body)

	if updates['ok']:
		for result in updates['result']:
			chat = result['message']['chat']
			if chat['username'] == config['username']:
				return chat['id']


def get_transactions(config, start, end):
	logger.info('Connect to: %s', config['server'])
	f = fints.client.FinTS3PinTanClient(config['blz'], config['login'], config['pin'], config['server'])

	accounts = f.get_sepa_accounts()

	# Find correct account
	account = [a for a in accounts if a.iban == config['iban']][0]
	logger.info('Found account: %s', account)

	statements = f.get_statement(account, start, end)
	logger.info('Found %d statements from %s to %s', len(statements), start, end)

	trxs = [t.data for t in statements]
	balance = f.get_balance(account)
	logger.info("Current Balance: {} {} {}".format(balance.date, balance.amount.amount, balance.amount.currency))

	return trxs, balance, account

def send_pushover(config, message, timestamp):
	conn = http.client.HTTPSConnection("api.pushover.net:443")
	conn.request("POST", "/1/messages.json",
		urllib.parse.urlencode({
			**config,
			"message": message,
			"timestamp": int(timestamp)
		}),
		{
			"Content-type": "application/x-www-form-urlencoded"
		}
	)
	conn.getresponse()

	logger.debug('Send via Pushover: %s', message)

def send_mqtt(config, data):
	publish.single(
		**config,
		payload = json.dumps(data, skipkeys = True)
	)

	logger.debug('Send via MQTT: %s', data)

def send_telegram(config, state, message):
	# Get chat id
	if 'telegram' not in state:
		state['telegram'] = {
			'chat_id' : get_telegram_chat_id(config)
		}

	# Send message
	conn = http.client.HTTPSConnection("api.telegram.org:443")
	conn.request("POST", "/bot%s/sendMessage" % config['token'],
		urllib.parse.urlencode({
			"chat_id" : state['telegram']['chat_id'],
			"text" : message,
			"parse_mode" : "html"
		}),
		{
			"Content-type": "application/x-www-form-urlencoded"
		}
	)
	resp = conn.getresponse()

	logger.debug('Sent via Telegram Bot: %s', message)


if __name__ == "__main__":
	logging.basicConfig(level = logging.INFO)

	fints_logger = logging.getLogger('fints')
	fints_logger.setLevel(logging.WARN)

	logger.setLevel(logging.DEBUG)

	try:
		with open(os.path.expanduser("~/.pushfin.yaml")) as f:
			config = yaml.load(f)
	except IOError:
		logger.error("Failed to open configuration file: ~/.pushfin.yaml")
		sys.exit(-1)

	try:
		with open(os.path.expanduser("~/.pushfin.state.yaml")) as f:
			state = yaml.load(f)
	except:
		last = datetime.date.today() - datetime.timedelta(weeks = 1)

		state = {
			'hashes' : { },
			'last' : last.toordinal()
		}

	start = datetime.date.fromordinal(state['last']-1)
	end = datetime.date.today()

	trxs, balance, account = get_transactions(config['fints'], start, end)

	logger.debug("Current state: %s", state)

	for trx in trxs:
		tx = dict(trx)

		if 'entry_date' in trx:
			tx['entry_date_fmt'] = trx['entry_date'].strftime(config['format_date'])
			tx['entry_date'] = time.mktime(trx['entry_date'].timetuple())
		if 'date' in trx:
			tx['date_fmt'] = trx['date'].strftime(config['format_date'])
			tx['date'] = time.mktime(trx['date'].timetuple())
		if 'amount' in trx:
			tx['amount'] = float(trx['amount'].amount)

		for tpl in config['templates']:
			field = list(tpl.keys())[0]

			if field in trx:
				value = trx[field]
				if value in tpl[field]:
					for entry in tpl[field][value]:
						tx = {**entry, **tx}

		data = {
			'trx' : tx,
			'bal' : {
				'date_fmt' : balance.date.strftime(config['format_date']),
				'date' : time.mktime(balance.date.timetuple()),
				'amount' : float(balance.amount.amount),
				'currency' : balance.amount.currency
			}
		}

		hash = hash_trx(tx)
		if hash in state['hashes']:
			logger.info("Skipping old transaction: %s", hash)
			continue
		else:
			logger.info("Processing transaction: %s", tx)

		if 'mqtt' in config:
			send_mqtt(config['mqtt'], data)

		if 'telegram' in config:
			fmt = config['telegram']['format'] if 'format' in config['telegram'] else config['format']
			msg = fmt.format(**data)
			send_telegram(config['telegram'], state, msg)

		if 'pushover' in config:
			fmt = config['pushover']['format'] if 'format' in config['pushover'] else config['format']
			msg = fmt.format(**data)
			send_pushover(config['pushover'], msg, tx['date'])

		# Remmeber that we already send this transaction
		state['hashes'][hash] = trx['date'].toordinal()

	# Save date of last transaction to config
	new_state = {
		'last' : end.toordinal(),
		'hashes' : { }
	}

	# Remove hashes older than two weeks
	for hash, date in state['hashes'].items():
		if datetime.date.fromordinal(date+14) >= datetime.date.today():
			new_state['hashes'][hash] = date

	with open(os.path.expanduser("~/.pushfin.state.yaml"), mode='w') as f:
		yml = yaml.dump(new_state, f)
