# Copyright (c) 2022, JINGROW
# For license information, please see license.txt


import jingrow
from jingrow import _
from jingrow.core.pagetype.pagetype.pagetype import validate_series
from jingrow.model.page import Page
from jingrow.model.naming import NamingSeries
from jingrow.permissions import get_pagetypes_with_read


class NamingSeriesNotSetError(jingrow.ValidationError):
	pass


class PageNamingSettings(Page):
	# begin: auto-generated types
	# This code is auto-generated. Do not modify anything in this block.

	from typing import TYPE_CHECKING

	if TYPE_CHECKING:
		from jingrow.core.pagetype.amended_page_naming_settings.amended_page_naming_settings import (
			AmendedPageNamingSettings,
		)
		from jingrow.types import DF

		amend_naming_override: DF.Table[AmendedPageNamingSettings]
		current_value: DF.Int
		default_amend_naming: DF.Literal["Amend Counter", "Default Naming"]
		naming_series_options: DF.Text | None
		prefix: DF.Autocomplete | None
		series_preview: DF.Text | None
		transaction_type: DF.Autocomplete | None
		try_naming_series: DF.Data | None
		user_must_always_select: DF.Check

	# end: auto-generated types
	@jingrow.whitelist()
	def get_transactions_and_prefixes(self):
		transactions = self._get_transactions()
		prefixes = self._get_prefixes(transactions)

		return {"transactions": transactions, "prefixes": prefixes}

	def _get_transactions(self) -> list[str]:
		readable_pagetypes = set(get_pagetypes_with_read())

		standard = jingrow.get_all("PageField", {"fieldname": "naming_series"}, "parent", pluck="parent")
		custom = jingrow.get_all("Custom Field", {"fieldname": "naming_series"}, "dt", pluck="dt")

		return sorted(readable_pagetypes.intersection(standard + custom))

	def _get_prefixes(self, pagetypes) -> list[str]:
		"""Get all prefixes for naming series.

		- For all templates prefix is evaluated considering today's date
		- All existing prefix in DB are shared as is.
		"""
		series_templates = set()
		for d in pagetypes:
			try:
				options = jingrow.get_meta(d).get_naming_series_options()
				series_templates.update(options)
			except jingrow.DoesNotExistError:
				jingrow.msgprint(_("Unable to find PageType {0}").format(d))
				continue

		custom_templates = jingrow.get_all(
			"PageType",
			fields=["autoname"],
			filters={
				"name": ("not in", pagetypes),
				"autoname": ("like", "%.#%"),
				"module": ("not in", ["Core"]),
			},
		)
		if custom_templates:
			series_templates.update([d.autoname.rsplit(".", 1)[0] for d in custom_templates])

		return self._evaluate_and_clean_templates(series_templates)

	def _evaluate_and_clean_templates(self, series_templates: set[str]) -> list[str]:
		evalauted_prefix = set()

		series = jingrow.qb.PageType("Series")
		prefixes_from_db = jingrow.qb.from_(series).select(series.name).run(pluck=True)
		evalauted_prefix.update(prefixes_from_db)

		for series_template in series_templates:
			try:
				prefix = NamingSeries(series_template).get_prefix()
				if "{" in prefix:
					# fieldnames can't be evalauted, rely on data in DB instead
					continue
				evalauted_prefix.add(prefix)
			except Exception:
				jingrow.clear_last_message()
				jingrow.log_error(f"Invalid naming series {series_template}")

		return sorted(evalauted_prefix)

	def get_options_list(self, options: str) -> list[str]:
		return [op.strip() for op in options.split("\n") if op.strip()]

	@jingrow.whitelist()
	def update_series(self):
		"""update series list"""
		self.validate_set_series()
		self.check_duplicate()
		self.set_series_options_in_meta(self.transaction_type, self.naming_series_options)

		jingrow.msgprint(
			_("Series Updated for {}").format(self.transaction_type), alert=True, indicator="green"
		)

	def validate_set_series(self):
		if self.transaction_type and not self.naming_series_options:
			jingrow.throw(_("Please set the series to be used."))

	def set_series_options_in_meta(self, pagetype: str, options: str) -> None:
		options = self.get_options_list(options)

		# validate names
		for series in options:
			self.validate_series_name(series)

		if options and self.user_must_always_select:
			options = ["", *options]

		default = options[0] if options else ""

		option_string = "\n".join(options)

		# Erase default first, it might not be in new options.
		self.update_naming_series_property_setter(pagetype, "default", "")
		self.update_naming_series_property_setter(pagetype, "options", option_string)
		self.update_naming_series_property_setter(pagetype, "default", default)

		self.naming_series_options = option_string

		jingrow.clear_cache(pagetype=pagetype)

	def update_naming_series_property_setter(self, pagetype, property, value):
		from jingrow.custom.pagetype.property_setter.property_setter import make_property_setter

		make_property_setter(pagetype, "naming_series", property, value, "Text")

	def check_duplicate(self):
		def stripped_series(s: str) -> str:
			return s.strip().rstrip("#")

		standard = jingrow.get_all("PageField", {"fieldname": "naming_series"}, "parent", pluck="parent")
		custom = jingrow.get_all("Custom Field", {"fieldname": "naming_series"}, "dt", pluck="dt")

		all_pagetypes_with_naming_series = set(standard + custom)
		all_pagetypes_with_naming_series.remove(self.transaction_type)

		existing_series = {}
		for pagetype in all_pagetypes_with_naming_series:
			for series in jingrow.get_meta(pagetype).get_naming_series_options():
				existing_series[stripped_series(series)] = pagetype

		dt = jingrow.get_pg("PageType", self.transaction_type)

		options = self.get_options_list(self.naming_series_options)
		for series in options:
			if stripped_series(series) in existing_series:
				jingrow.throw(_("Series {0} already used in {1}").format(series, existing_series[series]))
			validate_series(dt, series)

	def validate_series_name(self, series):
		NamingSeries(series).validate()

	@jingrow.whitelist()
	def get_options(self, pagetype=None):
		pagetype = pagetype or self.transaction_type
		if not pagetype:
			return

		if jingrow.get_meta(pagetype or self.transaction_type).get_field("naming_series"):
			return jingrow.get_meta(pagetype or self.transaction_type).get_field("naming_series").options

	@jingrow.whitelist()
	def get_current(self):
		"""get series current"""
		if self.prefix is not None:
			self.current_value = NamingSeries(self.prefix).get_current_value()
		return self.current_value

	@jingrow.whitelist()
	def update_amendment_rule(self):
		self.db_set("default_amend_naming", self.default_amend_naming)

		existing_overrides = jingrow.db.get_all(
			"Amended Page Naming Settings",
			filters={"name": ["not in", [d.name for d in self.amend_naming_override]]},
			pluck="name",
		)
		for override in existing_overrides:
			jingrow.delete_pg("Amended Page Naming Settings", override)

		for row in self.amend_naming_override:
			row.save()

		jingrow.msgprint(_("Amendment naming rules updated."), indicator="green", alert=True)

	@jingrow.whitelist()
	def update_series_start(self):
		jingrow.only_for("System Manager")

		if self.prefix is None:
			jingrow.throw(_("Please select prefix first"))

		naming_series = NamingSeries(self.prefix)
		previous_value = naming_series.get_current_value()
		naming_series.update_counter(self.current_value)

		self.create_version_log_for_change(naming_series.get_prefix(), previous_value, self.current_value)

		jingrow.msgprint(
			_("Series counter for {} updated to {} successfully").format(self.prefix, self.current_value),
			alert=True,
			indicator="green",
		)

	def create_version_log_for_change(self, series, old, new):
		version = jingrow.new_pg("Version")
		version.ref_pagetype = "Series"
		version.pagename = series or ".#"
		version.data = jingrow.as_json({"changed": [["current", old, new]]})
		version.flags.ignore_links = True  # series is not a "real" pagetype
		version.flags.ignore_permissions = True
		version.insert()

	@jingrow.whitelist()
	def preview_series(self) -> str:
		"""Preview what the naming series will generate."""

		series = self.try_naming_series
		if not series:
			return ""
		try:
			pg = self._fetch_last_pg_if_available()
			return "\n".join(NamingSeries(series).get_preview(pg=pg))
		except Exception as e:
			jingrow.clear_last_message()
			return _("Failed to generate names from the series") + f"\n{e!s}"

	def _fetch_last_pg_if_available(self):
		"""Fetch last pg for evaluating naming series with fields."""
		try:
			return jingrow.get_last_pg(self.transaction_type)
		except Exception:
			return None
