# Copyright (c) 2015, Xhive Global Ltd. and Contributors
# License: MIT. See LICENSE
import base64
import binascii
import json
from urllib.parse import urlencode, urlparse

import xhiveframework
import xhiveframework.client
import xhiveframework.handler
from xhiveframework import _
from xhiveframework.utils.data import sbool
from xhiveframework.utils.response import build_response


def handle():
	"""
	Handler for `/api` methods

	### Examples:

	`/api/method/{methodname}` will call a whitelisted method

	`/api/resource/{doctype}` will query a table
	        examples:
	        - `?fields=["name", "owner"]`
	        - `?filters=[["Task", "name", "like", "%005"]]`
	        - `?limit_start=0`
	        - `?limit_page_length=20`

	`/api/resource/{doctype}/{name}` will point to a resource
	        `GET` will return doclist
	        `POST` will insert
	        `PUT` will update
	        `DELETE` will delete

	`/api/resource/{doctype}/{name}?run_method={method}` will run a whitelisted controller method
	"""

	parts = xhiveframework.request.path[1:].split("/", 3)
	call = doctype = name = None

	if len(parts) > 1:
		call = parts[1]

	if len(parts) > 2:
		doctype = parts[2]

	if len(parts) > 3:
		name = parts[3]

	if call == "method":
		xhiveframework.local.form_dict.cmd = doctype
		return xhiveframework.handler.handle()

	elif call == "resource":
		if "run_method" in xhiveframework.local.form_dict:
			method = xhiveframework.local.form_dict.pop("run_method")
			doc = xhiveframework.get_doc(doctype, name)
			doc.is_whitelisted(method)

			if xhiveframework.local.request.method == "GET":
				if not doc.has_permission("read"):
					xhiveframework.throw(_("Not permitted"), xhiveframework.PermissionError)
				xhiveframework.local.response.update({"data": doc.run_method(method, **xhiveframework.local.form_dict)})

			if xhiveframework.local.request.method == "POST":
				if not doc.has_permission("write"):
					xhiveframework.throw(_("Not permitted"), xhiveframework.PermissionError)

				xhiveframework.local.response.update({"data": doc.run_method(method, **xhiveframework.local.form_dict)})
				xhiveframework.db.commit()

		else:
			if name:
				if xhiveframework.local.request.method == "GET":
					doc = xhiveframework.get_doc(doctype, name)
					if not doc.has_permission("read"):
						raise xhiveframework.PermissionError
					xhiveframework.local.response.update({"data": doc})

				if xhiveframework.local.request.method == "PUT":
					data = get_request_form_data()

					doc = xhiveframework.get_doc(doctype, name, for_update=True)

					if "flags" in data:
						del data["flags"]

					# Not checking permissions here because it's checked in doc.save
					doc.update(data)

					xhiveframework.local.response.update({"data": doc.save().as_dict()})

					# check for child table doctype
					if doc.get("parenttype"):
						xhiveframework.get_doc(doc.parenttype, doc.parent).save()

					xhiveframework.db.commit()

				if xhiveframework.local.request.method == "DELETE":
					# Not checking permissions here because it's checked in delete_doc
					xhiveframework.delete_doc(doctype, name, ignore_missing=False)
					xhiveframework.local.response.http_status_code = 202
					xhiveframework.local.response.message = "ok"
					xhiveframework.db.commit()

			elif doctype:
				if xhiveframework.local.request.method == "GET":
					# set fields for xhiveframework.get_list
					if xhiveframework.local.form_dict.get("fields"):
						xhiveframework.local.form_dict["fields"] = json.loads(xhiveframework.local.form_dict["fields"])

					# set limit of records for xhiveframework.get_list
					xhiveframework.local.form_dict.setdefault(
						"limit_page_length",
						xhiveframework.local.form_dict.limit or xhiveframework.local.form_dict.limit_page_length or 20,
					)

					# convert strings to native types - only as_dict and debug accept bool
					for param in ["as_dict", "debug"]:
						param_val = xhiveframework.local.form_dict.get(param)
						if param_val is not None:
							xhiveframework.local.form_dict[param] = sbool(param_val)

					# evaluate xhiveframework.get_list
					data = xhiveframework.call(xhiveframework.client.get_list, doctype, **xhiveframework.local.form_dict)

					# set xhiveframework.get_list result to response
					xhiveframework.local.response.update({"data": data})

				if xhiveframework.local.request.method == "POST":
					# fetch data from from dict
					data = get_request_form_data()
					data.update({"doctype": doctype})

					# insert document from request data
					doc = xhiveframework.get_doc(data).insert()

					# set response data
					xhiveframework.local.response.update({"data": doc.as_dict()})

					# commit for POST requests
					xhiveframework.db.commit()
			else:
				raise xhiveframework.DoesNotExistError

	else:
		raise xhiveframework.DoesNotExistError

	return build_response("json")


def get_request_form_data():
	if xhiveframework.local.form_dict.data is None:
		data = xhiveframework.safe_decode(xhiveframework.local.request.get_data())
	else:
		data = xhiveframework.local.form_dict.data

	try:
		return xhiveframework.parse_json(data)
	except ValueError:
		return xhiveframework.local.form_dict


def validate_auth():
	"""
	Authenticate and sets user for the request.
	"""
	authorization_header = xhiveframework.get_request_header("Authorization", "").split(" ")

	if len(authorization_header) == 2:
		validate_oauth(authorization_header)
		validate_auth_via_api_keys(authorization_header)

	validate_auth_via_hooks()


def validate_oauth(authorization_header):
	"""
	Authenticate request using OAuth and set session user

	Args:
	        authorization_header (list of str): The 'Authorization' header containing the prefix and token
	"""

	from xhiveframework.integrations.oauth2 import get_oauth_server
	from xhiveframework.oauth import get_url_delimiter

	form_dict = xhiveframework.local.form_dict
	token = authorization_header[1]
	req = xhiveframework.request
	parsed_url = urlparse(req.url)
	access_token = {"access_token": token}
	uri = (
		parsed_url.scheme + "://" + parsed_url.netloc + parsed_url.path + "?" + urlencode(access_token)
	)
	http_method = req.method
	headers = req.headers
	body = req.get_data()
	if req.content_type and "multipart/form-data" in req.content_type:
		body = None

	try:
		required_scopes = xhiveframework.db.get_value("OAuth Bearer Token", token, "scopes").split(
			get_url_delimiter()
		)
		valid, oauthlib_request = get_oauth_server().verify_request(
			uri, http_method, body, headers, required_scopes
		)
		if valid:
			xhiveframework.set_user(xhiveframework.db.get_value("OAuth Bearer Token", token, "user"))
			xhiveframework.local.form_dict = form_dict
	except AttributeError:
		pass


def validate_auth_via_api_keys(authorization_header):
	"""
	Authenticate request using API keys and set session user

	Args:
	        authorization_header (list of str): The 'Authorization' header containing the prefix and token
	"""

	try:
		auth_type, auth_token = authorization_header
		authorization_source = xhiveframework.get_request_header("XhiveFramework-Authorization-Source")
		if auth_type.lower() == "basic":
			api_key, api_secret = xhiveframework.safe_decode(base64.b64decode(auth_token)).split(":")
			validate_api_key_secret(api_key, api_secret, authorization_source)
		elif auth_type.lower() == "token":
			api_key, api_secret = auth_token.split(":")
			validate_api_key_secret(api_key, api_secret, authorization_source)
	except binascii.Error:
		xhiveframework.throw(
			_("Failed to decode token, please provide a valid base64-encoded token."),
			xhiveframework.InvalidAuthorizationToken,
		)
	except (AttributeError, TypeError, ValueError):
		pass


def validate_api_key_secret(api_key, api_secret, xhiveframework_authorization_source=None):
	"""xhiveframework_authorization_source to provide api key and secret for a doctype apart from User"""
	doctype = xhiveframework_authorization_source or "User"
	doc = xhiveframework.db.get_value(doctype=doctype, filters={"api_key": api_key}, fieldname=["name"])
	form_dict = xhiveframework.local.form_dict
	doc_secret = xhiveframework.utils.password.get_decrypted_password(doctype, doc, fieldname="api_secret")
	if api_secret == doc_secret:
		if doctype == "User":
			user = xhiveframework.db.get_value(doctype="User", filters={"api_key": api_key}, fieldname=["name"])
		else:
			user = xhiveframework.db.get_value(doctype, doc, "user")
		if xhiveframework.local.login_manager.user in ("", "Guest"):
			xhiveframework.set_user(user)
		xhiveframework.local.form_dict = form_dict


def validate_auth_via_hooks():
	for auth_hook in xhiveframework.get_hooks("auth_hooks", []):
		xhiveframework.get_attr(auth_hook)()
