import frappe
from frappe import _
from frappe.utils import now_datetime, flt
from datetime import datetime
from kapital_bank.api import BIRBankClient

_AZ_MAP = str.maketrans("ƏəİışŞÇçÖöÜüĞğ", "EeIissCcOoUuGg")


def _latinize(text: str, max_len: int = None) -> str:
	"""Transliterate Azerbaijani → Latin, strip non-ASCII, truncate."""
	text = str(text or "").translate(_AZ_MAP)
	text = text.encode("ascii", errors="ignore").decode("ascii").strip()
	return text[:max_len] if max_len else text


@frappe.whitelist()
def sync_bank_codes(login_name=None):
	"""Fetch bank codes from API and upsert into Kapital Bank Bank Code doctype."""
	client = BIRBankClient(login_name)
	resp = client.get_json("/bank-codes")
	bank_codes = resp.get("responseData", {}).get("bankCodes", [])

	added = 0
	updated = 0

	for item in bank_codes:
		bank_code = (item.get("bankCode") or "").strip()
		bank_name = (item.get("bankName") or "").strip()
		if not bank_code:
			continue

		if frappe.db.exists("Kapital Bank Bank Code", bank_code):
			existing_name = frappe.db.get_value("Kapital Bank Bank Code", bank_code, "bank_name")
			if existing_name != bank_name:
				frappe.db.set_value("Kapital Bank Bank Code", bank_code, "bank_name", bank_name)
				updated += 1
		else:
			doc = frappe.new_doc("Kapital Bank Bank Code")
			doc.bank_code = bank_code
			doc.bank_name = bank_name
			doc.insert(ignore_permissions=True)
			added += 1

	frappe.db.commit()
	return {"success": True, "added": added, "updated": updated, "total": len(bank_codes)}


@frappe.whitelist()
def send_payment_request(name, login_name=None):
	"""Validate Payment Request and send outgoing transfer to Kapital Bank."""
	pr = frappe.get_doc("Payment Request", name)

	if pr.docstatus != 1:
		frappe.throw(_("Payment Request {0} is not submitted.").format(name))

	if pr.payment_request_type != "Outward":
		frappe.throw(_("Payment Request {0} is not of type Outward.").format(name))

	if pr.status not in ("Initiated", "Partially Paid"):
		frappe.throw(_("Payment Request {0} has invalid status: {1}.").format(name, pr.status))

	kb_account = pr.get("kb_account")
	if not kb_account:
		frappe.throw(_("Field 'Kapital Bank Account' is not set."))

	mapping = frappe.db.get_value(
		"Kapital Bank Account Mapping",
		{"iban": kb_account, "parenttype": "Kapital Bank Settings"},
		["iban", "bank_account"],
		as_dict=True,
	)
	if not mapping:
		frappe.throw(_("Kapital Bank Account {0} is not mapped in Kapital Bank Settings.").format(kb_account))

	from_iban = kb_account

	party_bank_account = pr.get("kb_beneficiary_bank_account")
	if not party_bank_account:
		frappe.throw(_("Field 'Beneficiary Bank Account' is not set."))

	payee_ba = frappe.get_doc("Bank Account", party_bank_account)

	errors = []

	if not payee_ba.iban:
		errors.append(_("Field 'IBAN' is not set on Bank Account {0}.").format(party_bank_account))

	kb_bank_code = payee_ba.get("kb_bank_code") or ""
	if not kb_bank_code:
		errors.append(_("Field 'KB Bank Code' is not set on Bank Account {0}.").format(party_bank_account))
	elif len(kb_bank_code) != 6:
		errors.append(_("Field 'KB Bank Code' on Bank Account {0} must be exactly 6 characters.").format(party_bank_account))

	if not flt(pr.grand_total):
		errors.append(_("Field 'Amount' is zero on Payment Request {0}.").format(name))

	to_cust_name = _latinize(pr.party_name, 34)
	if not to_cust_name:
		errors.append(_("Party name '{0}' is empty after Latin transliteration.").format(pr.party_name))

	existing = frappe.db.get_value(
		"Kapital Bank Payment",
		{"payment_request": name, "status": ["not in", ["Rejected", "Cancelled"]]},
		"name",
	)
	if existing:
		errors.append(_("Active KB Payment {0} already exists for this Payment Request.").format(existing))

	if errors:
		return {"success": False, "errors": errors}

	op_name = f"PAY{datetime.now().strftime('%Y%m%d%H%M%S')}00"
	payee_iban = payee_ba.iban
	tax_id = payee_ba.get("tax_id") or ""
	to_cust_name = _latinize(pr.party_name, 34)
	purpose = _latinize(pr.name, 64)

	payload = {
		"fromAccount": from_iban,
		"transferData": {
			"operationName": op_name,
			"benBankCode": kb_bank_code,
			"toAccount": payee_iban,
			"toTaxNo": tax_id,
			"toCustName": to_cust_name,
			"amount": float(flt(pr.grand_total)),
			"purpose1": purpose,
		},
	}

	import requests as _requests

	frappe.log_error(f"[KB] Sending transfer payload for {name}:\n{payload}", "KB Transfer Payload")

	try:
		client = BIRBankClient(login_name)
		resp = client.post_json("/v2/internal-transfer", json=payload)
		operation_id = resp.get("operationId", "")

		kbp = frappe.new_doc("Kapital Bank Payment")
		kbp.update({
			"payment_request": name,
			"operation_name": op_name,
			"operation_id": operation_id,
			"status": "Sent",
			"from_account": from_iban,
			"to_account": payee_iban,
			"to_tax_no": tax_id,
			"to_cust_name": to_cust_name,
			"ben_bank_code": kb_bank_code,
			"amount": flt(pr.grand_total),
			"purpose1": purpose,
			"sent_at": now_datetime(),
			"status_updated_at": now_datetime(),
		})
		kbp.insert(ignore_permissions=True)
		frappe.db.commit()
	except _requests.HTTPError as e:
		frappe.db.rollback()
		return {"success": False, "message": str(e)}
	except Exception:
		frappe.log_error(frappe.get_traceback(), f"KB Send Payment Request {name}")
		frappe.db.rollback()
		raise

	return {"success": True}


@frappe.whitelist()
def check_payment_status(name, login_name=None):
	"""Poll status for pending KB Payments of a Payment Request."""
	pending = frappe.get_all(
		"Kapital Bank Payment",
		filters={"payment_request": name, "status": ["in", ["Sent", "Confirm Wait"]]},
		fields=["name", "operation_name", "payment_request", "operation_id"],
	)

	if not pending:
		return {"success": True, "updated": 0, "message": _("No pending transfers")}

	client = BIRBankClient(login_name)

	STATUS_MAP = {
		"CONFIRM_WAIT": "Confirm Wait",
		"SUCCESS": "Success",
		"CONFIRMED": "Success",
		"REJECTED": "Rejected",
		"CANCELLED": "Cancelled",
		"CANCELED": "Cancelled",
	}

	updated = 0
	errors = []

	for kbp in pending:
		try:
			resp = client.get_json("/internal-transfer/status", params={"operatorName": kbp.operation_name})
			data = resp.get("data", [])
			if not data:
				frappe.logger().warning(f"[KB] Status check for {kbp.operation_name}: empty data response")
				continue

			entry = data[0]
			bank_status = entry.get("status", "")
			new_status = STATUS_MAP.get(bank_status)

			if new_status is None:
				frappe.db.set_value(
					"Kapital Bank Payment",
					kbp.name,
					{"error_message": f"Unknown bank status: {bank_status}", "status_updated_at": now_datetime()},
					update_modified=False,
				)
				frappe.db.commit()
				continue

			frappe.db.set_value(
				"Kapital Bank Payment",
				kbp.name,
				{
					"status": new_status,
					"status_updated_at": now_datetime(),
					"error_message": entry.get("description", ""),
				},
				update_modified=False,
			)
			frappe.db.commit()
			updated += 1

			if new_status == "Success":
				pe_name = _create_pe_from_pr(kbp.payment_request)
				if pe_name:
					frappe.db.set_value(
						"Kapital Bank Payment", kbp.name, "payment_entry", pe_name, update_modified=False
					)
					frappe.db.commit()
			elif new_status == "Rejected":
				_handle_rejected_pr(kbp.payment_request, entry.get("description", ""))

		except Exception as e:
			frappe.log_error(frappe.get_traceback(), f"KB Check Status {kbp.operation_name}")
			errors.append(str(e))

	return {"success": True, "updated": updated, "errors": errors}


@frappe.whitelist()
def cancel_payment_request(name, login_name=None):
	"""Cancel pending bank transfers for a Payment Request."""
	pending = frappe.get_all(
		"Kapital Bank Payment",
		filters={"payment_request": name, "status": ["in", ["Sent", "Confirm Wait"]]},
		fields=["name", "operation_name", "operation_id"],
	)

	if not pending:
		return {"success": True, "cancelled": 0, "message": _("Nothing to cancel")}

	client = BIRBankClient(login_name)
	cancelled = 0
	errors = []

	for kbp in pending:
		try:
			if not kbp.operation_id:
				frappe.logger().warning(f"[KB] Cancel: no operation_id for {kbp.operation_name}, cancelling locally")
			else:
				resp = client.delete("/internal-transfer", params={"operationId": kbp.operation_id})
				if not resp.ok:
					frappe.log_error(
						f"[KB] Cancel DELETE failed for {kbp.operation_name}: HTTP {resp.status_code}",
						"KB Cancel Error",
					)

			frappe.db.set_value(
				"Kapital Bank Payment",
				kbp.name,
				{"status": "Cancelled", "status_updated_at": now_datetime()},
				update_modified=False,
			)
			frappe.db.commit()
			cancelled += 1

		except Exception as e:
			frappe.log_error(frappe.get_traceback(), f"KB Cancel {kbp.operation_name}")
			errors.append(str(e))

	return {"success": True, "cancelled": cancelled, "errors": errors}


def poll_pending_payments():
	"""Scheduler: poll status for all Payment Requests with pending KB Payments."""
	rows = frappe.db.sql(
		"""
		SELECT DISTINCT payment_request
		FROM `tabKapital Bank Payment`
		WHERE status IN ('Sent', 'Confirm Wait')
		AND payment_request IS NOT NULL
		""",
		as_dict=True,
	)

	for row in rows:
		try:
			check_payment_status(row.payment_request)
		except Exception:
			frappe.log_error(frappe.get_traceback(), "KB Poll Error")


def on_cancel_payment_request(doc, method):
	"""Doc event: block cancellation of Payment Request if pending bank transfers exist."""
	pending = frappe.get_all(
		"Kapital Bank Payment",
		filters={"payment_request": doc.name, "status": ["in", ["Sent", "Confirm Wait"]]},
		fields=["name", "operation_name"],
	)
	if pending:
		names = ", ".join(p.operation_name for p in pending)
		frappe.throw(
			_(
				"Cannot cancel Payment Request {0}: pending bank transfers exist ({1}). "
				"Cancel the bank transfers first using the 'Kapital Bank' menu."
			).format(doc.name, names)
		)


def _create_pe_from_pr(pr_name):
	"""Create and submit a Payment Entry from a Payment Request. Returns PE name or None."""
	pr = frappe.get_doc("Payment Request", pr_name)
	if pr.status not in ("Initiated", "Partially Paid"):
		frappe.log_error(
			f"KB: Payment Request {pr_name} has status '{pr.status}', skipping PE creation",
			"KB Auto PE",
		)
		return None
	pe = pr.create_payment_entry(submit=True)
	frappe.db.commit()
	return pe.name if pe else None


def _handle_rejected_pr(pr_name, description):
	"""Log rejection — PR stays Initiated so user can retry or cancel manually."""
	frappe.log_error(
		f"KB Transfer rejected for Payment Request {pr_name}: {description}",
		"KB Payment Rejected",
	)
