# Copyright (c) 2022, InfluxFramework LLC
# License: MIT. See LICENSE

import os
from mimetypes import guess_type
from typing import TYPE_CHECKING

from werkzeug.wrappers import Response

import influxframework
import influxframework.sessions
import influxframework.utils
from influxframework import _, is_whitelisted
from influxframework.core.doctype.server_script.server_script_utils import get_server_script_map
from influxframework.utils import cint
from influxframework.utils.csvutils import build_csv_response
from influxframework.utils.image import optimize_image
from influxframework.utils.response import build_response

if TYPE_CHECKING:
	from influxframework.core.doctype.file.file import File
	from influxframework.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",
)


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

	cmd = influxframework.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
		influxframework.response["message"] = data

	return build_response("json")


def execute_cmd(cmd, from_async=False):
	"""execute a request as python module"""
	for hook in influxframework.get_hooks("override_whitelisted_methods", {}).get(cmd, []):
		# override using the first 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:
		influxframework.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 influxframework.call(method, **influxframework.form_dict)


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

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


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

	http_method = influxframework.local.request.method

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


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


@influxframework.whitelist(allow_guest=True)
def version():
	return influxframework.__version__


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


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


@influxframework.whitelist()
def uploadfile():
	ret = None

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

	return ret


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

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

	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.startswith("image/"):
			args = {"content": content, "content_type": content_type}
			if influxframework.form_dict.max_width:
				args["max_width"] = int(influxframework.form_dict.max_width)
			if influxframework.form_dict.max_height:
				args["max_height"] = int(influxframework.form_dict.max_height)
			content = optimize_image(**args)

	influxframework.local.uploaded_file = content
	influxframework.local.uploaded_filename = filename

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

	if method:
		method = influxframework.get_attr(method)
		is_whitelisted(method)
		return method()
	else:
		return influxframework.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)


@influxframework.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, influxframework.core.doctype.file.file.download_file
	URL Params : file_name = /path/to/file relative to site path
	"""
	file: "File" = influxframework.get_doc("File", {"file_url": file_url})
	if not file.is_downloadable():
		raise influxframework.PermissionError

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


def get_attr(cmd):
	"""get method object from cmd"""
	if "." in cmd:
		method = influxframework.get_attr(cmd)
	else:
		method = globals()[cmd]
	influxframework.log("method:" + cmd)
	return method


@influxframework.whitelist(allow_guest=True)
def ping():
	return "pong"


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

	if not args and arg:
		args = arg

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

	else:
		docs = influxframework.parse_json(docs)
		doc = influxframework.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 = influxframework.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 = getfullargspec(method_obj).args

	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)

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

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

	influxframework.response["message"] = response


# for backwards compatibility
runserverobj = run_doc_method
