# Copyright (c) 2022, XhiveFramework Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE

import os
from mimetypes import guess_type
from typing import TYPE_CHECKING

from werkzeug.wrappers import Response

import xhiveframework
import xhiveframework.sessions
import xhiveframework.utils
from xhiveframework import _, is_whitelisted, ping
from xhiveframework.core.doctype.server_script.server_script_utils import get_server_script_map
from xhiveframework.monitor import add_data_to_monitor
from xhiveframework.utils import cint
from xhiveframework.utils.csvutils import build_csv_response
from xhiveframework.utils.deprecations import deprecation_warning
from xhiveframework.utils.image import optimize_image
from xhiveframework.utils.response import build_response

if TYPE_CHECKING:
	from xhiveframework.core.doctype.file.file import File
	from xhiveframework.core.doctype.user.user import User

ALLOWED_MIMETYPES = (
	"image/png",
	"image/jpeg",
	"application/pdf",
	"application/msword",
	"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
	"application/vnd.ms-excel",
	"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
	"application/vnd.oasis.opendocument.text",
	"application/vnd.oasis.opendocument.spreadsheet",
	"text/plain",
	"video/quicktime",
	"video/mp4",
)


def handle():
	"""handle request"""

	cmd = xhiveframework.local.form_dict.cmd
	data = None

	if cmd != "login":
		data = execute_cmd(cmd)

	# data can be an empty string or list which are valid responses
	if data is not None:
		if isinstance(data, Response):
			# method returns a response object, pass it on
			return data

		# add the response to `message` label
		xhiveframework.response["message"] = data


def execute_cmd(cmd, from_async=False):
	"""execute a request as python module"""
	for hook in reversed(xhiveframework.get_hooks("override_whitelisted_methods", {}).get(cmd, [])):
		# override using the last hook
		cmd = hook
		break

	# via server script
	server_script = get_server_script_map().get("_api", {}).get(cmd)
	if server_script:
		return run_server_script(server_script)

	try:
		method = get_attr(cmd)
	except Exception as e:
		xhiveframework.throw(_("Failed to get method for command {0} with {1}").format(cmd, e))

	if from_async:
		method = method.queue

	if method != run_doc_method:
		is_whitelisted(method)
		is_valid_http_method(method)

	return xhiveframework.call(method, **xhiveframework.form_dict)


def run_server_script(server_script):
	response = xhiveframework.get_doc("Server Script", server_script).execute_method()

	# some server scripts return output using flags (empty dict by default),
	# while others directly modify xhiveframework.response
	# return flags if not empty dict (this overwrites xhiveframework.response.message)
	if response != {}:
		return response


def is_valid_http_method(method):
	if xhiveframework.flags.in_safe_exec:
		return

	http_method = xhiveframework.local.request.method

	if http_method not in xhiveframework.allowed_http_methods_for_whitelisted_func[method]:
		throw_permission_error()


def throw_permission_error():
	xhiveframework.throw(_("Not permitted"), xhiveframework.PermissionError)


@xhiveframework.whitelist(allow_guest=True)
def logout():
	xhiveframework.local.login_manager.logout()
	xhiveframework.db.commit()


@xhiveframework.whitelist(allow_guest=True)
def web_logout():
	xhiveframework.local.login_manager.logout()
	xhiveframework.db.commit()
	xhiveframework.respond_as_web_page(
		_("Logged Out"), _("You have been successfully logged out"), indicator_color="green"
	)


@xhiveframework.whitelist()
def uploadfile():
	ret = None
	check_write_permission(xhiveframework.form_dict.doctype, xhiveframework.form_dict.docname)

	try:
		if xhiveframework.form_dict.get("from_form"):
			try:
				ret = xhiveframework.get_doc(
					{
						"doctype": "File",
						"attached_to_name": xhiveframework.form_dict.docname,
						"attached_to_doctype": xhiveframework.form_dict.doctype,
						"attached_to_field": xhiveframework.form_dict.docfield,
						"file_url": xhiveframework.form_dict.file_url,
						"file_name": xhiveframework.form_dict.filename,
						"is_private": xhiveframework.utils.cint(xhiveframework.form_dict.is_private),
						"content": xhiveframework.form_dict.filedata,
						"decode": True,
					}
				)
				ret.save()
			except xhiveframework.DuplicateEntryError:
				# ignore pass
				ret = None
				xhiveframework.db.rollback()
		else:
			if xhiveframework.form_dict.get("method"):
				method = xhiveframework.get_attr(xhiveframework.form_dict.method)
				is_whitelisted(method)
				ret = method()
	except Exception:
		xhiveframework.errprint(xhiveframework.utils.get_traceback())
		xhiveframework.response["http_status_code"] = 500
		ret = None

	return ret


@xhiveframework.whitelist(allow_guest=True)
def upload_file():
	user = None
	if xhiveframework.session.user == "Guest":
		if xhiveframework.get_system_settings("allow_guests_to_upload_files"):
			ignore_permissions = True
		else:
			raise xhiveframework.PermissionError
	else:
		user: "User" = xhiveframework.get_doc("User", xhiveframework.session.user)
		ignore_permissions = False

	files = xhiveframework.request.files
	is_private = xhiveframework.form_dict.is_private
	doctype = xhiveframework.form_dict.doctype
	docname = xhiveframework.form_dict.docname
	fieldname = xhiveframework.form_dict.fieldname
	file_url = xhiveframework.form_dict.file_url
	folder = xhiveframework.form_dict.folder or "Home"
	method = xhiveframework.form_dict.method
	filename = xhiveframework.form_dict.file_name
	optimize = xhiveframework.form_dict.optimize
	content = None

	if library_file := xhiveframework.form_dict.get("library_file_name"):
		xhiveframework.has_permission("File", doc=library_file, throw=True)
		doc = xhiveframework.get_value(
			"File",
			xhiveframework.form_dict.library_file_name,
			["is_private", "file_url", "file_name"],
			as_dict=True,
		)
		is_private = doc.is_private
		file_url = doc.file_url
		filename = doc.file_name

	if not ignore_permissions:
		check_write_permission(doctype, docname)

	if "file" in files:
		file = files["file"]
		content = file.stream.read()
		filename = file.filename

		content_type = guess_type(filename)[0]
		if optimize and content_type and content_type.startswith("image/"):
			args = {"content": content, "content_type": content_type}
			if xhiveframework.form_dict.max_width:
				args["max_width"] = int(xhiveframework.form_dict.max_width)
			if xhiveframework.form_dict.max_height:
				args["max_height"] = int(xhiveframework.form_dict.max_height)
			content = optimize_image(**args)

	xhiveframework.local.uploaded_file = content
	xhiveframework.local.uploaded_filename = filename

	if content is not None and (xhiveframework.session.user == "Guest" or (user and not user.has_desk_access())):
		filetype = guess_type(filename)[0]
		if filetype not in ALLOWED_MIMETYPES:
			xhiveframework.throw(_("You can only upload JPG, PNG, PDF, TXT or Microsoft documents."))

	if method:
		method = xhiveframework.get_attr(method)
		is_whitelisted(method)
		return method()
	else:
		return xhiveframework.get_doc(
			{
				"doctype": "File",
				"attached_to_doctype": doctype,
				"attached_to_name": docname,
				"attached_to_field": fieldname,
				"folder": folder,
				"file_name": filename,
				"file_url": file_url,
				"is_private": cint(is_private),
				"content": content,
			}
		).save(ignore_permissions=ignore_permissions)


def check_write_permission(doctype: str | None = None, name: str | None = None):
	check_doctype = doctype and not name
	if doctype and name:
		try:
			doc = xhiveframework.get_doc(doctype, name)
			doc.has_permission("write")
		except xhiveframework.DoesNotExistError:
			# doc has not been inserted yet, name is set to "new-some-doctype"
			check_doctype = True

	if check_doctype:
		xhiveframework.has_permission(doctype, "write", throw=True)


@xhiveframework.whitelist(allow_guest=True)
def download_file(file_url: str):
	"""
	Download file using token and REST API. Valid session or
	token is required to download private files.

	Method : GET
	Endpoints : download_file, xhiveframework.core.doctype.file.file.download_file
	URL Params : file_name = /path/to/file relative to site path
	"""
	file: "File" = xhiveframework.get_doc("File", {"file_url": file_url})
	if not file.is_downloadable():
		raise xhiveframework.PermissionError

	xhiveframework.local.response.filename = os.path.basename(file_url)
	xhiveframework.local.response.filecontent = file.get_content()
	xhiveframework.local.response.type = "download"


def get_attr(cmd):
	"""get method object from cmd"""
	if "." in cmd:
		method = xhiveframework.get_attr(cmd)
	else:
		deprecation_warning(
			f"Calling shorthand for {cmd} is deprecated, please specify full path in RPC call."
		)
		method = globals()[cmd]
	return method


def run_doc_method(method, docs=None, dt=None, dn=None, arg=None, args=None):
	"""run a whitelisted controller method"""
	from inspect import signature

	if not args and arg:
		args = arg

	if dt:  # not called from a doctype (from a page)
		if not dn:
			dn = dt  # single
		doc = xhiveframework.get_doc(dt, dn)

	else:
		docs = xhiveframework.parse_json(docs)
		doc = xhiveframework.get_doc(docs)
		doc._original_modified = doc.modified
		doc.check_if_latest()

	if not doc or not doc.has_permission("read"):
		throw_permission_error()

	try:
		args = xhiveframework.parse_json(args)
	except ValueError:
		pass

	method_obj = getattr(doc, method)
	fn = getattr(method_obj, "__func__", method_obj)
	is_whitelisted(fn)
	is_valid_http_method(fn)

	fnargs = list(signature(method_obj).parameters)

	if not fnargs or (len(fnargs) == 1 and fnargs[0] == "self"):
		response = doc.run_method(method)

	elif "args" in fnargs or not isinstance(args, dict):
		response = doc.run_method(method, args)

	else:
		response = doc.run_method(method, **args)

	xhiveframework.response.docs.append(doc)
	if response is None:
		return

	# build output as csv
	if cint(xhiveframework.form_dict.get("as_csv")):
		build_csv_response(response, _(doc.doctype).replace(" ", ""))
		return

	xhiveframework.response["message"] = response

	add_data_to_monitor(methodname=method)


# for backwards compatibility
runserverobj = run_doc_method
