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


import frappe
import frappe.defaults
from frappe import _, msgprint
from frappe.contacts.address_and_contact import (
	delete_contact_and_address,
	load_address_and_contact,
)
from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options
from frappe.utils import get_link_to_form, getdate, nowdate

from erpnext.accounts.party import (
	get_dashboard_info,
	validate_party_accounts,
	validate_party_currency_before_merging,
)
from erpnext.controllers.website_list_for_contact import (
	add_role_for_portal_user,
	link_portal_users_to_contacts,
)
from erpnext.utilities.transaction_base import TransactionBase


class Supplier(TransactionBase):
	# 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.allowed_to_transact_with.allowed_to_transact_with import (
			AllowedToTransactWith,
		)
		from erpnext.accounts.doctype.party_account.party_account import PartyAccount
		from erpnext.utilities.doctype.portal_user.portal_user import PortalUser

		accounts: DF.Table[PartyAccount]
		allow_purchase_invoice_creation_without_purchase_order: DF.Check
		allow_purchase_invoice_creation_without_purchase_receipt: DF.Check
		companies: DF.Table[AllowedToTransactWith]
		country: DF.Link | None
		default_bank_account: DF.Link | None
		default_currency: DF.Link | None
		default_price_list: DF.Link | None
		disabled: DF.Check
		email_id: DF.ReadOnly | None
		hold_type: DF.Literal["", "All", "Invoices", "Payments"]
		image: DF.AttachImage | None
		is_frozen: DF.Check
		is_internal_supplier: DF.Check
		is_transporter: DF.Check
		language: DF.Link | None
		mobile_no: DF.ReadOnly | None
		naming_series: DF.Literal["SUP-.YYYY.-"]
		on_hold: DF.Check
		payment_terms: DF.Link | None
		portal_users: DF.Table[PortalUser]
		prevent_pos: DF.Check
		prevent_rfqs: DF.Check
		primary_address: DF.Text | None
		release_date: DF.Date | None
		represents_company: DF.Link | None
		supplier_details: DF.Text | None
		supplier_group: DF.Link | None
		supplier_name: DF.Data
		supplier_primary_address: DF.Link | None
		supplier_primary_contact: DF.Link | None
		supplier_type: DF.Literal["Company", "Individual", "Partnership"]
		tax_category: DF.Link | None
		tax_id: DF.Data | None
		tax_withholding_category: DF.Link | None
		warn_pos: DF.Check
		warn_rfqs: DF.Check
		website: DF.Data | None
	# end: auto-generated types

	def onload(self):
		"""Load address and contacts in `__onload`"""
		load_address_and_contact(self)
		self.load_dashboard_info()

	def before_save(self):
		if not self.on_hold:
			self.hold_type = ""
			self.release_date = ""
		elif self.on_hold and not self.hold_type:
			self.hold_type = "All"

	def is_blocked_for(self, hold_type: str) -> bool:
		"""Whether transactions of `hold_type` ("Invoices", "Payments" or "All") are on hold today."""
		return bool(
			self.on_hold
			and self.hold_type in ("All", hold_type)
			and (not self.release_date or getdate(nowdate()) < getdate(self.release_date))
		)

	def load_dashboard_info(self):
		info = get_dashboard_info(self.doctype, self.name)
		self.set_onload("dashboard_info", info)

	def autoname(self):
		supp_master_name = frappe.defaults.get_global_default("supp_master_name")
		if supp_master_name == "Supplier Name":
			self.name = self.supplier_name
		elif supp_master_name == "Naming Series":
			set_name_by_naming_series(self)
		else:
			set_name_from_naming_options(frappe.get_meta(self.doctype).autoname, self)

	def on_update(self):
		self.create_primary_contact()
		self.create_primary_address()
		link_portal_users_to_contacts(self)

	def add_role_for_user(self):
		for portal_user in self.portal_users:
			add_role_for_portal_user(portal_user, "Supplier")

	def _add_supplier_role(self, portal_user):
		if not portal_user.is_new():
			return

		user_doc = frappe.get_doc("User", portal_user.user)
		roles = {r.role for r in user_doc.roles}

		if "Supplier" in roles:
			return

		if "System Manager" not in frappe.get_roles():
			frappe.msgprint(
				_("Please add 'Supplier' role to user {0}.").format(portal_user.user),
				alert=True,
			)
			return

		user_doc.add_roles("Supplier")
		frappe.msgprint(_("Added Supplier Role to User {0}.").format(frappe.bold(user_doc.name)), alert=True)

	def validate(self):
		self.flags.is_new_doc = self.is_new()

		# validation for Naming Series mandatory field...
		if frappe.defaults.get_global_default("supp_master_name") == "Naming Series":
			if not self.naming_series:
				msgprint(_("Series is mandatory"), raise_exception=1)

		validate_party_accounts(self)
		self.validate_internal_supplier()
		self.validate_primary_contact_and_address()
		self.add_role_for_user()
		self.validate_currency_for_receivable_payable_and_advance_account()

	def validate_primary_contact_and_address(self):
		for fieldname, link_doctype in (
			("supplier_primary_contact", "Contact"),
			("supplier_primary_address", "Address"),
		):
			name = self.get(fieldname)
			if not name or not self.has_value_changed(fieldname):
				continue

			if not frappe.db.exists(
				"Dynamic Link",
				{
					"parenttype": link_doctype,
					"parent": name,
					"link_doctype": "Supplier",
					"link_name": self.name,
				},
			):
				frappe.throw(
					_("{0} {1} is not linked to Supplier {2}").format(
						_(link_doctype), frappe.bold(name), frappe.bold(self.name)
					)
				)

	@frappe.whitelist()
	def get_supplier_group_details(self):
		doc = frappe.get_doc("Supplier Group", self.supplier_group)
		self.payment_terms = ""
		self.accounts = []

		if doc.accounts:
			for account in doc.accounts:
				child = self.append("accounts")
				child.company = account.company
				child.account = account.account

		if doc.payment_terms:
			self.payment_terms = doc.payment_terms

		self.save()

	def validate_internal_supplier(self):
		if not self.is_internal_supplier:
			self.represents_company = ""

		if self.disabled:
			return

		internal_supplier = frappe.db.get_value(
			"Supplier",
			{
				"is_internal_supplier": 1,
				"represents_company": self.represents_company,
				"disabled": 0,
				"name": ("!=", self.name),
			},
			"name",
		)

		if internal_supplier:
			internal_supplier_link = get_link_to_form("Supplier", internal_supplier)
			frappe.throw(
				_(
					"Internal Supplier {0} already exists for {1}. Disable it to make this Supplier internal."
				).format(
					internal_supplier_link,
					frappe.bold(self.represents_company),
				),
				title=_("Internal Supplier Already Exists"),
			)

	def create_primary_contact(self):
		from erpnext.selling.doctype.customer.customer import make_contact

		if not self.supplier_primary_contact:
			if self.mobile_no or self.email_id:
				contact = make_contact(self)
				self.db_set("supplier_primary_contact", contact.name)
				self.db_set("mobile_no", self.mobile_no)
				self.db_set("email_id", self.email_id)

	def create_primary_address(self):
		from frappe.contacts.doctype.address.address import get_address_display

		from erpnext.selling.doctype.customer.customer import make_address

		if self.flags.is_new_doc and self.get("address_line1"):
			address = make_address(self)
			address_display = get_address_display(address.name)

			self.db_set("supplier_primary_address", address.name)
			self.db_set("primary_address", address_display)

	def on_trash(self):
		if self.supplier_primary_contact:
			self.db_set("supplier_primary_contact", None)
		if self.supplier_primary_address:
			self.db_set("supplier_primary_address", None)

		delete_contact_and_address("Supplier", self.name)

	def before_rename(self, olddn, newdn, merge=False):
		if merge:
			validate_party_currency_before_merging("Supplier", olddn, newdn)

	def after_rename(self, olddn, newdn, merge=False):
		if frappe.defaults.get_global_default("supp_master_name") == "Supplier Name":
			self.db_set("supplier_name", newdn)


@frappe.whitelist()
@frappe.validate_and_sanitize_search_inputs
def get_supplier_primary(doctype, txt, searchfield, start, page_len, filters):
	supplier = filters.get("supplier")
	type = filters.get("type")

	# `type` is caller-supplied and was interpolated into qb.DocType(), so any doctype could be
	# joined to Dynamic Link and read. The two pickers send only these values.
	if type not in ("Contact", "Address"):
		frappe.throw(_("Invalid type"), frappe.PermissionError)

	# authorise the party, not Contact/Address: the `if_owner` row on Address would empty the picker rather than error
	frappe.has_permission("Supplier", doc=supplier, throw=True)

	type_doctype = frappe.qb.DocType(type)
	dynamic_link = frappe.qb.DocType("Dynamic Link")

	query = (
		frappe.qb.from_(type_doctype)
		.join(dynamic_link)
		.on(type_doctype.name == dynamic_link.parent)
		.select(type_doctype.name)
		.where(
			(dynamic_link.link_name == supplier)
			& (dynamic_link.link_doctype == "Supplier")
			& (type_doctype.name.like(f"%{txt}%"))
		)
	)

	if type == "Contact":
		query = query.select(type_doctype.email_id)

	return query.run()
