# Copyright (c) 2015, JINGROW and Contributors
# License: MIT. See LICENSE
"""
bootstrap client session
"""

import os

import jingrow
import jingrow.defaults
import jingrow.desk.desk_page
from jingrow.core.pagetype.navbar_settings.navbar_settings import get_app_logo, get_navbar_settings
from jingrow.desk.pagetype.changelog_feed.changelog_feed import get_changelog_feed_items
from jingrow.desk.pagetype.form_tour.form_tour import get_onboarding_ui_tours
from jingrow.desk.pagetype.route_history.route_history import frequently_visited_links
from jingrow.desk.form.load import get_meta_bundle
from jingrow.email.inbox import get_email_accounts
from jingrow.integrations.jingrow_providers.jingrowcloud_billing import is_fc_site
from jingrow.model.base_page import get_controller
from jingrow.permissions import has_permission
from jingrow.query_builder import PageType
from jingrow.query_builder.functions import Count
from jingrow.query_builder.terms import ParameterizedValueWrapper, SubQuery
from jingrow.social.pagetype.energy_point_log.energy_point_log import get_energy_points
from jingrow.social.pagetype.energy_point_settings.energy_point_settings import (
	is_energy_point_enabled,
)
from jingrow.utils import add_user_info, cstr, get_system_timezone
from jingrow.utils.change_log import get_versions
from jingrow.utils.jingrowcloud import on_jingrowcloud
from jingrow.website.pagetype.web_page_view.web_page_view import is_tracking_enabled


def get_bootinfo():
	"""build and return boot info"""
	from jingrow.translate import get_lang_dict, get_translated_pagetypes

	jingrow.set_user_lang(jingrow.session.user)
	bootinfo = jingrow._dict()
	hooks = jingrow.get_hooks()
	doclist = []

	# user
	get_user(bootinfo)

	# system info
	bootinfo.sitename = jingrow.local.site
	bootinfo.sysdefaults = jingrow.defaults.get_defaults()
	bootinfo.server_date = jingrow.utils.nowdate()
	bootinfo.app_name = jingrow.get_system_settings("app_name") or (jingrow.get_hooks("app_name") or [_("Jingrow")])[0]

	if jingrow.session["user"] != "Guest":
		bootinfo.user_info = get_user_info()

	bootinfo.modules = {}
	bootinfo.module_list = []
	load_desktop_data(bootinfo)
	bootinfo.letter_heads = get_letter_heads()
	bootinfo.active_domains = jingrow.get_active_domains()
	bootinfo.all_domains = [d.get("name") for d in jingrow.get_all("Domain")]
	add_layouts(bootinfo)

	bootinfo.module_app = jingrow.local.module_app
	bootinfo.single_types = [d.name for d in jingrow.get_all("PageType", {"issingle": 1})]
	bootinfo.nested_set_pagetypes = [
		d.parent for d in jingrow.get_all("PageField", {"fieldname": "lft"}, ["parent"])
	]
	add_home_page(bootinfo, doclist)
	bootinfo.page_info = get_allowed_pages()
	load_translations(bootinfo)
	add_timezone_info(bootinfo)
	load_conf_settings(bootinfo)
	load_print(bootinfo, doclist)
	doclist.extend(get_meta_bundle("Page"))
	bootinfo.home_folder = jingrow.db.get_value("File", {"is_home_folder": 1})
	bootinfo.navbar_settings = get_navbar_settings()
	bootinfo.notification_settings = get_notification_settings()
	bootinfo.onboarding_tours = get_onboarding_ui_tours()
	set_time_zone(bootinfo)

	# ipinfo
	if jingrow.session.data.get("ipinfo"):
		bootinfo.ipinfo = jingrow.session["data"]["ipinfo"]

	# add pgs
	bootinfo.pgs = doclist
	load_country_pg(bootinfo)
	load_currency_pgs(bootinfo)

	for method in hooks.boot_session or []:
		jingrow.get_attr(method)(bootinfo)

	if bootinfo.lang:
		bootinfo.lang = str(bootinfo.lang)
	bootinfo.versions = {k: v["version"] for k, v in get_versions().items()}

	bootinfo.error_report_email = jingrow.conf.error_report_email
	bootinfo.calendars = sorted(jingrow.get_hooks("calendars"))
	bootinfo.treeviews = jingrow.get_hooks("treeviews") or []
	bootinfo.lang_dict = get_lang_dict()
	bootinfo.success_action = get_success_action()
	bootinfo.update(get_email_accounts(user=jingrow.session.user))
	bootinfo.energy_points_enabled = is_energy_point_enabled()
	bootinfo.website_tracking_enabled = is_tracking_enabled()
	bootinfo.sms_gateway_enabled = bool(jingrow.db.get_single_value("SMS Settings", "sms_gateway_url"))
	bootinfo.points = get_energy_points(jingrow.session.user)
	bootinfo.frequently_visited_links = frequently_visited_links()
	bootinfo.link_preview_pagetypes = get_link_preview_pagetypes()
	bootinfo.additional_filters_config = get_additional_filters_from_hooks()
	bootinfo.desk_settings = get_desk_settings()
	bootinfo.app_logo_url = get_app_logo()
	bootinfo.link_title_pagetypes = get_link_title_pagetypes()
	bootinfo.translated_pagetypes = get_translated_pagetypes()
	bootinfo.subscription_conf = add_subscription_conf()
	bootinfo.marketplace_apps = get_marketplace_apps()
	bootinfo.is_fc_site = is_fc_site()
	bootinfo.changelog_feed = get_changelog_feed_items()
	bootinfo.vue_app_extensions = jingrow.get_hooks("vue_app_extensions") or []

	if sentry_dsn := get_sentry_dsn():
		bootinfo.sentry_dsn = sentry_dsn

	return bootinfo


def get_letter_heads():
	letter_heads = {}

	if not jingrow.has_permission("Letter Head"):
		return letter_heads
	for letter_head in jingrow.get_list("Letter Head", fields=["name", "content", "footer"]):
		letter_heads.setdefault(
			letter_head.name, {"header": letter_head.content, "footer": letter_head.footer}
		)

	return letter_heads


def load_conf_settings(bootinfo):
	from jingrow.core.api.file import get_max_file_size

	bootinfo.max_file_size = get_max_file_size()
	for key in ("developer_mode", "socketio_port", "file_watcher_port"):
		if key in jingrow.conf:
			bootinfo[key] = jingrow.conf.get(key)


def load_desktop_data(bootinfo):
	from jingrow.desk.desktop import get_workspace_sidebar_items

	bootinfo.allowed_workspaces = get_workspace_sidebar_items().get("pages")
	bootinfo.module_wise_workspaces = get_controller("Workspace").get_module_wise_workspaces()
	bootinfo.dashboards = jingrow.get_all("Dashboard")


def get_allowed_pages(cache=False):
	return get_user_pages_or_reports("Page", cache=cache)


def get_allowed_reports(cache=False):
	return get_user_pages_or_reports("Report", cache=cache)


def get_allowed_report_names(cache=False) -> set[str]:
	return {cstr(report) for report in get_allowed_reports(cache).keys() if report}


def get_user_pages_or_reports(parent, cache=False):
	if cache:
		has_role = jingrow.cache.get_value("has_role:" + parent, user=jingrow.session.user)
		if has_role:
			return has_role

	roles = jingrow.get_roles()
	has_role = {}

	page = PageType("Page")
	report = PageType("Report")

	is_report = parent == "Report"

	if is_report:
		columns = (report.name.as_("title"), report.ref_pagetype, report.report_type)
	else:
		columns = (page.title.as_("title"),)

	customRole = PageType("Custom Role")
	hasRole = PageType("Has Role")
	parentTable = PageType(parent)

	# get pages or reports set on custom role
	pages_with_custom_roles = (
		jingrow.qb.from_(customRole)
		.from_(hasRole)
		.from_(parentTable)
		.select(customRole[parent.lower()].as_("name"), customRole.modified, customRole.ref_pagetype, *columns)
		.where(
			(hasRole.parent == customRole.name)
			& (parentTable.name == customRole[parent.lower()])
			& (customRole[parent.lower()].isnotnull())
			& (hasRole.role.isin(roles))
		)
	).run(as_dict=True)

	for p in pages_with_custom_roles:
		has_role[p.name] = {"modified": p.modified, "title": p.title, "ref_pagetype": p.ref_pagetype}

	subq = (
		jingrow.qb.from_(customRole)
		.select(customRole[parent.lower()])
		.where(customRole[parent.lower()].isnotnull())
	)

	pages_with_standard_roles = (
		jingrow.qb.from_(hasRole)
		.from_(parentTable)
		.select(parentTable.name.as_("name"), parentTable.modified, *columns)
		.where(
			(hasRole.role.isin(roles)) & (hasRole.parent == parentTable.name) & (parentTable.name.notin(subq))
		)
		.distinct()
	)

	if is_report:
		pages_with_standard_roles = pages_with_standard_roles.where(report.disabled == 0)

	pages_with_standard_roles = pages_with_standard_roles.run(as_dict=True)

	for p in pages_with_standard_roles:
		if p.name not in has_role:
			has_role[p.name] = {"modified": p.modified, "title": p.title}
			if parent == "Report":
				has_role[p.name].update({"ref_pagetype": p.ref_pagetype})

	no_of_roles = SubQuery(
		jingrow.qb.from_(hasRole).select(Count("*")).where(hasRole.parent == parentTable.name)
	)

	# pages and reports with no role are allowed
	rows_with_no_roles = (
		jingrow.qb.from_(parentTable)
		.select(parentTable.name, parentTable.modified, *columns)
		.where(no_of_roles == 0)
	).run(as_dict=True)

	for r in rows_with_no_roles:
		if r.name not in has_role:
			has_role[r.name] = {"modified": r.modified, "title": r.title}
			if is_report:
				has_role[r.name] |= {"ref_pagetype": r.ref_pagetype}

	if is_report:
		if not has_permission("Report", raise_exception=False):
			return {}

		reports = jingrow.get_list(
			"Report",
			fields=["name", "report_type"],
			filters={"name": ("in", has_role.keys())},
			ignore_ifnull=True,
		)
		for report in reports:
			has_role[report.name]["report_type"] = report.report_type

		non_permitted_reports = set(has_role.keys()) - {r.name for r in reports}
		for r in non_permitted_reports:
			has_role.pop(r, None)

	# Expire every six hours
	jingrow.cache.set_value("has_role:" + parent, has_role, jingrow.session.user, 21600)
	return has_role


def load_translations(bootinfo):
	from jingrow.translate import get_messages_for_boot

	bootinfo["lang"] = jingrow.lang
	bootinfo["__messages"] = get_messages_for_boot()


def get_user_info():
	# get info for current user
	user_info = jingrow._dict()
	add_user_info(jingrow.session.user, user_info)

	if jingrow.session.user == "Administrator" and user_info.Administrator.email:
		user_info[user_info.Administrator.email] = user_info.Administrator

	return user_info


def get_user(bootinfo):
	"""get user info"""
	bootinfo.user = jingrow.get_user().load_user()


def add_home_page(bootinfo, pgs):
	"""load home page"""
	if jingrow.session.user == "Guest":
		return
	home_page = jingrow.db.get_default("desktop:home_page")

	if home_page == "setup-wizard":
		bootinfo.setup_wizard_requires = jingrow.get_hooks("setup_wizard_requires")

	try:
		page = jingrow.desk.desk_page.get(home_page)
		pgs.append(page)
		bootinfo["home_page"] = page.name
	except (jingrow.DoesNotExistError, jingrow.PermissionError):
		jingrow.clear_last_message()
		bootinfo["home_page"] = "Workspaces"


def add_timezone_info(bootinfo):
	system = bootinfo.sysdefaults.get("time_zone")
	import jingrow.utils.momentjs

	bootinfo.timezone_info = {"zones": {}, "rules": {}, "links": {}}
	jingrow.utils.momentjs.update(system, bootinfo.timezone_info)


def load_print(bootinfo, doclist):
	print_settings = jingrow.db.get_singles_dict("Print Settings")
	print_settings.pagetype = ":Print Settings"
	doclist.append(print_settings)
	load_print_css(bootinfo, print_settings)


def load_print_css(bootinfo, print_settings):
	import jingrow.www.printview

	bootinfo.print_css = jingrow.www.printview.get_print_style(
		print_settings.print_style or "Redesign", for_legacy=True
	)


def get_unseen_notes():
	note = PageType("Note")
	nsb = PageType("Note Seen By").as_("nsb")

	return (
		jingrow.qb.from_(note)
		.select(note.name, note.title, note.content, note.notify_on_every_login)
		.where(
			(note.notify_on_login == 1)
			& (note.expire_notification_on > jingrow.utils.now())
			& (
				ParameterizedValueWrapper(jingrow.session.user).notin(
					SubQuery(jingrow.qb.from_(nsb).select(nsb.user).where(nsb.parent == note.name))
				)
			)
		)
	).run(as_dict=1)


def get_success_action():
	return jingrow.get_all("Success Action", fields=["*"])


def get_link_preview_pagetypes():
	from jingrow.utils import cint

	link_preview_pagetypes = [d.name for d in jingrow.get_all("PageType", {"show_preview_popup": 1})]
	customizations = jingrow.get_all(
		"Property Setter", fields=["pg_type", "value"], filters={"property": "show_preview_popup"}
	)

	for custom in customizations:
		if not cint(custom.value) and custom.pg_type in link_preview_pagetypes:
			link_preview_pagetypes.remove(custom.pg_type)
		else:
			link_preview_pagetypes.append(custom.pg_type)

	return link_preview_pagetypes


def get_additional_filters_from_hooks():
	filter_config = jingrow._dict()
	filter_hooks = jingrow.get_hooks("filters_config")
	for hook in filter_hooks:
		filter_config.update(jingrow.get_attr(hook)())

	return filter_config


def add_layouts(bootinfo):
	# add routes for readable pagetypes
	bootinfo.pagetype_layouts = jingrow.get_all("PageType Layout", ["name", "route", "document_type"])


def get_desk_settings():
	from jingrow.core.pagetype.user.user import desk_properties

	return jingrow.get_value("User", jingrow.session.user, desk_properties, as_dict=True)


def get_notification_settings():
	return jingrow.get_cached_pg("Notification Settings", jingrow.session.user)


def get_link_title_pagetypes():
	dts = jingrow.get_all("PageType", {"show_title_field_in_link": 1})
	custom_dts = jingrow.get_all(
		"Property Setter",
		{"property": "show_title_field_in_link", "value": "1"},
		["pg_type as name"],
	)
	return [d.name for d in dts + custom_dts if d]


def set_time_zone(bootinfo):
	bootinfo.time_zone = {
		"system": get_system_timezone(),
		"user": bootinfo.get("user_info", {}).get(jingrow.session.user, {}).get("time_zone", None)
		or get_system_timezone(),
	}


def load_country_pg(bootinfo):
	country = jingrow.db.get_default("country")
	if not country:
		return
	try:
		bootinfo.pgs.append(jingrow.get_cached_pg("Country", country))
	except Exception:
		pass


def load_currency_pgs(bootinfo):
	currency = jingrow.qb.PageType("Currency")

	currency_pgs = (
		jingrow.qb.from_(currency)
		.select(
			currency.name,
			currency.fraction,
			currency.fraction_units,
			currency.number_format,
			currency.smallest_currency_fraction_value,
			currency.symbol,
			currency.symbol_on_right,
		)
		.where(currency.enabled == 1)
		.run(as_dict=1, update={"pagetype": ":Currency"})
	)

	bootinfo.pgs += currency_pgs


def get_marketplace_apps():
	import requests

	apps = []
	cache_key = "jingrow_marketplace_apps"

	if jingrow.conf.developer_mode or not on_jingrowcloud():
		return apps

	def get_apps_from_fc():
		remote_site = jingrow.conf.jingrowcloud_url or "jcloud.jingrow.com"
		request_url = f"https://{remote_site}/api/action/jcloud.api.marketplace.get_marketplace_apps"
		request = requests.get(request_url, timeout=2.0)
		return request.json()["message"]

	try:
		apps = jingrow.cache().get_value(cache_key, get_apps_from_fc, shared=True)
		installed_apps = set(jingrow.get_installed_apps())
		apps = [app for app in apps if app["name"] not in installed_apps]
	except Exception:
		# Don't retry for a day
		jingrow.cache().set_value(cache_key, apps, shared=True, expires_in_sec=24 * 60 * 60)

	return apps


def add_subscription_conf():
	try:
		return jingrow.conf.subscription
	except Exception:
		return ""


def get_sentry_dsn():
	if not jingrow.get_system_settings("enable_telemetry"):
		return

	return os.getenv("JINGROW_SENTRY_DSN")
