from functools import lru_cache

import frappe
from frappe import _
from frappe.geo.country_info import get_all as get_all_countries
from frappe.model import DEFAULT_FIELDS, display_fieldtypes
from frappe.utils import get_datetime, now_datetime, today
from frappe.utils.data import cstr, sbool

LAYOUT_FIELDTYPES = set(display_fieldtypes)

EVENT_PROPOSAL_EXCLUDE_FIELDS = DEFAULT_FIELDS | {
	"naming_series",
	"amended_from",
	"host",
	"host_company",
	"host_company_logo",
	"additional_notes",
	"status",
	"submitted_by",
}

STANDARD_EXCLUDE_FIELDS = DEFAULT_FIELDS | {
	"additional_fields",
	"event",
	"section_break_additional",
	"submitted_by",
	"status",
}


@frappe.whitelist(allow_guest=True)  # nosemgrep: frappe-semgrep-rules.rules.security.guest-whitelisted-method
def get_dial_codes() -> list:
	return _get_dial_codes()


@lru_cache(maxsize=1)
def _get_dial_codes() -> list:
	data = get_all_countries()
	codes = []
	seen = set()
	for country in sorted(data):
		info = data[country]
		isd = info.get("isd", "")
		code = (info.get("code") or "").upper()
		if isd and isd not in seen:
			codes.append({"country": country, "code": code, "dial_code": isd})
			seen.add(isd)
	return codes


def get_form_fields(doctype: str, exclude_fields: set) -> list:
	meta = frappe.get_meta(doctype)
	fields = []
	for df in meta.fields:
		if df.fieldname in exclude_fields:
			continue
		if df.fieldtype in LAYOUT_FIELDTYPES:
			continue
		if df.hidden:
			continue
		if df.read_only:
			continue
		default_value = df.default
		if default_value:
			if default_value == "Today":
				default_value = today()
			elif default_value == "Now":
				default_value = cstr(now_datetime())
			elif default_value.startswith("eval:") or default_value.startswith("%"):
				default_value = None

		field_data = {
			"fieldname": df.fieldname,
			"fieldtype": df.fieldtype,
			"label": df.label or df.fieldname,
			"options": df.options,
			"reqd": df.reqd,
			"default": default_value,
			"description": df.description,
		}
		if df.fieldtype == "Link" and df.options:
			link_values = frappe.get_all(
				df.options,
				fields=["name"],
				limit_page_length=0,
				order_by="name asc",
			)
			field_data["link_options"] = [d.name for d in link_values]
		if df.fieldtype == "Table" and df.options:
			child_meta = frappe.get_meta(df.options)
			child_fields = []
			for child_df in child_meta.fields:
				if child_df.fieldtype in LAYOUT_FIELDTYPES:
					continue
				if child_df.hidden:
					continue
				child_fields.append(
					{
						"fieldname": child_df.fieldname,
						"fieldtype": child_df.fieldtype,
						"label": child_df.label or child_df.fieldname,
						"options": child_df.options,
						"reqd": child_df.reqd,
					}
				)
			field_data["child_fields"] = child_fields
		fields.append(field_data)
	return fields


def validate_custom_form(event_route: str, form_route: str):
	event_name = frappe.get_cached_value("Pohodex Event Manager Event", {"route": event_route}, "name")
	if not event_name:
		frappe.throw(_("Event not found"), frappe.DoesNotExistError)
	event_doc = frappe.get_cached_doc("Pohodex Event Manager Event", event_name)

	if not event_doc.is_published:
		frappe.throw(_("Event not found"), frappe.DoesNotExistError)

	form_row = None
	for row in event_doc.custom_forms:
		if row.route == form_route and row.publish:
			form_row = row
			break

	if not form_row:
		frappe.throw(_("This form is not available for this event"), frappe.DoesNotExistError)

	return event_doc, form_row


def get_auto_set_fields(form_doctype: str):
	meta = frappe.get_meta(form_doctype)
	auto_set = {}
	for df in meta.fields:
		if df.fieldname == "event" and df.fieldtype == "Link":
			auto_set["event"] = "from_route"
		elif df.fieldname == "submitted_by" and df.fieldtype == "Link":
			auto_set["submitted_by"] = "session_user"
	return auto_set


@frappe.whitelist(allow_guest=True)  # nosemgrep: frappe-semgrep-rules.rules.security.guest-whitelisted-method
def get_custom_form_data(event_route: str, form_route: str) -> dict:
	event_doc, form_row = validate_custom_form(event_route, form_route)
	form_doctype = form_row.form_doctype
	allow_guest_submission = sbool(event_doc.allow_guest_booking)

	if not allow_guest_submission and frappe.session.user == "Guest":
		frappe.throw(_("Please log in to submit this form"), frappe.AuthenticationError)

	event_data = {
		"name": event_doc.name,
		"title": event_doc.title,
		"route": event_doc.route,
		"banner_image": event_doc.banner_image,
		"start_date": event_doc.start_date,
		"end_date": event_doc.end_date,
		"start_time": event_doc.start_time,
		"end_time": event_doc.end_time,
		"time_zone": event_doc.time_zone,
		"venue": event_doc.venue,
		"medium": event_doc.medium,
		"short_description": event_doc.short_description,
	}

	closed = False
	if form_row.auto_close_at and get_datetime(form_row.auto_close_at) < now_datetime():
		closed = True

	if closed:
		return {
			"form_fields": [],
			"custom_fields": [],
			"form_title": form_doctype,
			"event": event_data,
			"closed": True,
			"closed_title": form_row.closed_title or _("Submissions Closed"),
			"closed_message": form_row.closed_message or _("Submissions for this form have closed."),
			"success_title": "",
			"success_message": "",
		}

	auto_set = get_auto_set_fields(form_doctype)
	exclude_fields = STANDARD_EXCLUDE_FIELDS | set(auto_set.keys())
	form_fields = get_form_fields(form_doctype, exclude_fields)

	form_doctype_meta = frappe.get_meta(form_doctype)
	custom_fields = []
	if form_doctype_meta.has_field("additional_fields"):
		custom_fields = frappe.get_all(
			"Pohodex Event Manager Custom Field",
			filters={
				"event": event_doc.name,
				"applied_to": "Custom Form",
				"custom_form_doctype": form_doctype,
				"enabled": 1,
			},
			fields=[
				"label",
				"fieldname",
				"fieldtype",
				"options",
				"mandatory",
				"placeholder",
				"default_value",
				"order",
			],
			order_by="order asc",
		)

	return {
		"form_fields": form_fields,
		"custom_fields": custom_fields,
		"form_title": form_doctype_meta.name,
		"event": event_data,
		"closed": False,
		"closed_title": form_row.closed_title or _("Submissions Closed"),
		"closed_message": form_row.closed_message or _("Submissions for this form have closed."),
		"success_title": form_row.success_title or _("Thank you!"),
		"success_message": form_row.success_message or "",
	}


@frappe.whitelist(allow_guest=True)  # nosemgrep: frappe-semgrep-rules.rules.security.guest-whitelisted-method
def submit_custom_form(
	event_route: str, form_route: str, data: dict | str, custom_fields_data: dict | str | None = None
) -> None:
	event_doc, form_row = validate_custom_form(event_route, form_route)
	form_doctype = form_row.form_doctype

	if not event_doc.allow_guest_booking and frappe.session.user == "Guest":
		frappe.throw(_("Please login to submit this form"), frappe.AuthenticationError)

	if form_row.auto_close_at and get_datetime(form_row.auto_close_at) < now_datetime():
		frappe.throw(_("Submissions for this form have closed."))

	data = frappe.parse_json(data) or {}
	custom_fields_data = frappe.parse_json(custom_fields_data) or {}

	auto_set = get_auto_set_fields(form_doctype)
	exclude_fields = STANDARD_EXCLUDE_FIELDS | set(auto_set.keys())

	doc_data = {"doctype": form_doctype}

	for field, source in auto_set.items():
		if source == "from_route":
			doc_data[field] = event_doc.name
		elif source == "session_user":
			doc_data[field] = frappe.session.user

	allowed_fieldnames = {f["fieldname"] for f in get_form_fields(form_doctype, exclude_fields)}
	for fieldname, value in data.items():
		if fieldname in allowed_fieldnames:
			doc_data[fieldname] = value

	meta = frappe.get_meta(form_doctype)
	for df in meta.fields:
		if df.fieldtype == "Table" and df.fieldname not in exclude_fields:
			if df.fieldname in data and isinstance(data[df.fieldname], list):
				doc_data[df.fieldname] = data[df.fieldname]

	doc = frappe.get_doc(doc_data)

	if custom_fields_data and meta.has_field("additional_fields"):
		custom_field_definitions = frappe.get_all(
			"Pohodex Event Manager Custom Field",
			filters={
				"event": event_doc.name,
				"applied_to": "Custom Form",
				"custom_form_doctype": form_doctype,
				"enabled": 1,
			},
			fields=["fieldname", "label", "fieldtype"],
		)
		allowed_custom = {cf["fieldname"]: cf for cf in custom_field_definitions}

		for fieldname, value in custom_fields_data.items():
			if fieldname in allowed_custom and value not in (None, ""):
				cf = allowed_custom[fieldname]
				doc.append(
					"additional_fields",
					{
						"label": cf["label"],
						"fieldname": fieldname,
						"fieldtype": cf["fieldtype"],
						"value": cstr(value),
					},
				)

	doc.insert(ignore_permissions=True)


def validate_event_proposal_settings():
	settings = frappe.get_cached_doc("Pohodex Event Manager Settings")
	if not settings.accept_event_proposals:
		frappe.throw(_("Event Proposals are not being accepted"), frappe.DoesNotExistError)

	if not settings.allow_guest_event_proposals and frappe.session.user == "Guest":
		frappe.throw(_("Please log in to submit a proposal"), frappe.AuthenticationError)

	return settings


@frappe.whitelist(allow_guest=True)  # nosemgrep: frappe-semgrep-rules.rules.security.guest-whitelisted-method
def get_event_proposal_form_data() -> dict:
	settings = validate_event_proposal_settings()
	form_fields = get_form_fields("Event Proposal", EVENT_PROPOSAL_EXCLUDE_FIELDS)

	return {
		"form_fields": form_fields,
		"form_title": _("Event Proposal"),
		"banner_title": settings.event_proposal_banner_title or _("Propose an Event"),
		"success_title": settings.event_proposal_success_title or _("Thank you!"),
		"success_message": settings.event_proposal_success_message or "",
	}


@frappe.whitelist(allow_guest=True)  # nosemgrep: frappe-semgrep-rules.rules.security.guest-whitelisted-method
def submit_event_proposal(data: dict | str) -> None:
	validate_event_proposal_settings()

	data = frappe.parse_json(data) or {}

	doc_data = {"doctype": "Event Proposal"}

	if frappe.session.user != "Guest":
		doc_data["submitted_by"] = frappe.session.user

	allowed_fieldnames = {
		f["fieldname"] for f in get_form_fields("Event Proposal", EVENT_PROPOSAL_EXCLUDE_FIELDS)
	}
	for fieldname, value in data.items():
		if fieldname in allowed_fieldnames:
			doc_data[fieldname] = value

	meta = frappe.get_meta("Event Proposal")
	for df in meta.fields:
		if df.fieldtype == "Table" and df.fieldname not in EVENT_PROPOSAL_EXCLUDE_FIELDS:
			if df.fieldname in data and isinstance(data[df.fieldname], list):
				doc_data[df.fieldname] = data[df.fieldname]

	frappe.get_doc(doc_data).insert(ignore_permissions=True)
