import frappe
from frappe.tests.utils import FrappeTestCase
from frappe.utils import flt, getdate, today

from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
from erpnext.accounts.report.sales_register.sales_register import execute
from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order


class TestItemWiseSalesRegister(AccountsTestMixin, FrappeTestCase):
	def setUp(self):
		self.create_company()
		self.create_customer()
		self.create_item()
		self.create_child_cost_center()

	def tearDown(self):
		frappe.db.rollback()

	def create_child_cost_center(self):
		cc_name = "South Wing"
		if frappe.db.exists("Cost Center", cc_name):
			cc = frappe.get_doc("Cost Center", cc_name)
		else:
			parent = frappe.db.get_value("Cost Center", self.cost_center, "parent_cost_center")
			cc = frappe.get_doc(
				{
					"doctype": "Cost Center",
					"company": self.company,
					"is_group": False,
					"parent_cost_center": parent,
					"cost_center_name": cc_name,
				}
			)
			cc = cc.save()
		self.south_cc = cc.name

	def create_sales_invoice(self, rate=100, do_not_submit=False):
		si = create_sales_invoice(
			item=self.item,
			company=self.company,
			customer=self.customer,
			debit_to=self.debit_to,
			posting_date=today(),
			parent_cost_center=self.cost_center,
			cost_center=self.cost_center,
			rate=rate,
			price_list_rate=rate,
			do_not_save=1,
		)
		si = si.save()
		if not do_not_submit:
			si = si.submit()
		return si

	def test_ledger_view_nets_pos_paid_invoice(self):
		# A POS payment settles the receivable inside the invoice, so the ledger view must credit it
		# and net to zero instead of showing a phantom outstanding.
		make_pos_profile()
		si = create_sales_invoice(
			item=self.item,
			company=self.company,
			customer=self.customer,
			debit_to=self.debit_to,
			posting_date=today(),
			parent_cost_center=self.cost_center,
			cost_center=self.cost_center,
			rate=100,
			price_list_rate=100,
			do_not_save=1,
		)
		si.is_pos = 1
		si.append("payments", {"mode_of_payment": "Cash", "amount": 100})
		si = si.save().submit()
		self.assertEqual(flt(si.outstanding_amount), 0.0)

		filters = frappe._dict(
			{
				"from_date": today(),
				"to_date": today(),
				"company": self.company,
				"include_payments": True,
				"customer": self.customer,
			}
		)
		rows = execute(filters)[1]
		inv_row = next(x for x in rows if x.get("voucher_no") == si.name)

		self.assertEqual(flt(inv_row.get("debit")), 100.0)
		self.assertEqual(flt(inv_row.get("credit")), 100.0)

		# running balance is unchanged by a fully-paid POS invoice
		idx = rows.index(inv_row)
		self.assertEqual(flt(inv_row.get("balance")), flt(rows[idx - 1].get("balance")))

	def test_basic_report_output(self):
		si = self.create_sales_invoice(rate=98)

		filters = frappe._dict({"from_date": today(), "to_date": today(), "company": self.company})
		report = execute(filters)

		res = [x for x in report[1] if x.get("voucher_no") == si.name]

		expected_result = {
			"voucher_type": si.doctype,
			"voucher_no": si.name,
			"posting_date": getdate(),
			"customer": self.customer,
			"receivable_account": self.debit_to,
			"net_total": 98.0,
			"grand_total": 98.0,
			"debit": 98.0,
		}

		report_output = {k: v for k, v in res[0].items() if k in expected_result}
		self.assertDictEqual(report_output, expected_result)

	def test_sales_register_ignores_tax_rows_from_other_doctype(self):
		si = self.create_sales_invoice(rate=98)

		# Real workflow setup: create a Sales Order with taxes in the shared child table.
		so = make_sales_order(
			item=self.item,
			company=self.company,
			customer=self.customer,
			rate=77,
			do_not_save=1,
			do_not_submit=1,
		)
		so.append(
			"taxes",
			{
				"charge_type": "Actual",
				"account_head": self.income_account,
				"description": "SO Tax",
				"tax_amount": 55.0,
			},
		)
		so.insert()
		so.submit()

		# Mimic custom naming collision across doctypes (same parent value in shared child table).
		frappe.rename_doc("Sales Order", so.name, si.name, force=True)

		filters = frappe._dict({"from_date": today(), "to_date": today(), "company": self.company})
		report = execute(filters)

		res = [x for x in report[1] if x.get("voucher_no") == si.name]
		self.assertEqual(len(res), 1)
		result = frappe._dict(res[0])
		self.assertEqual(result.net_total, 98.0)
		self.assertEqual(result.tax_total, 0)
		self.assertEqual(result.grand_total, 98.0)

	def test_journal_with_cost_center_filter(self):
		je1 = frappe.get_doc(
			{
				"doctype": "Journal Entry",
				"voucher_type": "Journal Entry",
				"company": self.company,
				"posting_date": getdate(),
				"accounts": [
					{
						"account": self.debit_to,
						"party_type": "Customer",
						"party": self.customer,
						"credit_in_account_currency": 77,
						"credit": 77,
						"is_advance": "Yes",
						"cost_center": self.cost_center,
					},
					{
						"account": self.cash,
						"debit_in_account_currency": 77,
						"debit": 77,
					},
				],
			}
		)
		je1.submit()

		je2 = frappe.get_doc(
			{
				"doctype": "Journal Entry",
				"voucher_type": "Journal Entry",
				"company": self.company,
				"posting_date": getdate(),
				"accounts": [
					{
						"account": self.debit_to,
						"party_type": "Customer",
						"party": self.customer,
						"credit_in_account_currency": 98,
						"credit": 98,
						"is_advance": "Yes",
						"cost_center": self.south_cc,
					},
					{
						"account": self.cash,
						"debit_in_account_currency": 98,
						"debit": 98,
					},
				],
			}
		)
		je2.submit()

		filters = frappe._dict(
			{
				"from_date": today(),
				"to_date": today(),
				"company": self.company,
				"include_payments": True,
				"customer": self.customer,
				"cost_center": self.cost_center,
			}
		)
		report_output = execute(filters)[1]
		filtered_output = [x for x in report_output if x.get("voucher_no") == je1.name]
		self.assertEqual(len(filtered_output), 1)
		expected_result = {
			"voucher_type": je1.doctype,
			"voucher_no": je1.name,
			"posting_date": je1.posting_date,
			"customer": self.customer,
			"receivable_account": self.debit_to,
			"net_total": 77.0,
			"credit": 77.0,
		}
		result_fields = {k: v for k, v in filtered_output[0].items() if k in expected_result}
		self.assertDictEqual(result_fields, expected_result)

		filters = frappe._dict(
			{
				"from_date": today(),
				"to_date": today(),
				"company": self.company,
				"include_payments": True,
				"customer": self.customer,
				"cost_center": self.south_cc,
			}
		)
		report_output = execute(filters)[1]
		filtered_output = [x for x in report_output if x.get("voucher_no") == je2.name]
		self.assertEqual(len(filtered_output), 1)
		expected_result = {
			"voucher_type": je2.doctype,
			"voucher_no": je2.name,
			"posting_date": je2.posting_date,
			"customer": self.customer,
			"receivable_account": self.debit_to,
			"net_total": 98.0,
			"credit": 98.0,
		}
		result_output = {k: v for k, v in filtered_output[0].items() if k in expected_result}
		self.assertDictEqual(result_output, expected_result)
