#!/usr/bin/python

import datetime
import ipaddress
import logging
import stat
import tornado.web

from . import base

# Setup logging
log = logging.getLogger("pbs.web.mirrors")

class IndexHandler(base.BaseHandler):
	async def get(self):
		await self.render("mirrors/index.html", mirrors=self.backend.mirrors)


class ShowHandler(base.BaseHandler):
	async def get(self, hostname):
		mirror = await self.backend.mirrors.get_by_hostname(hostname)
		if not mirror:
			raise tornado.web.HTTPError(404, "Could not find mirror %s" % hostname)

		await self.render("mirrors/show.html", mirror=mirror)


class CheckHandler(base.BaseHandler):
	@base.authenticated
	async def post(self, hostname):
		mirror = await self.backend.mirrors.get_by_hostname(hostname)
		if not mirror:
			raise tornado.web.HTTPError(404, "Could not find mirror %s" % hostname)

		# Check permissions
		if not mirror.has_perm(self.current_user):
			raise tornado.web.HTTPError(403, "%s has no permission for %s" \
				% (self.current_user, mirror))

		# Run the check
		await mirror.check(force=True)

		# Redirect back to the mirror
		self.redirect("/mirrors/%s" % mirror.hostname)


class CreateHandler(base.AdminHandler):
	@base.authenticated
	async def get(self):
		await self.render("mirrors/edit.html", mirror=None)

	@base.authenticated
	async def post(self):
		# Create the mirror
		mirror = await self.backend.mirrors.create(
			hostname = self.get_argument("hostname"),
			path     = self.get_argument("path"),
			owner    = self.get_argument("owner"),
			contact  = self.get_argument("contact"),
			notes    = self.get_argument("notes", ""),
			user     = self.current_user,
		)

		# Redirect the user back
		self.redirect("/mirrors/%s" % mirror.hostname)


class EditHandler(base.BaseHandler):
	@base.authenticated
	async def get(self, hostname):
		mirror = await self.backend.mirrors.get_by_hostname(hostname)
		if not mirror:
			raise tornado.web.HTTPError(404, "Could not find mirror %s" % hostname)

		# Check permissions
		if not mirror.has_perm(self.current_user):
			raise tornado.web.HTTPError(403)

		await self.render("mirrors/edit.html", mirror=mirror)

	@base.authenticated
	async def post(self, hostname):
		mirror = await self.backend.mirrors.get_by_hostname(hostname)
		if not mirror:
			raise tornado.web.HTTPError(404, "Could not find mirror %s" % hostname)

		# Check permissions
		if not mirror.has_perm(self.current_user):
			raise tornado.web.HTTPError(403)

		# Store new values
		mirror.owner          = self.get_argument("owner")
		mirror.contact        = self.get_argument("contact")
		mirror.notes          = self.get_argument("notes", None)

		self.redirect("/mirrors/%s" % mirror.hostname)


class DeleteHandler(base.BaseHandler):
	@base.authenticated
	async def get(self, hostname):
		mirror = await self.backend.mirrors.get_by_hostname(hostname)
		if not mirror:
			raise tornado.web.HTTPError(404, "Could not find mirror %s" % hostname)

		# Check permissions
		if not mirror.has_perm(self.current_user):
			raise tornado.web.HTTPError(403)

		await self.render("mirrors/delete.html", mirror=mirror)

	@base.authenticated
	async def post(self, hostname):
		mirror = await self.backend.mirrors.get_by_hostname(hostname)
		if not mirror:
			raise tornado.web.HTTPError(404, "Could not find mirror %s" % hostname)

		# Check permissions
		if not mirror.has_perm(self.current_user):
			raise tornado.web.HTTPError(403)

		# Delete the mirror
		await mirror.delete(deleted_by=self.current_user)

		# Redirect back to all mirrors
		self.redirect("/mirrors")


class DownloadsHandler(base.BaseHandler):
	"""
		A universal download redirector which does not need to keep any state.

		It will check if the mirror serves the file and redirect the client. We are
		starting with the closest mirror and walk through all of the until we have
		found the right file.

		As a last resort, we will try to serve the file locally.
	"""
	def prepare(self):
		# Don't send any Content-Type header
		self.clear_header("Content-Type")

	@base.ratelimit(limit=100, minutes=60, key="downloads")
	async def get(self, path):
		# Check if the file exists
		if not await self.backend.stat(path, stat.S_IFREG):
			raise tornado.web.HTTPError(404)

		# Tell the clients to never cache the redirect
		self.set_header("Cache-Control", "no-store")

		# Fetch all mirrors for this client
		mirrors = await self.backend.mirrors.get_mirrors_for_address(self.current_address)

		# Walk through all mirrors
		for mirror in mirrors:
			# Don't send clients to a mirror they don't support
			if isinstance(self.current_address, ipaddress.IPv6Address):
				if not mirror.supports_ipv6():
					continue
			elif isinstance(self.current_address, ipaddress.IPv4Address):
				if not mirror.supports_ipv4():
					continue

			# Skip the mirror if it does not serve the file we are looking for
			if not await mirror.serves_file(path):
				continue

			# Log action
			log.info("Sending %s to download %s from %s", self.current_address, path, mirror)

			# Make the URL
			url = mirror.make_url(path)

			# Redirect the user
			return self.redirect(url)

		# If we go here, we did not find any working mirror.
		# We will send the user to our local file store and hopefully the right
		# file will be there. If not, the client will receive 404 from there.
		url = self.backend.path_to_url(path, mirrored=False)

		self.redirect(url)

	@base.ratelimit(limit=100, minutes=60, key="downloads")
	async def head(self, path):
		# Stat the file
		s = await self.backend.stat(path, stat.S_IFREG)

		# Send 404 if the file does not exist
		if not s:
			raise tornado.web.HTTPError(404)

		# Fetch the MIME type
		mimetype = await self.backend.mimetype(path)

		# Send a couple of headers
		self.set_header("Content-Type", mimetype or "application/octet-stream")
		self.set_header("Content-Length", s.st_size)
		self.set_header("Last-Modified", datetime.datetime.fromtimestamp(s.st_mtime))
		self.set_header("Etag", "%x-%x" % (int(s.st_mtime), s.st_size))

	async def write_error(self, *args, **kwargs):
		"""
			Don't send any body in error responses
		"""
		pass
