import json

import frappe


@frappe.whitelist()
def create_purpose_mappings(transactions, paid_from, paid_to, document_type="Payment Entry", mode="Both"):
	"""Bulk-create transaction mappings and/or documents from selected BRT transactions."""
	try:
		txn_list = json.loads(transactions) if isinstance(transactions, str) else transactions

		do_mappings = mode in ("Both", "Mappings Only")
		do_documents = mode in ("Both", "Documents & Reconcile")

		created_mappings = 0
		already_mapped = 0
		skipped_no_data = 0
		created_docs = 0
		reconciled = 0
		errors = []

		if do_mappings:
			settings = frappe.get_single("Kapital Bank Settings")
			existing = {
				(row.purpose_keyword, row.payment_type)
				for row in settings.transaction_mappings
				if row.purpose_keyword and row.payment_type
			}

			# Deduplicate within batch; first occurrence wins for party info
			unique_purposes = {}

			for txn in txn_list:
				purpose = (txn.get("purpose") or "").strip()
				if not purpose:
					skipped_no_data += 1
					continue
				payment_type = "Pay" if txn.get("drcr") == "D" else "Receive"
				key = (purpose, payment_type)
				if key not in unique_purposes:
					unique_purposes[key] = {
						"count": 0,
						# party_type is already "Customer"/"Supplier" from ERPNext BRT
						"counterparty_type": (txn.get("party_type") or "").strip(),
						"counterparty": (txn.get("party") or "").strip(),
					}
				unique_purposes[key]["count"] += 1

			for (purpose_text, payment_type), data in unique_purposes.items():
				purpose_name = _ensure_purpose(purpose_text)

				if (purpose_name, payment_type) in existing:
					already_mapped += 1
					continue

				settings.append("transaction_mappings", {
					"purpose_keyword": purpose_name,
					"payment_type": payment_type,
					"paid_from": paid_from,
					"paid_to": paid_to,
					"document_type": document_type,
					"counterparty_type": data["counterparty_type"] or None,
					"counterparty": data["counterparty"] or None,
				})
				existing.add((purpose_name, payment_type))
				created_mappings += 1

			if created_mappings > 0:
				settings.save(ignore_permissions=True)
				for (purpose_text, _payment_type) in unique_purposes:
					purpose_name = frappe.db.get_value(
						"Kapital Bank Purpose", {"purpose_keyword": purpose_text}, "name"
					)
					if purpose_name:
						frappe.db.set_value(
							"Kapital Bank Purpose", purpose_name, "status", "Mapped",
							update_modified=False,
						)
				frappe.db.commit()

		if do_documents:
			for txn in txn_list:
				if not txn.get("bank_transaction_name"):
					errors.append({
						"reference_number": txn.get("reference_number") or "",
						"message": "No bank transaction name",
					})
					continue

				result = _create_and_reconcile_doc(txn, paid_from, paid_to, document_type)
				if result.get("success"):
					created_docs += 1
					if result.get("reconciled"):
						reconciled += 1
					elif result.get("error"):
						errors.append({
							"reference_number": txn.get("reference_number") or "",
							"message": result.get("error"),
						})
				else:
					errors.append({
						"reference_number": txn.get("reference_number") or "",
						"message": result.get("error") or "Unknown error",
					})

		return {
			"success": True,
			"created_mappings": created_mappings,
			"already_mapped": already_mapped,
			"skipped_no_data": skipped_no_data,
			"created_docs": created_docs,
			"reconciled": reconciled,
			"errors": errors,
		}

	except Exception as e:
		frappe.log_error(
			f"create_purpose_mappings: {e}\n{frappe.get_traceback()}",
			"Kapital Bank Mapping",
		)
		return {"success": False, "message": str(e)}


def _create_and_reconcile_doc(txn, paid_from, paid_to, document_type):
	"""Create a Payment Entry or Journal Entry and reconcile it with the bank transaction."""
	bank_transaction_name = txn.get("bank_transaction_name")
	try:
		bank_txn = frappe.get_doc("Bank Transaction", bank_transaction_name)

		if document_type == "Journal Entry":
			result = _create_journal_entry_for_brt(txn, paid_from, paid_to, bank_txn)
		else:
			result = _create_payment_entry_for_brt(txn, paid_from, paid_to, bank_txn)

		if not result.get("success"):
			return result

		doc_name = result["doc_name"]

		try:
			from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
				reconcile_vouchers,
			)

			reconcile_vouchers(
				bank_transaction_name,
				json.dumps([{
					"payment_doctype": document_type,
					"payment_name": doc_name,
					"amount": abs(float(txn.get("amount") or 0)),
				}]),
			)
			return {"success": True, "doc_name": doc_name, "reconciled": True}
		except Exception as re:
			frappe.log_error(
				f"reconcile_vouchers failed for {doc_name}: {re}\n{frappe.get_traceback()}",
				"Kapital Bank Mapping",
			)
			return {"success": True, "doc_name": doc_name, "reconciled": False, "error": str(re)}

	except Exception as e:
		frappe.log_error(
			f"_create_and_reconcile_doc failed for {bank_transaction_name}: {e}\n{frappe.get_traceback()}",
			"Kapital Bank Mapping",
		)
		return {"success": False, "error": str(e)}


def _create_payment_entry_for_brt(txn, paid_from, paid_to, bank_txn):
	"""Create and submit a Payment Entry for a BRT transaction."""
	try:
		amount = abs(float(txn.get("amount") or 0))
		party_type = (txn.get("party_type") or "").strip()
		party = (txn.get("party") or "").strip()
		posting_date = bank_txn.date or txn.get("date")

		pe = frappe.new_doc("Payment Entry")
		pe.payment_type = "Pay" if txn.get("drcr") == "D" else "Receive"
		pe.company = bank_txn.company
		pe.posting_date = posting_date
		pe.paid_from = paid_from
		pe.paid_to = paid_to
		pe.paid_amount = amount
		pe.received_amount = amount
		pe.reference_no = bank_txn.reference_number or txn.get("reference_number") or ""
		pe.reference_date = posting_date
		pe.remarks = txn.get("purpose") or ""
		if party_type and party:
			pe.party_type = party_type
			pe.party = party
		pe.insert(ignore_permissions=True)
		pe.submit()
		return {"success": True, "doc_name": pe.name}

	except Exception as e:
		frappe.log_error(
			f"_create_payment_entry_for_brt failed: {e}\n{frappe.get_traceback()}",
			"Kapital Bank Mapping",
		)
		return {"success": False, "error": str(e)}


def _create_journal_entry_for_brt(txn, paid_from, paid_to, bank_txn):
	"""Create and submit a Journal Entry for a BRT transaction."""
	try:
		amount = abs(float(txn.get("amount") or 0))
		party_type = (txn.get("party_type") or "").strip()
		party = (txn.get("party") or "").strip()
		is_pay = txn.get("drcr") == "D"
		posting_date = bank_txn.date or txn.get("date")
		ref_no = bank_txn.reference_number or txn.get("reference_number") or ""

		# Determine account types to assign party to the right row
		paid_from_type = frappe.db.get_value("Account", paid_from, "account_type") or ""
		paid_to_type = frappe.db.get_value("Account", paid_to, "account_type") or ""

		je = frappe.new_doc("Journal Entry")
		je.voucher_type = "Bank Entry"
		je.posting_date = posting_date
		je.company = bank_txn.company
		je.cheque_no = ref_no
		je.cheque_date = posting_date
		je.user_remark = txn.get("purpose") or ""

		# Pay: paid_from credit, paid_to debit
		# Receive: paid_from debit, paid_to credit
		if is_pay:
			from_credit, from_debit = amount, 0
			to_credit, to_debit = 0, amount
		else:
			from_credit, from_debit = 0, amount
			to_credit, to_debit = amount, 0

		from_row = {
			"account": paid_from,
			"credit_in_account_currency": from_credit,
			"debit_in_account_currency": from_debit,
		}
		to_row = {
			"account": paid_to,
			"credit_in_account_currency": to_credit,
			"debit_in_account_currency": to_debit,
		}

		if party_type and party:
			if paid_from_type in ("Receivable", "Payable"):
				from_row["party_type"] = party_type
				from_row["party"] = party
			if paid_to_type in ("Receivable", "Payable"):
				to_row["party_type"] = party_type
				to_row["party"] = party

		je.append("accounts", from_row)
		je.append("accounts", to_row)
		je.insert(ignore_permissions=True)
		je.submit()
		return {"success": True, "doc_name": je.name}

	except Exception as e:
		frappe.log_error(
			f"_create_journal_entry_for_brt failed: {e}\n{frappe.get_traceback()}",
			"Kapital Bank Mapping",
		)
		return {"success": False, "error": str(e)}


def _ensure_purpose(purpose_text):
	"""Return the name of an existing or newly created Kapital Bank Purpose."""
	existing = frappe.db.get_value(
		"Kapital Bank Purpose", {"purpose_keyword": purpose_text}, "name"
	)
	if existing:
		return existing

	try:
		doc = frappe.new_doc("Kapital Bank Purpose")
		doc.purpose_keyword = purpose_text
		doc.direction = "Both"
		doc.status = "New"
		doc.insert(ignore_permissions=True)
		return doc.name
	except frappe.DuplicateEntryError:
		return frappe.db.get_value(
			"Kapital Bank Purpose", {"purpose_keyword": purpose_text}, "name"
		)
	except Exception as e:
		frappe.log_error(
			f"Insert purpose failed for {purpose_text}: {e}",
			"Kapital Bank Purpose Loading",
		)
		raise
