# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt

# For license information, please see license.txt


import frappe
from frappe import _
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.model.document import Document
from frappe.utils import cint

from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
	get_accounting_dimensions,
)
from erpnext.accounts.utils import sync_auto_reconcile_config

SELLING_DOCTYPES = [
	"Sales Invoice",
	"Sales Order",
	"Delivery Note",
	"Quotation",
	"Sales Invoice Item",
	"Sales Order Item",
	"Delivery Note Item",
	"Quotation Item",
	"POS Invoice",
	"POS Invoice Item",
]

BUYING_DOCTYPES = [
	"Purchase Invoice",
	"Purchase Order",
	"Purchase Receipt",
	"Purchase Invoice Item",
	"Purchase Order Item",
	"Purchase Receipt Item",
]


class AccountsSettings(Document):
	# 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 frappe.types import DF

		from erpnext.accounts.doctype.repost_allowed_types.repost_allowed_types import RepostAllowedTypes

		add_taxes_from_item_tax_template: DF.Check
		add_taxes_from_taxes_and_charges_template: DF.Check
		allow_multi_currency_invoices_against_single_party_account: DF.Check
		allow_pegged_currencies_exchange_rates: DF.Check
		allow_stale: DF.Check
		auto_reconcile_payments: DF.Check
		auto_reconciliation_job_trigger: DF.Int
		automatically_fetch_payment_terms: DF.Check
		automatically_process_deferred_accounting_entry: DF.Check
		automatically_run_rules_on_unreconciled_transactions: DF.Check
		book_asset_depreciation_entry_automatically: DF.Check
		book_deferred_entries_based_on: DF.Literal["Days", "Months"]
		book_deferred_entries_via_journal_entry: DF.Check
		book_stock_expense_gl_entries: DF.Check
		book_tax_discount_loss: DF.Check
		calculate_depr_using_total_days: DF.Check
		check_supplier_invoice_uniqueness: DF.Check
		confirm_before_resetting_posting_date: DF.Check
		create_pr_in_draft_status: DF.Check
		credit_controller: DF.Link | None
		default_ageing_range: DF.Data | None
		delete_linked_ledger_entries: DF.Check
		determine_address_tax_category_from: DF.Literal["Billing Address", "Shipping Address"]
		disable_include_dimensions: DF.Check
		enable_accounting_dimensions: DF.Check
		enable_common_party_accounting: DF.Check
		enable_discounts_and_margin: DF.Check
		enable_fuzzy_matching: DF.Check
		enable_immutable_ledger: DF.Check
		enable_loyalty_point_program: DF.Check
		enable_overdue_billing_threshold: DF.Check
		enable_party_matching: DF.Check
		enable_subscription: DF.Check
		exchange_gain_loss_posting_date: DF.Literal["Invoice", "Payment", "Reconciliation Date"]
		fetch_payment_schedule_in_payment_request: DF.Check
		fetch_valuation_rate_for_internal_transaction: DF.Check
		general_ledger_remarks_length: DF.Int
		ignore_account_closing_balance: DF.Check
		ignore_is_opening_check_for_reporting: DF.Check
		maintain_same_internal_transaction_rate: DF.Check
		maintain_same_rate_action: DF.Literal["Stop", "Warn"]
		make_payment_via_journal_entry: DF.Check
		merge_similar_account_heads: DF.Check
		over_billing_allowance: DF.Currency
		pcv_job_timeout: DF.Int
		receivable_payable_fetch_method: DF.Literal["Buffered Cursor", "UnBuffered Cursor"]
		receivable_payable_remarks_length: DF.Int
		reconciliation_queue_size: DF.Int
		repost_allowed_types: DF.Table[RepostAllowedTypes]
		role_allowed_to_bypass_overdue_billing: DF.Link | None
		role_allowed_to_over_bill: DF.Link | None
		role_to_notify_on_depreciation_failure: DF.Link | None
		role_to_override_stop_action: DF.Link | None
		round_row_wise_tax: DF.Check
		show_balance_in_coa: DF.Check
		show_inclusive_tax_in_print: DF.Check
		show_payment_schedule_in_print: DF.Check
		show_taxes_as_table_in_print: DF.Check
		stale_days: DF.Int
		submit_journal_entries: DF.Check
		transfer_match_days: DF.Int
		unlink_advance_payment_on_cancelation_of_order: DF.Check
		unlink_payment_on_cancellation_of_invoice: DF.Check
		use_legacy_budget_controller: DF.Check
		use_legacy_controller_for_pcv: DF.Check
	# end: auto-generated types

	def validate(self):
		self.validate_auto_tax_settings()
		old_doc = self.get_doc_before_save()
		clear_cache = False

		if old_doc.add_taxes_from_item_tax_template != self.add_taxes_from_item_tax_template:
			frappe.db.set_default(
				"add_taxes_from_item_tax_template", self.get("add_taxes_from_item_tax_template", 0)
			)
			clear_cache = True

		if old_doc.enable_common_party_accounting != self.enable_common_party_accounting:
			frappe.db.set_default(
				"enable_common_party_accounting", self.get("enable_common_party_accounting", 0)
			)
			clear_cache = True

		self.validate_stale_days()

		if old_doc.show_payment_schedule_in_print != self.show_payment_schedule_in_print:
			self.enable_payment_schedule_in_print()

		if old_doc.enable_accounting_dimensions != self.enable_accounting_dimensions:
			toggle_accounting_dimension_sections(not self.enable_accounting_dimensions)
			clear_cache = True

		if old_doc.enable_discounts_and_margin != self.enable_discounts_and_margin:
			toggle_sales_discount_section(not self.enable_discounts_and_margin)
			clear_cache = True

		if old_doc.enable_loyalty_point_program != self.enable_loyalty_point_program:
			toggle_loyalty_point_program_section(not self.enable_loyalty_point_program)
			clear_cache = True

		if old_doc.enable_subscription != self.enable_subscription:
			toggle_subscription_sections(not self.enable_subscription)
			clear_cache = True

		if old_doc.enable_overdue_billing_threshold != self.enable_overdue_billing_threshold:
			toggle_overdue_billing_threshold_field(not self.enable_overdue_billing_threshold)
			clear_cache = True

		if clear_cache:
			frappe.clear_cache()

		self.validate_and_sync_auto_reconcile_config()
		self.update_property_for_accounting_dimension()

	def validate_stale_days(self):
		if not self.allow_stale and cint(self.stale_days) <= 0:
			frappe.msgprint(
				_("Stale Days should start from 1."), title="Error", indicator="red", raise_exception=1
			)

	def enable_payment_schedule_in_print(self):
		show_in_print = cint(self.show_payment_schedule_in_print)
		for doctype in ("Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"):
			make_property_setter(
				doctype, "due_date", "print_hide", show_in_print, "Check", validate_fields_for_doctype=False
			)
			make_property_setter(
				doctype,
				"payment_schedule",
				"print_hide",
				0 if show_in_print else 1,
				"Check",
				validate_fields_for_doctype=False,
			)

	def validate_and_sync_auto_reconcile_config(self):
		if self.has_value_changed("auto_reconciliation_job_trigger"):
			if (
				cint(self.auto_reconciliation_job_trigger) > 0
				and cint(self.auto_reconciliation_job_trigger) < 60
			):
				sync_auto_reconcile_config(self.auto_reconciliation_job_trigger)
			else:
				frappe.throw(_("Cron Interval should be between 1 and 59 Min"))

		if self.has_value_changed("reconciliation_queue_size"):
			if cint(self.reconciliation_queue_size) < 5 or cint(self.reconciliation_queue_size) > 100:
				frappe.throw(_("Queue Size should be between 5 and 100"))

	def validate_auto_tax_settings(self):
		if self.add_taxes_from_item_tax_template and self.add_taxes_from_taxes_and_charges_template:
			frappe.throw(
				_("You cannot enable both the settings '{0}' and '{1}'.").format(
					frappe.bold(_(self.meta.get_label("add_taxes_from_item_tax_template"))),
					frappe.bold(_(self.meta.get_label("add_taxes_from_taxes_and_charges_template"))),
				),
				title=_("Auto Tax Settings Error"),
			)

	def update_property_for_accounting_dimension(self):
		doctypes = [entry.document_type for entry in self.repost_allowed_types]
		if not doctypes:
			return

		from erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger import get_child_docs

		doctypes += get_child_docs(doctypes)

		set_allow_on_submit_for_dimension_fields(doctypes)


def toggle_accounting_dimension_sections(hide):
	accounting_dimension_doctypes = frappe.get_hooks("accounting_dimension_doctypes")
	for doctype in accounting_dimension_doctypes:
		create_property_setter_for_hiding_field(doctype, "accounting_dimensions_section", hide)


def toggle_sales_discount_section(hide):
	for doctype in SELLING_DOCTYPES + BUYING_DOCTYPES:
		meta = frappe.get_meta(doctype)
		if meta.has_field("additional_discount_section"):
			create_property_setter_for_hiding_field(doctype, "additional_discount_section", hide)
		if meta.has_field("discount_and_margin"):
			create_property_setter_for_hiding_field(doctype, "discount_and_margin", hide)


def toggle_loyalty_point_program_section(hide):
	for doctype in SELLING_DOCTYPES:
		meta = frappe.get_meta(doctype)
		if meta.has_field("loyalty_points_redemption"):
			create_property_setter_for_hiding_field(doctype, "loyalty_points_redemption", hide)


def toggle_subscription_sections(hide):
	subscription_doctypes = frappe.get_hooks("subscription_doctypes")
	for doctype in subscription_doctypes:
		create_property_setter_for_hiding_field(doctype, "subscription_section", hide)


def toggle_overdue_billing_threshold_field(hide):
	create_property_setter_for_hiding_field("Customer Credit Limit", "overdue_billing_threshold", hide)


def create_property_setter_for_hiding_field(doctype, field_name, hide):
	make_property_setter(
		doctype,
		field_name,
		"hidden",
		hide,
		"Check",
		validate_fields_for_doctype=False,
	)


def set_allow_on_submit_for_dimension_fields(doctypes):
	for dt in doctypes:
		meta = frappe.get_meta(dt)
		for dimension in get_accounting_dimensions():
			df = meta.get_field(dimension)
			if df and not df.allow_on_submit:
				frappe.db.set_value("Custom Field", dt + "-" + dimension, "allow_on_submit", 1)
