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


import json

import frappe
from frappe import _
from frappe.utils import cint, flt, getdate

from erpnext.stock.doctype.item.item import get_last_purchase_details, validate_end_of_life


def update_last_purchase_rate(doc, is_submit) -> None:
	"""updates last_purchase_rate in item table for each item"""

	if doc.get("is_internal_supplier") or frappe.db.get_single_value(
		"Buying Settings", "disable_last_purchase_rate"
	):
		return

	this_purchase_date = getdate(doc.get("posting_date") or doc.get("transaction_date"))

	for d in doc.get("items"):
		if d.get("is_free_item"):
			continue

		# get last purchase details
		last_purchase_details = get_last_purchase_details(d.item_code, doc.name)

		# compare last purchase date and this transaction's date
		last_purchase_rate = None
		if last_purchase_details and (
			doc.get("docstatus") == 2 or last_purchase_details.purchase_date > this_purchase_date
		):
			last_purchase_rate = last_purchase_details["base_net_rate"]
		elif is_submit == 1:
			# even if this transaction is the latest one, it should be submitted
			# for it to be considered for latest purchase rate
			if flt(d.conversion_factor):
				last_purchase_rate = flt(d.base_net_rate) / flt(d.conversion_factor)
			# Check if item code is present
			# Conversion factor should not be mandatory for non itemized items
			elif d.item_code:
				frappe.throw(_("UOM Conversion factor is required in row {0}").format(d.idx))

		# update last purchsae rate
		frappe.db.set_value(
			"Item", d.item_code, "last_purchase_rate", flt(last_purchase_rate), update_modified=False
		)


def validate_for_items(doc) -> None:
	for d in doc.get("items"):
		set_stock_levels(row=d)  # update with latest quantities
		item = validate_item_and_get_basic_data(row=d)
		validate_stock_item_warehouse(row=d, item=item)
		validate_end_of_life(d.item_code, item.end_of_life, item.disabled)

	validate_duplicate_items(doc)


def validate_duplicate_items(doc) -> None:
	rows = [
		(
			row.item_code,
			row.get("purchase_receipt"),
			row.get("purchase_order"),
			row.get("material_request"),
			row.get("supplier_quotation"),
		)
		for row in doc.get("items")
		if row.item_code
	]
	if len(rows) != len(set(rows)) and not cint(
		frappe.db.get_single_value("Buying Settings", "allow_multiple_items")
	):
		frappe.throw(_("Same item cannot be entered multiple times."))


def set_stock_levels(row) -> None:
	projected_qty = frappe.db.get_value(
		"Bin",
		{
			"item_code": row.item_code,
			"warehouse": row.warehouse,
		},
		"projected_qty",
	)

	qty_data = {
		"projected_qty": flt(projected_qty),
		"ordered_qty": 0,
		"received_qty": 0,
	}
	if row.doctype in ("Purchase Receipt Item", "Purchase Invoice Item"):
		qty_data.pop("received_qty")

	for field in qty_data:
		if row.meta.get_field(field):
			row.set(field, qty_data[field])


def validate_item_and_get_basic_data(row) -> dict:
	item = frappe.db.get_values(
		"Item",
		filters={"name": row.item_code},
		fieldname=["is_stock_item", "is_sub_contracted_item", "end_of_life", "disabled"],
		as_dict=1,
	)
	if not item:
		frappe.throw(_("Row #{0}: Item {1} does not exist").format(row.idx, frappe.bold(row.item_code)))

	return item[0]


def validate_stock_item_warehouse(row, item) -> None:
	if item.is_stock_item == 1 and row.qty and not row.warehouse and not row.get("delivered_by_supplier"):
		frappe.throw(
			_("Row #{1}: Warehouse is mandatory for stock Item {0}").format(
				frappe.bold(row.item_code), row.idx
			)
		)


def check_on_hold_or_closed_status(doctype, docname) -> None:
	status = frappe.db.get_value(doctype, docname, "status")

	if status in ("Closed", "On Hold"):
		frappe.throw(
			_("{0} {1} status is {2}.").format(
				frappe.bold(_(doctype)),
				frappe.bold(docname),
				frappe.bold(_(status)),
			),
			frappe.InvalidStatusError,
		)


@frappe.whitelist()
def get_linked_material_requests(items):
	"""
	Retrieve Material Requests linked to a list of items.
	"""

	try:
		items = frappe.parse_json(items)
	except (TypeError, ValueError):
		frappe.throw(_("Items must be a list of Item codes"))

	if isinstance(items, str):
		items = [items]

	if not isinstance(items, list | tuple) or any(not isinstance(item, str) for item in items):
		frappe.throw(_("Items must be a list of Item codes"))

	permitted_material_requests = frappe.get_list(
		"Material Request",
		filters=[
			["material_request_type", "=", "Purchase"],
			["docstatus", "=", 1],
			["status", "!=", "Stopped"],
			["per_ordered", "<", 99.99],
			["Material Request Item", "item_code", "in", items],
		],
		pluck="name",
		distinct=True,
	)

	if not permitted_material_requests:
		return []

	mr_list = []
	for item in items:
		material_request = frappe.db.sql(
			"""SELECT distinct mr.name AS mr_name,
				(mr_item.qty - mr_item.ordered_qty) AS qty,
				mr_item.item_code AS item_code,
				mr_item.name AS mr_item
			FROM `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
			WHERE mr.name = mr_item.parent
				AND mr.name IN %(permitted)s
				AND mr_item.item_code = %(item)s
				AND mr.material_request_type = 'Purchase'
				AND mr.per_ordered < 99.99
				AND mr.docstatus = 1
				AND mr.status != 'Stopped'
                        ORDER BY mr_item.item_code ASC""",
			{"item": item, "permitted": permitted_material_requests},
			as_dict=1,
		)
		if material_request:
			mr_list.extend(material_request)

	return mr_list
