"""
A helper for purx's cron: updating the resources.

This stuff is being run about once a day.
It needs enrollment.py next to it.
"""

import datetime
import os
import time

import requests

from gavo import api
from gavo import utils
from gavo.base import osinter

enrollment, _ = utils.loadPythonModule(
	"/".join(__file__.split("/")[:-1]+["enrollment"]))

StateMachine = enrollment.StateMachine


def handleHTTPError(httpError, row):
	"""modifies and saves row after an http error happened.

	403 means: take me off.  Other errors basically trigger a state
	machine.
	"""


# TODO: Use this

_INVALIDMAIL_TEMPLATE = """From: purx admin <{admin_addr}>
To: {contact_email}
Subject: Bad XML in record {ivoid}

Hello,

You are publishing the VOResource record at

{source_url}

through purx (see http://dc.g-vo.org/PURX for more information).

On a harvest I just performed on the record, what I got back was
non well-formed or invalid XML.

Please fix the following problem(s):

{errmsg}

If you just want to get rid of the publication, please arrange for a
403 Forbidden to be returned from this URL and purx will forget your
resource.

Thanks.
"""


def _handleUpdateErrors(ex, row):
	"""performs actions for handling an exception ex while updating row.

	In general, HTTP errors lead to transitions in a state machine;
	validation errors we consider to be under the immediate control of
	the contact person, and we complain immediately for now.
	"""
	if (isinstance(ex, requests.exceptions.HTTPError) and
		ex.response.status_code==403):
			StateMachine.process(row, "forbidden")
	else:
		# for now, we consider all other errors as equivalent and,
		# every 300 ks, move the state machine ahead
		if (datetime.datetime.utcnow()-row["rectimestamp"]).total_seconds()>3e5:
			StateMachine.process(row, "error", payload=ex)
	

def updateSource(row):
	"""updates the record in row if necessary and returns the updated
	record if so.

	This will raise an enrollment.NotModified exception if no changes
	need to be dealt with.
	"""
	try:
		tree, lastModified = enrollment.getRegistryRecord(
			row["source_url"],
			lastModified=row["modification_date"])
		enrollment.ensureValidVOResource(tree)

	except enrollment.NotModified:
		# this is for when the service had a temporary failure before.
		StateMachine.process(row, 'not-modified')
		raise

	except Exception as ex:
		_handleUpdateErrors(ex, row)

	else:
		newUpstreamUpdate = api.parseISODT(tree.get("updated"))
		if newUpstreamUpdate<=row["upstream_update"]:
			raise enrollment.NotModified(
				"@updated on record says it's the same thing")
	
		row["upstream_update"] = newUpstreamUpdate
		row["modification_date"] = lastModified
		row["xml_source"] = enrollment.serializeTree(
			enrollment.fixIdentifier(tree, row["ivoid"])).decode("utf-8")
		row["title"] = tree.xpath("title")[0].text
		try:
			row["contact_email"] = enrollment.getContactEmail(tree)
		except api.ValidationError:
			# someone has botched the contact address.  Let's keep what we have.
			pass

		StateMachine.process(row, "success")

		return row


def expireStalePENDING(expiryPeriod):
	"""removes all PENDING records older than expiryPeriod.
	"""
	ageLimit = datetime.datetime.utcnow(
		)-datetime.timedelta(seconds=expiryPeriod)

	with api.getWritableAdminConn() as conn:
		conn.execute("DELETE FROM purx.sources WHERE"
			" status='PENDING'"
			" and rectimestamp<%(ageLimit)s", locals())


def updateAll():
	"""tries to update all records in purx.sources.

	This is what's being run every 20 hours from a cron job in purx/q; there's
	no harm in running it out of order now and then; nothing should be
	conditioned on the frequency at which this is run.

	This will also remove requests that have been pending for more than 150 ks.
	"""
	with api.getTableConn() as conn:
		for row in conn.queryToDicts("SELECT * FROM purx.sources"
				" WHERE status!='DELETED'"):
			try:
				updateSource(row)
			except enrollment.NotModified:
				pass
			except Exception as msg:
				api.ui.notifyError("Unhandled exception while updating purx"
					" resource %s: %s"%(row["ivoid"], msg))

	expireStalePENDING(1.5e5)


if __name__=="__main__":
	api.LoggingUI(api.ui)
	updateAll()
