# Copyright (c) 2015, Frappe Technologies and contributors
# License: MIT. See LICENSE

from typing import Any

import frappe
from frappe import _
from frappe.core.doctype.submission_queue.submission_queue import queue_submission
from frappe.model.document import Document
from frappe.utils import cint
from frappe.utils.scheduler import is_scheduler_inactive


class BulkUpdate(Document):
	# begin: auto-generated types
	# This code is auto-generated. Do not modify anything in this block.

	from typing import TYPE_CHECKING

	if TYPE_CHECKING:
		from frappe.types import DF

		condition: DF.SmallText | None
		document_type: DF.Link
		field: DF.Literal[None]
		limit: DF.Int
		update_value: DF.SmallText
	# end: auto-generated types

	@frappe.whitelist()
	def bulk_update(self):
		self.check_permission("write")
		limit = self.limit if self.limit and cint(self.limit) < 500 else 500
		query_args = {"doctype": self.document_type, "limit": limit, "pluck": "name"}
		if self.condition:
			try:
				filters = frappe.parse_json(self.condition)
				if isinstance(filters, dict):
					if "or_filters" in filters:
						query_args["or_filters"] = filters.pop("or_filters")
				query_args["filters"] = filters
			except Exception as e:
				frappe.throw(_("The Bulk Update could not happen due to <b>{0}</b>").format(str(e)))

		docnames = frappe.get_all(**query_args)
		return submit_cancel_or_update_docs(
			self.document_type, docnames, "update", {self.field: self.update_value}
		)


@frappe.whitelist()
def submit_cancel_or_update_docs(
	doctype: str,
	docnames: str | list[str],
	action: str = "submit",
	data: str | dict[str, Any] | None = None,
	task_id: str | None = None,
) -> list[str] | None:
	if not frappe.get_cached_value("User", frappe.session.user, "bulk_actions"):
		frappe.throw(_("You are not allowed to perform bulk actions."), frappe.PermissionError)

	if isinstance(docnames, str):
		docnames = frappe.parse_json(docnames)

	if len(docnames) < 20:
		return _bulk_action(doctype, docnames, action, data, task_id)
	elif len(docnames) <= 500:
		frappe.msgprint(_("Bulk operation is enqueued in background."), alert=True)
		frappe.enqueue(
			_bulk_action,
			doctype=doctype,
			docnames=docnames,
			action=action,
			data=data,
			task_id=task_id,
			queue="short",
			timeout=1000,
		)
	else:
		frappe.throw(_("Bulk operations only support up to 500 documents."), title=_("Too Many Documents"))


def _bulk_action(doctype, docnames, action, data, task_id=None):
	if data:
		data = frappe.parse_json(data)

	child_table_updates = data.get("child_table_updates") if data else None
	failed = []
	num_documents = len(docnames)

	for idx, docname in enumerate(docnames, 1):
		doc = frappe.get_doc(doctype, docname)
		try:
			message = ""
			if action == "submit" and doc.docstatus.is_draft():
				if doc.meta.queue_in_background and not is_scheduler_inactive():
					queue_submission(doc, action)
					message = _("Queuing {0} for Submission").format(doctype)
				else:
					doc.submit()
					message = _("Submitting {0}").format(doctype)
			elif action == "cancel" and doc.docstatus.is_submitted():
				doc.cancel()
				message = _("Cancelling {0}").format(doctype)
			elif action == "update" and not doc.docstatus.is_cancelled():
				# Handle child table updates
				if child_table_updates:
					table_fields = doc.meta.get_table_fields()
					for child_doctype, field_updates in child_table_updates.items():
						# Find the table field that contains this child doctype
						table_fieldname = next(
							(field.fieldname for field in table_fields if field.options == child_doctype),
							None,
						)

						if table_fieldname and hasattr(doc, table_fieldname):
							child_meta = frappe.get_meta(child_doctype)
							child_docs = getattr(doc, table_fieldname)
							for child_doc in child_docs:
								for fieldname, value in field_updates.items():
									if child_meta.has_field(fieldname):
										setattr(child_doc, fieldname, value)

				# Handle regular field updates
				if data:
					doc.update(data)

				doc.save()
				message = _("Updating {0}").format(doctype)
			else:
				failed.append(docname)
			frappe.db.commit()
			frappe.publish_progress(
				percent=idx / num_documents * 100,
				title=message,
				description=docname,
				task_id=task_id,
			)

		except Exception:
			frappe.log_error("Bulk action failed")
			failed.append(docname)
			frappe.db.rollback()

	return failed


from frappe.deprecation_dumpster import show_progress
