"""Create ERPNext Customer/Supplier/Bank Account records for unmapped Bank Integration entries."""

import frappe
from frappe import _


def _find_existing_party(doctype, name_field, name, tax_id):
	"""VOEN-first lookup: a matching tax_id wins over a name match.

	Returns the existing party's name, or None.
	"""
	tid = (tax_id or "").strip()
	if tid:
		existing = frappe.db.get_value(doctype, {"tax_id": tid}, "name")
		if existing:
			return existing
	clean_name = (name or "").strip()
	if clean_name:
		existing = frappe.db.get_value(doctype, {name_field: clean_name}, "name")
		if existing:
			return existing
	return None


def _pick_leaf(doctype, configured, label):
	"""Validate `configured` as a leaf node of an NSM tree doctype
	(Customer Group / Supplier Group / Territory).

	- If configured: it must exist and NOT be a group node — else raise.
	- If not configured: return None. The party is then created without that
	  field (ERPNext allows an empty customer_group / supplier_group / territory).
	"""
	if not configured:
		return None
	if not frappe.db.exists(doctype, configured):
		frappe.throw(_("{0} '{1}' does not exist.").format(label, configured))
	if frappe.db.get_value(doctype, configured, "is_group"):
		frappe.throw(_("{0} '{1}' is a group node — pick a leaf (non-group) {0}.").format(label, configured))
	return configured


@frappe.whitelist()
def create_unmapped_customers(bank_integration):
	"""For each Bank Integration Customer Mapping row without erp_customer, create an ERPNext Customer."""
	bi = frappe.get_doc("Bank Statement Importer", bank_integration)

	unmapped = []
	for mapping in bi.customer_mappings:
		if not mapping.erp_customer and mapping.bi_customer_name:
			if frappe.db.exists("Bank Integration Customer", mapping.bi_customer_name):
				bi_cust = frappe.get_doc("Bank Integration Customer", mapping.bi_customer_name)
				unmapped.append((mapping, bi_cust))

	if not unmapped:
		return {"success": True, "created_count": 0, "message": "No unmapped customers in table"}

	# Validate any configured group / territory per row upfront (clear message on
	# bad config). If nothing is configured the party is created without it.
	resolved = [
		(
			_pick_leaf("Customer Group", mapping.customer_group or bi.default_customer_group, "Customer Group"),
			_pick_leaf("Territory", mapping.territory or bi.default_territory, "Territory"),
		)
		for mapping, _bi_cust in unmapped
	]

	created = 0
	linked = 0
	for (mapping, bi_cust), (customer_group, territory) in zip(unmapped, resolved):
		try:
			customer_name = bi_cust.customer_name
			payment_terms = mapping.payment_terms or bi.default_payment_terms

			# VOEN beats name: if an existing Customer shares this tax_id, reuse it
			# rather than creating a duplicate. Same for name as a fallback.
			existing = _find_existing_party("Customer", "customer_name", customer_name, bi_cust.tax_id)
			if existing:
				mapping.erp_customer = existing
				mapping.mapping_type = "Manual"
				existing_group, existing_territory = frappe.db.get_value(
					"Customer", existing, ["customer_group", "territory"]
				) or (None, None)
				frappe.db.set_value("Bank Integration Customer", bi_cust.name, {
					"status": "Mapped",
					"mapped_customer": existing,
					"customer_group": existing_group,
					"territory": existing_territory,
					"payment_terms": payment_terms or None,
				}, update_modified=False)
				linked += 1
				continue

			party = frappe.new_doc("Customer")
			party.customer_name = customer_name
			party.customer_type = "Company"
			party.customer_group = customer_group or None
			party.territory = territory or None

			if bi_cust.tax_id:
				party.tax_id = bi_cust.tax_id

			if payment_terms:
				party.payment_terms = payment_terms

			party.insert(ignore_permissions=True)

			mapping.erp_customer = party.name
			mapping.mapping_type = "Automatic"

			frappe.db.set_value("Bank Integration Customer", bi_cust.name, {
				"status": "Mapped",
				"mapped_customer": party.name,
				"customer_group": party.customer_group,
				"territory": party.territory,
				"payment_terms": payment_terms or None,
			}, update_modified=False)

			created += 1
		except Exception as e:
			frappe.log_error(
				f"create_unmapped_customers error for {bi_cust.customer_name}: {e}",
				"BI Customer Creation",
			)

	if created > 0 or linked > 0:
		bi.save(ignore_permissions=True)
	frappe.db.commit()
	return {"success": True, "created_count": created, "linked_count": linked}


@frappe.whitelist()
def create_unmapped_suppliers(bank_integration):
	bi = frappe.get_doc("Bank Statement Importer", bank_integration)

	unmapped = []
	for mapping in bi.supplier_mappings:
		if not mapping.erp_supplier and mapping.bi_supplier_name:
			if frappe.db.exists("Bank Integration Supplier", mapping.bi_supplier_name):
				bi_supp = frappe.get_doc("Bank Integration Supplier", mapping.bi_supplier_name)
				unmapped.append((mapping, bi_supp))

	if not unmapped:
		return {"success": True, "created_count": 0, "message": "No unmapped suppliers in table"}

	resolved = [
		_pick_leaf("Supplier Group", mapping.supplier_group or bi.default_supplier_group, "Supplier Group")
		for mapping, _bi_supp in unmapped
	]

	created = 0
	linked = 0
	for (mapping, bi_supp), supplier_group in zip(unmapped, resolved):
		try:
			supplier_name = bi_supp.supplier_name
			payment_terms = mapping.payment_terms or bi.default_payment_terms

			existing = _find_existing_party("Supplier", "supplier_name", supplier_name, bi_supp.tax_id)
			if existing:
				mapping.erp_supplier = existing
				mapping.mapping_type = "Manual"
				existing_group = frappe.db.get_value("Supplier", existing, "supplier_group")
				frappe.db.set_value("Bank Integration Supplier", bi_supp.name, {
					"status": "Mapped",
					"mapped_supplier": existing,
					"supplier_group": existing_group,
					"payment_terms": payment_terms or None,
				}, update_modified=False)
				linked += 1
				continue

			party = frappe.new_doc("Supplier")
			party.supplier_name = supplier_name
			party.supplier_type = "Company"
			party.supplier_group = supplier_group or None

			if bi_supp.tax_id:
				party.tax_id = bi_supp.tax_id

			if payment_terms:
				party.payment_terms = payment_terms

			party.insert(ignore_permissions=True)

			mapping.erp_supplier = party.name
			mapping.mapping_type = "Automatic"

			frappe.db.set_value("Bank Integration Supplier", bi_supp.name, {
				"status": "Mapped",
				"mapped_supplier": party.name,
				"supplier_group": party.supplier_group,
				"payment_terms": payment_terms or None,
			}, update_modified=False)

			created += 1
		except Exception as e:
			frappe.log_error(
				f"create_unmapped_suppliers error for {bi_supp.supplier_name}: {e}",
				"BI Supplier Creation",
			)

	if created > 0 or linked > 0:
		bi.save(ignore_permissions=True)
	frappe.db.commit()
	return {"success": True, "created_count": created, "linked_count": linked}


@frappe.whitelist()
def add_unmapped_to_table(bank_integration, table):
	"""Add Bank Integration Customer/Supplier/Purpose registry rows (status=New) into the appropriate child table.

	`table` ∈ {customer_mappings, supplier_mappings, transaction_mappings}.
	"""
	bi = frappe.get_doc("Bank Statement Importer", bank_integration)

	if table == "customer_mappings":
		existing = {row.bi_customer_name for row in bi.customer_mappings if row.bi_customer_name}
		new_records = frappe.get_all(
			"Bank Integration Customer",
			filters={"status": "New", "parent_bank_integration": bank_integration},
			fields=["name", "customer_name", "tax_id"],
		)
		added = 0
		for r in new_records:
			if r.name in existing:
				continue
			bi.append("customer_mappings", {
				"bi_customer_name": r.name,
				"tax_id": r.tax_id or None,
				"mapping_type": "Manual",
			})
			added += 1

	elif table == "supplier_mappings":
		existing = {row.bi_supplier_name for row in bi.supplier_mappings if row.bi_supplier_name}
		new_records = frappe.get_all(
			"Bank Integration Supplier",
			filters={"status": "New", "parent_bank_integration": bank_integration},
			fields=["name", "supplier_name", "tax_id"],
		)
		added = 0
		for r in new_records:
			if r.name in existing:
				continue
			bi.append("supplier_mappings", {
				"bi_supplier_name": r.name,
				"tax_id": r.tax_id or None,
				"mapping_type": "Manual",
			})
			added += 1

	elif table == "transaction_mappings":
		existing = {row.purpose_keyword for row in bi.transaction_mappings if row.purpose_keyword}
		new_records = frappe.get_all(
			"Bank Integration Purpose",
			filters={"status": "New", "parent_bank_integration": bank_integration},
			fields=["name", "purpose_keyword", "direction"],
		)
		added = 0
		for r in new_records:
			if r.name in existing:
				continue
			pt = ""
			if r.direction == "Pay":
				pt = "Pay"
			elif r.direction == "Receive":
				pt = "Receive"
			bi.append("transaction_mappings", {
				"purpose_keyword": r.name,
				"payment_type": pt or None,
				"document_type": "Payment Entry",
			})
			added += 1
	else:
		frappe.throw(_("Unknown table: {0}").format(table))

	if added > 0:
		bi.save(ignore_permissions=True)
	frappe.db.commit()
	return {"success": True, "added_count": added}
