"""Whitelisted import endpoints + background job for Bank Integration Excel import."""

import json

import frappe
from frappe import _
from frappe.utils import cint, flt

from jey_erp.bank_integration.excel_parser import parse_excel


@frappe.whitelist()
def parse_excel_for_preview(file_url, bank_integration, bank_account):
	"""Parse Excel and return preview transactions, deduped against existing BTs."""
	result = parse_excel(file_url, bank_integration)
	transactions = result["transactions"]
	dropped_date = result.get("dropped_date", 0)
	dropped_amount = result.get("dropped_amount", 0)
	dropped_direction = result.get("dropped_direction", 0)
	unknown_directions = result.get("unknown_directions", [])

	# Rows with an empty reference_number cannot be deduplicated against later
	# imports — surface a warning so the user notices and fixes the source file
	# or the Reference Number mapping in the preset.
	empty_ref_count = sum(1 for t in transactions if not (t.get("ref_no") or "").strip())

	if not transactions:
		return {
			"success": True,
			"transactions": [],
			"skipped_duplicates": 0,
			"total_parsed": 0,
			"dropped_date": dropped_date,
			"dropped_amount": dropped_amount,
			"dropped_direction": dropped_direction,
			"unknown_directions": unknown_directions,
			"empty_ref_count": 0,
		}

	ref_nos = [t["ref_no"] for t in transactions if t.get("ref_no")]
	existing = set()
	if ref_nos:
		rows = frappe.get_all(
			"Bank Transaction",
			filters={
				"reference_number": ["in", ref_nos],
				"bank_account": bank_account,
			},
			fields=["reference_number"],
			limit_page_length=0,
		)
		existing = {r.reference_number for r in rows}

	fresh = []
	skipped = 0
	for t in transactions:
		if t.get("ref_no") and t["ref_no"] in existing:
			skipped += 1
		else:
			fresh.append(t)

	return {
		"success": True,
		"transactions": fresh,
		"skipped_duplicates": skipped,
		"total_parsed": len(transactions),
		"dropped_date": dropped_date,
		"dropped_amount": dropped_amount,
		"dropped_direction": dropped_direction,
		"unknown_directions": unknown_directions,
		"empty_ref_count": empty_ref_count,
	}


@frappe.whitelist()
def import_bulk_bt(txn_list, bank_integration, bank_account):
	"""Enqueue bulk Bank Transaction creation as a background job.

	Creates Bank Transactions only — registries (Bank Integration Customer /
	Supplier / Purpose) are populated separately via the "Load Data" button on
	the Bank Integration form. Party is resolved later, at "Create & Reconcile"
	time, from the mappings.
	"""
	if isinstance(txn_list, str):
		txn_list = json.loads(txn_list)

	user = frappe.session.user

	frappe.enqueue(
		_process_bulk_bt_import,
		txn_list=txn_list,
		bank_integration=bank_integration,
		bank_account=bank_account,
		user=user,
		queue="default",
		timeout=600,
	)

	return {"success": True, "enqueued": True, "total": len(txn_list)}


def _process_bulk_bt_import(txn_list, bank_integration, bank_account, user):
	"""Background job: create one Bank Transaction per row."""
	import erpnext

	frappe.set_user(user)

	bi = frappe.get_doc("Bank Statement Importer", bank_integration)
	company = erpnext.get_default_company()

	ba_doc = frappe.get_doc("Bank Account", bank_account)
	ba_account = ba_doc.account
	ba_currency = (
		frappe.db.get_value("Account", ba_account, "account_currency") if ba_account else None
	) or "AZN"

	use_currency_from_file = bool(bi.get("use_currency_from_file"))

	total = len(txn_list)
	imported_count = 0
	errors = []

	for idx, txn in enumerate(txn_list):
		frappe.publish_realtime(
			"bi_bt_import_progress",
			{"current": idx + 1, "total": total},
			user=user,
		)

		try:
			counterparty = (txn.get("counterparty") or "").strip()
			drcr = (txn.get("drcr") or "D").upper()
			purpose = (txn.get("purpose") or "").strip()
			ref_no = (txn.get("ref_no") or "").strip()
			amount = abs(flt(txn.get("amount", 0)))

			row_currency = (txn.get("currency") or "").strip().upper() if use_currency_from_file else ""

			bt = frappe.new_doc("Bank Transaction")
			bt.date = txn.get("date")
			bt.deposit = amount if drcr == "C" else 0
			bt.withdrawal = amount if drcr == "D" else 0
			bt.currency = row_currency or ba_currency
			bt.description = txn.get("description") or purpose
			bt.reference_number = ref_no
			bt.transaction_id = ref_no
			bt.company = company
			bt.bank_party_name = counterparty or purpose
			bt.bank_account = bank_account

			bt.insert(ignore_permissions=True)
			bt.submit()

			imported_count += 1
			frappe.db.commit()

		except Exception as e:
			frappe.db.rollback()
			frappe.local.message_log = []
			errors.append({
				"ref_no": txn.get("ref_no"),
				"counterparty": txn.get("counterparty"),
				"amount": txn.get("amount"),
				"date": txn.get("date"),
				"error_type": "import_error",
				"message": str(e)[:500],
				"txn_data": txn,
			})
			frappe.log_error(
				f"BI BT import failed for ref {txn.get('ref_no')}: {e}\n{frappe.get_traceback()}",
				"BI BT Import Error",
			)
			frappe.db.commit()

	frappe.publish_realtime(
		"bi_bt_import_complete",
		{"total": total, "imported": imported_count, "errors": errors},
		user=user,
	)
	frappe.log_error(f"BI BT import done: {imported_count}/{total}", "BI BT Import Complete")


def _upsert_customer(name, voen, iban, bank_integration):
	"""Returns True if new record was created."""
	if voen and frappe.db.exists("Bank Integration Customer", {
		"tax_id": voen, "parent_bank_integration": bank_integration
	}):
		return False
	if frappe.db.exists("Bank Integration Customer", {
		"customer_name": name, "parent_bank_integration": bank_integration
	}):
		return False

	doc = frappe.new_doc("Bank Integration Customer")
	doc.customer_name = name
	doc.tax_id = voen or None
	doc.iban = iban or None
	doc.status = "New"
	doc.parent_bank_integration = bank_integration
	try:
		doc.insert(ignore_permissions=True)
		return True
	except frappe.DuplicateEntryError:
		return False


def _upsert_supplier(name, voen, iban, bank_integration):
	if voen and frappe.db.exists("Bank Integration Supplier", {
		"tax_id": voen, "parent_bank_integration": bank_integration
	}):
		return False
	if frappe.db.exists("Bank Integration Supplier", {
		"supplier_name": name, "parent_bank_integration": bank_integration
	}):
		return False

	doc = frappe.new_doc("Bank Integration Supplier")
	doc.supplier_name = name
	doc.tax_id = voen or None
	doc.iban = iban or None
	doc.status = "New"
	doc.parent_bank_integration = bank_integration
	try:
		doc.insert(ignore_permissions=True)
		return True
	except frappe.DuplicateEntryError:
		return False


def _upsert_purpose(purpose, drcr, bank_integration):
	existing = frappe.db.get_value("Bank Integration Purpose", {
		"purpose_keyword": purpose,
		"parent_bank_integration": bank_integration,
	}, ["name", "direction"], as_dict=True)

	new_dir = "Receive" if drcr == "C" else "Pay"

	if existing:
		if existing.direction and existing.direction != new_dir and existing.direction != "Both":
			frappe.db.set_value("Bank Integration Purpose", existing.name, "direction", "Both", update_modified=False)
		return False

	doc = frappe.new_doc("Bank Integration Purpose")
	doc.purpose_keyword = purpose
	doc.direction = new_dir
	doc.status = "New"
	doc.parent_bank_integration = bank_integration
	try:
		doc.insert(ignore_permissions=True)
		return True
	except frappe.DuplicateEntryError:
		return False


@frappe.whitelist()
def get_bi_reference_data_list(bank_integration, data_type, limit=100, offset=0):
	"""Paginated rows for the Bank Statement Importer "Data" tab subtabs.

	data_type ∈ {customers, suppliers, purposes}.
	"""
	limit = cint(limit)
	offset = cint(offset)

	try:
		if data_type == "customers":
			total = frappe.db.count("Bank Integration Customer", filters={"parent_bank_integration": bank_integration})
			data = frappe.get_all(
				"Bank Integration Customer",
				filters={"parent_bank_integration": bank_integration},
				fields=["name", "customer_name", "tax_id", "iban", "status", "mapped_customer", "creation"],
				limit=limit, start=offset, order_by="customer_name asc",
			)

		elif data_type == "suppliers":
			total = frappe.db.count("Bank Integration Supplier", filters={"parent_bank_integration": bank_integration})
			data = frappe.get_all(
				"Bank Integration Supplier",
				filters={"parent_bank_integration": bank_integration},
				fields=["name", "supplier_name", "tax_id", "iban", "status", "mapped_supplier", "creation"],
				limit=limit, start=offset, order_by="supplier_name asc",
			)

		elif data_type == "purposes":
			total = frappe.db.count("Bank Integration Purpose", filters={"parent_bank_integration": bank_integration})
			data = frappe.get_all(
				"Bank Integration Purpose",
				filters={"parent_bank_integration": bank_integration},
				fields=["name", "purpose_keyword", "direction", "status"],
				limit=limit, start=offset, order_by="purpose_keyword asc",
			)

		else:
			return {"success": False, "message": f"Unknown data_type: {data_type}"}

		return {
			"success": True,
			"data": data,
			"total_count": total,
			"has_more": (offset + limit) < total,
		}

	except Exception as e:
		frappe.log_error(f"get_bi_reference_data_list({data_type}): {e}\n{frappe.get_traceback()}", "BI Reference Data")
		return {"success": False, "message": str(e)}


@frappe.whitelist()
def update_bank_account_bi_default(bank_account, bi_type, bi_name):
	"""Save the user's BRT-time choice to the BA's hidden fields."""
	if not bank_account:
		frappe.throw(_("Bank Account required"))
	if bi_type and bi_type not in ("Bank Statement Importer", "Kapital Bank Settings"):
		frappe.throw(_("Invalid Bank Integration Type"))

	frappe.db.set_value("Bank Account", bank_account, {
		"bank_integration_type": bi_type or None,
		"bank_integration": bi_name or None,
	}, update_modified=False)
	frappe.db.commit()
	return {"success": True}


@frappe.whitelist()
def load_registries_from_excel(file_url, bank_integration,
                               load_customers=1, load_suppliers=1, load_purposes=1):
	"""Parse an Excel statement and upsert ONLY the selected reference registries
	(Bank Integration Customer / Supplier / Purpose) — no Bank Transactions.
	This is the "Load Data" action on the Bank Integration form, the file-based
	counterpart to kapital_bank's API-driven counterparty/purpose loaders."""
	load_customers = cint(load_customers)
	load_suppliers = cint(load_suppliers)
	load_purposes = cint(load_purposes)
	if not (load_customers or load_suppliers or load_purposes):
		return {"success": False, "message": _("Select at least one type to load.")}

	result = parse_excel(file_url, bank_integration)
	transactions = result["transactions"]
	dropped_date = result.get("dropped_date", 0)
	dropped_amount = result.get("dropped_amount", 0)
	dropped_direction = result.get("dropped_direction", 0)
	unknown_directions = result.get("unknown_directions", [])

	new_c = new_s = new_p = 0
	for txn in transactions:
		counterparty = (txn.get("counterparty") or "").strip()
		contr_voen = (txn.get("contr_voen") or "").strip()
		contr_iban = (txn.get("contr_iban") or "").strip()
		drcr = (txn.get("drcr") or "D").upper()
		purpose = (txn.get("purpose") or "").strip()

		if counterparty:
			if drcr == "C" and load_customers:
				if _upsert_customer(counterparty, contr_voen, contr_iban, bank_integration):
					new_c += 1
			elif drcr == "D" and load_suppliers:
				if _upsert_supplier(counterparty, contr_voen, contr_iban, bank_integration):
					new_s += 1

		if purpose and load_purposes:
			if _upsert_purpose(purpose, drcr, bank_integration):
				new_p += 1

	frappe.db.commit()
	return {
		"success": True,
		"total_rows": len(transactions),
		"new_customers": new_c,
		"new_suppliers": new_s,
		"new_purposes": new_p,
		"dropped_date": dropped_date,
		"dropped_amount": dropped_amount,
		"dropped_direction": dropped_direction,
		"unknown_directions": unknown_directions,
	}
