"""REST API v2

This file defines routes and implementation for REST API.

Note:
	- All functions in this file should be treated as "whitelisted" as they are exposed via routes
	- None of the functions present here should be called from python code, their location and
	  internal implementation can change without treating it as "breaking change".
"""
import json
from typing import Any

from werkzeug.routing import Rule

import jingrow
import jingrow.client
from jingrow import _, get_newargs, is_whitelisted
from jingrow.core.pagetype.server_script.server_script_utils import get_server_script_map
from jingrow.handler import is_valid_http_method, run_server_script, upload_file

PERMISSION_MAP = {
	"GET": "read",
	"POST": "write",
}


def handle_rpc_call(method: str, pagetype: str | None = None):
	from jingrow.modules.utils import load_pagetype_module

	if pagetype:
		# Expand to run actual method from pagetype controller
		module = load_pagetype_module(pagetype)
		method = module.__name__ + "." + method

	method = jingrow.override_whitelisted_method(method)

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

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

	is_whitelisted(method)
	is_valid_http_method(method)

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


def login():
	"""Login happens implicitly, this function doesn't do anything."""
	pass


def logout():
	jingrow.local.login_manager.logout()
	jingrow.db.commit()


def read_pg(pagetype: str, name: str):
	pg = jingrow.get_pg(pagetype, name)
	pg.check_permission("read")
	pg.apply_fieldlevel_read_permissions()
	
	# 添加权限标记，与列表 API 保持一致
	try:
		pg._can_read = int(pg.has_permission("read"))
		pg._can_write = int(pg.has_permission("write"))
		pg._can_delete = int(pg.has_permission("delete"))
	except Exception:
		pg._can_read = 0
		pg._can_write = 0
		pg._can_delete = 0
	
	return pg


def document_list(pagetype: str):
	if jingrow.form_dict.get("fields"):
		jingrow.form_dict["fields"] = json.loads(jingrow.form_dict["fields"])

	# set limit of records for jingrow.get_list
	jingrow.form_dict.limit_page_length = jingrow.form_dict.limit or 20
	# evaluate jingrow.get_list
	return jingrow.call(jingrow.client.get_list, pagetype, **jingrow.form_dict)


def count(pagetype: str) -> int:
	from jingrow.desk.reportview import get_count

	jingrow.form_dict.pagetype = pagetype

	return get_count()


def create_pg(pagetype: str):
	data = jingrow.form_dict
	data.pop("pagetype", None)
	return jingrow.new_pg(pagetype, **data).insert()


def copy_pg(pagetype: str, name: str, ignore_no_copy: bool = True):
	"""Return a clean copy of the given document that can be modified and posted as a new document."""
	pg = jingrow.get_pg(pagetype, name)
	pg.check_permission("read")
	pg.apply_fieldlevel_read_permissions()

	copy = jingrow.copy_pg(pg, ignore_no_copy=ignore_no_copy)

	return copy.as_dict(no_private_properties=True, no_nulls=True)


def update_pg(pagetype: str, name: str):
	data = jingrow.form_dict

	pg = jingrow.get_pg(pagetype, name, for_update=True)
	data.pop("flags", None)
	pg.update(data)
	pg.save()

	# check for child table pagetype
	if pg.get("parenttype"):
		jingrow.get_pg(pg.parenttype, pg.parent).save()

	return pg


def delete_pg(pagetype: str, name: str):
	jingrow.client.delete_pg(pagetype, name)
	jingrow.response.http_status_code = 202
	return "ok"


def get_meta(pagetype: str):
	jingrow.only_for("All")
	return jingrow.get_meta(pagetype)


def execute_pg_method(pagetype: str, name: str, method: str | None = None):
	"""Get a document from DB and execute method on it.

	Use cases:
	- Submitting/cancelling document
	- Triggering some kind of update on a document
	"""
	method = method or jingrow.form_dict.pop("run_method")
	pg = jingrow.get_pg(pagetype, name)
	pg.is_whitelisted(method)

	pg.check_permission(PERMISSION_MAP[jingrow.request.method])
	return pg.run_method(method, **jingrow.form_dict)


def run_pg_method(method: str, document: dict[str, Any] | str, kwargs=None):
	"""run a whitelisted controller method on in-memory document.


	This is useful for building clients that don't necessarily encode all the business logic but
	call server side function on object to validate and modify the pg.

	The pg CAN exists in DB too and can write to DB as well if method is POST.
	"""

	if isinstance(document, str):
		document = jingrow.parse_json(document)

	if kwargs is None:
		kwargs = {}

	pg = jingrow.get_pg(document)
	pg._original_modified = pg.modified
	pg.check_if_latest()

	pg.check_permission(PERMISSION_MAP[jingrow.request.method])

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

	new_kwargs = get_newargs(fn, kwargs)
	response = pg.run_method(method, **new_kwargs)
	jingrow.response.pgs.append(pg)  # send modified document and result both.
	return response


url_rules = [
	# RPC calls
	Rule("/action/login", endpoint=login),
	Rule("/action/logout", endpoint=logout),
	Rule("/action/ping", endpoint=jingrow.ping),
	Rule("/action/upload_file", endpoint=upload_file),
	Rule("/action/<method>", endpoint=handle_rpc_call),
	Rule(
		"/action/run_pg_method",
		methods=["GET", "POST"],
		endpoint=lambda: jingrow.call(run_pg_method, **jingrow.form_dict),
	),
	Rule("/action/<pagetype>/<method>", endpoint=handle_rpc_call),
	# Page level APIs
	Rule("/document/<pagetype>", methods=["GET"], endpoint=document_list),
	Rule("/document/<pagetype>", methods=["POST"], endpoint=create_pg),
	Rule("/document/<pagetype>/<path:name>/", methods=["GET"], endpoint=read_pg),
	Rule("/document/<pagetype>/<path:name>/copy", methods=["GET"], endpoint=copy_pg),
	Rule("/document/<pagetype>/<path:name>/", methods=["PATCH", "PUT"], endpoint=update_pg),
	Rule("/document/<pagetype>/<path:name>/", methods=["DELETE"], endpoint=delete_pg),
	Rule(
		"/document/<pagetype>/<path:name>/action/<method>/",
		methods=["GET", "POST"],
		endpoint=execute_pg_method,
	),
	# Collection level APIs
	Rule("/pagetype/<pagetype>/meta", methods=["GET"], endpoint=get_meta),
	Rule("/pagetype/<pagetype>/count", methods=["GET"], endpoint=count),
]
