# Copyright (c) 2022, JINGROW and Contributors
# License: MIT. See LICENSE

import os
from mimetypes import guess_type
from typing import TYPE_CHECKING

from werkzeug.wrappers import Response

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

if TYPE_CHECKING:
	from jingrow.core.pagetype.file.file import File
	from jingrow.core.pagetype.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",
	"text/csv",
)


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

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


def execute_cmd(cmd, from_async=False):
	"""execute a request as python module"""
	cmd = jingrow.override_whitelisted_method(cmd)

	# 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:
		jingrow.throw(_("Failed to get method for command {0} with {1}").format(cmd, e))

	if from_async:
		method = method.queue

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

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


def run_server_script(server_script):
	response = jingrow.get_pg("Server Script", server_script).execute_method()

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


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

	http_method = jingrow.local.request.method

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


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


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


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


@jingrow.whitelist()
def uploadfile():
	deprecation_warning(
		"uploadfile is deprecated and will be removed in v16. Use upload_file instead.",
	)
	ret = None
	check_write_permission(jingrow.form_dict.pagetype, jingrow.form_dict.pagename)

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

	return ret


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

	files = jingrow.request.files
	is_private = jingrow.form_dict.is_private
	pagetype = jingrow.form_dict.pagetype
	pagename = jingrow.form_dict.pagename
	fieldname = jingrow.form_dict.fieldname
	file_url = jingrow.form_dict.file_url
	folder = jingrow.form_dict.folder or "Home"
	method = jingrow.form_dict.method
	filename = jingrow.form_dict.file_name
	optimize = jingrow.form_dict.optimize
	content = None

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

	if not ignore_permissions:
		check_write_permission(pagetype, pagename)

	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 jingrow.form_dict.max_width:
				args["max_width"] = int(jingrow.form_dict.max_width)
			if jingrow.form_dict.max_height:
				args["max_height"] = int(jingrow.form_dict.max_height)
			content = optimize_image(**args)

	jingrow.local.uploaded_file_url = file_url
	jingrow.local.uploaded_file = content
	jingrow.local.uploaded_filename = filename

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

	if method:
		method = jingrow.get_attr(method)
		is_whitelisted(method)
		return method()
	else:
		return jingrow.get_pg(
			{
				"pagetype": "File",
				"attached_to_pagetype": pagetype,
				"attached_to_name": pagename,
				"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(pagetype: str | None = None, name: str | None = None):
	if not pagetype:
		return

	if not name:
		jingrow.has_permission(pagetype, "write", throw=True)
		return

	try:
		pg = jingrow.get_pg(pagetype, name)
	except jingrow.DoesNotExistError:
		# pg has not been inserted yet, name is set to "new-some-pagetype"
		# If pg inserts fine then only this attachment will be linked see file/utils.py:relink_mismatched_files
		jingrow.new_pg(pagetype).check_permission("write")
		return

	pg.check_permission("write")


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

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


def get_attr(cmd):
	"""get method object from cmd"""
	if "." in cmd:
		method = jingrow.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_pg_method(method, pgs=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 pagetype (from a page)
		if not dn:
			dn = dt  # single
		pg = jingrow.get_pg(dt, dn)

	else:
		pgs = jingrow.parse_json(pgs)
		pg = jingrow.get_pg(pgs)
		pg._original_modified = pg.modified
		pg.check_if_latest()

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

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

	method_obj = getattr(pg, 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 = pg.run_method(method)

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

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

	jingrow.response.pgs.append(pg)
	if response is None:
		return

	# build output as csv
	if cint(jingrow.form_dict.get("as_csv")):
		build_csv_response(response, _(pg.pagetype).replace(" ", ""))
		return

	jingrow.response["message"] = response

	add_data_to_monitor(methodname=method)


# for backwards compatibility
runserverobj = run_pg_method
