import frappe
from frappe import _
from frappe.model.document import Document
from frappe.utils import now_datetime, getdate, flt, cint
from datetime import date
from calendar import monthrange

MONTH_NAMES = [
	"Yanvar", "Fevral", "Mart", "Aprel", "May", "İyun",
	"İyul", "Avqust", "Sentyabr", "Oktyabr", "Noyabr", "Dekabr"
]

QUARTER_NAMES = ["Q1", "Q2", "Q3", "Q4"]
QUARTER_MONTHS = {1: (1, 3), 2: (4, 6), 3: (7, 9), 4: (10, 12)}


class TaxYearClosingWizard(Document):

	def validate(self):
		if self.fiscal_year:
			fy = frappe.get_doc("Fiscal Year", self.fiscal_year)
			self.year = getdate(fy.year_start_date).year

	def before_submit(self):
		if self.overall_status != "Completed":
			frappe.throw(_("Complete all steps before submitting."))

	def on_cancel(self):
		self._cancel_all_step_entries()

	def _cancel_all_step_entries(self):
		for step in self.steps or []:
			for je_name in (step.journal_entries or "").split(","):
				je_name = je_name.strip()
				if je_name and frappe.db.exists("Journal Entry", je_name):
					je = frappe.get_doc("Journal Entry", je_name)
					if je.docstatus == 1:
						je.cancel()

	def add_log(self, phase, level, message):
		self.append("log_entries", {
			"timestamp": now_datetime(),
			"phase": phase,
			"level": level,
			"message": message,
		})

	# ── Step Generation ──────────────────────────────────────

	def generate_steps(self):
		"""Generate all timeline steps for the year based on tax regime."""
		self.steps = []
		self._detect_company()
		self._detect_benefits_for_year()

		year = cint(self.year)
		regime = self.tax_regime or "General (VAT + Profit)"
		step_order = 0

		for month in range(1, 13):
			step_order += 1
			taxes = []

			# Monthly taxes
			if regime == "General (VAT + Profit)" and self.is_vat_payer:
				taxes.append("ƏDV")
			if self.has_employees:
				taxes.append("Payroll (data)")

			m_start = date(year, month, 1)
			m_end = date(year, month, monthrange(year, month)[1])
			next_m = month + 1 if month < 12 else 1
			next_y = year if month < 12 else year + 1
			decl_deadline = date(next_y, next_m, min(20, monthrange(next_y, next_m)[1]))

			self.append("steps", {
				"step_order": step_order,
				"step_type": "Monthly",
				"period_label": f"{MONTH_NAMES[month-1]} {year}",
				"month_start": str(m_start),
				"month_end": str(m_end),
				"quarter": f"Q{(month-1)//3 + 1}",
				"taxes_included": ", ".join(taxes) if taxes else "—",
				"status": "Pending",
				"deadline_declaration": str(decl_deadline),
				"deadline_payment": str(decl_deadline),
			})

			# Quarterly step after months 3, 6, 9, 12
			if month in (3, 6, 9, 12):
				q_num = month // 3
				step_order += 1
				q_taxes = []

				if regime == "General (VAT + Profit)":
					q_taxes.append(_("Profit Tax (advance)"))
				elif regime in ("Simplified Tax", "Special Regime"):
					q_taxes.append(_("Simplified Tax"))

				if self.has_assets:
					q_taxes.append(_("Property Tax (1/4)"))

				q_start_m = QUARTER_MONTHS[q_num][0]
				q_end_m = QUARTER_MONTHS[q_num][1]
				q_next_m = q_end_m + 1 if q_end_m < 12 else 1
				q_next_y = year if q_end_m < 12 else year + 1
				q_deadline = date(q_next_y, q_next_m, min(15, monthrange(q_next_y, q_next_m)[1]))

				self.append("steps", {
					"step_order": step_order,
					"step_type": "Quarterly",
					"period_label": f"{QUARTER_NAMES[q_num-1]} {year}",
					"month_start": str(date(year, q_start_m, 1)),
					"month_end": str(date(year, q_end_m, monthrange(year, q_end_m)[1])),
					"quarter": f"Q{q_num}",
					"taxes_included": ", ".join(q_taxes) if q_taxes else "—",
					"status": "Pending",
					"deadline_declaration": str(q_deadline) if regime != "General (VAT + Profit)" else str(date(year + 1, 3, 31)),
					"deadline_payment": str(q_deadline),
				})

		# Annual closing step
		step_order += 1
		annual_taxes = []
		if regime == "General (VAT + Profit)":
			annual_taxes.append(_("Profit Tax (annual)"))
		if self.has_assets:
			annual_taxes.append(_("Property Tax (annual)"))
		annual_taxes.append("Period Closing")

		self.append("steps", {
			"step_order": step_order,
			"step_type": "Annual",
			"period_label": _("Annual Closing {0}").format(year),
			"month_start": str(date(year, 1, 1)),
			"month_end": str(date(year, 12, 31)),
			"quarter": "",
			"taxes_included": ", ".join(annual_taxes),
			"status": "Pending",
			"deadline_declaration": str(date(year + 1, 3, 31)),
			"deadline_payment": str(date(year + 1, 3, 31)),
		})

		# Set first step as Active
		if self.steps:
			self.steps[0].status = "Active"

		self.overall_status = "Not Started"
		self.add_log("Setup", "Success",
			f"Generated {len(self.steps)} steps for {year}")

	def _detect_company(self):
		company = frappe.get_doc("Company", self.company)
		self.company_voen = company.tax_id or ""
		self.accounting_method = getattr(company, "accounting_method", "") or ""
		self.company_classification = getattr(company, "business_classification", "") or ""
		self.company_activity_code = getattr(company, "main_activity", "") or ""
		self.company_tax_authority = getattr(company, "tax_authority", "") or ""
		self.is_vat_payer = 1 if getattr(company, "vat_certificate_number", None) else 0
		self.has_assets = 1 if frappe.db.count("Asset", {
			"company": self.company, "docstatus": 1,
			"status": ["not in", ["Scrapped", "Sold"]]}) else 0
		self.has_employees = 1 if frappe.db.count("Employee", {
			"company": self.company, "status": "Active"}) else 0

		# Avg monthly employees
		year = cint(self.year)
		monthly = frappe.db.sql("""
			SELECT COUNT(DISTINCT employee) as cnt
			FROM `tabSalary Slip`
			WHERE company=%s AND docstatus=1
			AND posting_date BETWEEN %s AND %s
		""", (self.company, f"{year}-01-01", f"{year}-12-31"), as_dict=True)
		self.avg_employees = monthly[0].cnt if monthly else 0

		# Auto-detect regime
		if not self.tax_regime:
			settings = frappe.get_single("Tax Period Closing Settings")
			if settings.auto_detect_regime:
				self.tax_regime = "General (VAT + Profit)" if self.is_vat_payer else "Simplified Tax"
			else:
				self.tax_regime = settings.default_tax_regime or "General (VAT + Profit)"

	def _detect_benefits_for_year(self):
		"""Reuse benefit detection from Tax Period Closing Wizard."""
		self.detected_benefits = []
		classification = self.company_classification or ""
		is_micro = "Micro" in classification

		if is_micro and self.avg_employees >= 3:
			self.append("detected_benefits", {
				"enabled": 1,
				"article": "Maddə 106.1.20",
				"benefit_name": "Mikro sahibkar — 75% mənfəət azadolması",
				"applies_to": "Profit Tax (Mənfəət)",
				"exemption_percent": 75,
				"condition_met": "Yes",
				"condition_details": f"Orta aylıq işçi sayı: {self.avg_employees} (≥3). DSMF borcu: əl ilə yoxlanmalıdır.",
				"law_reference": "Vergi Məcəlləsi, Maddə 106.1.20",
				"law_url": "https://e-qanun.az/framework/46947",
			})

		if is_micro:
			self.append("detected_benefits", {
				"enabled": 1,
				"article": "DSMF Micro",
				"benefit_name": "Mikro — DSMF 15% (22% əvəzinə)",
				"applies_to": "DSMF",
				"exemption_percent": 32,
				"condition_met": "Yes",
				"condition_details": "Mikro sahibkar DSMF dərəcəsi 15%. Payroll-da tətbiq olunur.",
				"law_reference": "Sosial sığorta haqqında Qanun",
				"law_url": "https://e-qanun.az/framework/46854",
			})

	# ── Step Execution ───────────────────────────────────────

	def get_active_step(self):
		for step in self.steps or []:
			if step.status == "Active":
				return step
		return None

	def get_step_by_order(self, order):
		for step in self.steps or []:
			if step.step_order == order:
				return step
		return None

	def calculate_step(self, step_order):
		"""Calculate taxes for a specific step."""
		step = self.get_step_by_order(step_order)
		if not step:
			frappe.throw(_("Step not found."))

		# Check blocking
		if step.step_order > 1:
			prev = self.get_step_by_order(step.step_order - 1)
			if prev and prev.status not in ("Completed", "Skipped"):
				frappe.throw(
					_("Previous step ({0}) is not completed. Complete or skip it first.").format(
						prev.period_label))

		step.status = "Active"
		settings = frappe.get_single("Tax Period Closing Settings")
		from_d = str(step.month_start)
		to_d = str(step.month_end)
		total_tax = 0
		details_parts = []

		if step.step_type == "Monthly":
			total_tax, details_parts = self._calc_monthly_step(step, settings, from_d, to_d)
		elif step.step_type == "Quarterly":
			total_tax, details_parts = self._calc_quarterly_step(step, settings, from_d, to_d)
		elif step.step_type == "Annual":
			total_tax, details_parts = self._calc_annual_step(step, settings, from_d, to_d)

		step.tax_amount = total_tax
		step.details = "\n".join(details_parts)
		step.status = "Calculated"

		self.add_log("Calculation", "Success",
			f"Step {step.step_order} ({step.period_label}): {total_tax:,.2f} AZN")

	def _calc_monthly_step(self, step, settings, from_d, to_d):
		"""Calculate monthly taxes (VAT + payroll data)."""
		from erpnext.accounts.utils import get_balance_on
		total = 0
		details = []

		# VAT
		if "ƏDV" in (step.taxes_included or ""):
			vat_in = settings.vat_input_account
			vat_out = settings.vat_output_account
			if vat_in and vat_out:
				output_bal = abs(flt(get_balance_on(vat_out, to_d, company=self.company, start_date=from_d)))
				input_bal = abs(flt(get_balance_on(vat_in, to_d, company=self.company, start_date=from_d)))
				net_vat = input_bal  # amount to offset
				total += net_vat
				details.append(f"VAT: Output={output_bal:,.2f} | Input={input_bal:,.2f} | Net={output_bal-input_bal:,.2f}")

		# Payroll data
		if "Payroll" in (step.taxes_included or ""):
			payroll = frappe.db.sql("""
				SELECT SUM(gross_pay) as gross, SUM(total_deduction) as ded
				FROM `tabSalary Slip`
				WHERE company=%s AND docstatus=1
				AND posting_date BETWEEN %s AND %s
			""", (self.company, from_d, to_d), as_dict=True)
			if payroll and payroll[0].gross:
				details.append(f"Payroll: Gross={flt(payroll[0].gross):,.2f} | Deductions={flt(payroll[0].ded):,.2f}")

		return total, details

	def _calc_quarterly_step(self, step, settings, from_d, to_d):
		"""Calculate quarterly taxes (profit advance / simplified / property)."""
		from erpnext.accounts.utils import get_balance_on
		total = 0
		details = []

		# Profit Tax advance
		if "Mənfəət" in (step.taxes_included or ""):
			income_accs = frappe.get_all("Account", filters={
				"company": self.company, "root_type": "Income", "is_group": 0
			}, pluck="name")
			expense_accs = frappe.get_all("Account", filters={
				"company": self.company, "root_type": "Expense", "is_group": 0
			}, pluck="name")

			total_income = sum(abs(flt(get_balance_on(a, to_d, company=self.company, start_date=from_d))) for a in income_accs)
			total_expense = sum(abs(flt(get_balance_on(a, to_d, company=self.company, start_date=from_d))) for a in expense_accs)
			profit = total_income - total_expense

			rate = flt(settings.profit_tax_rate) or 20
			tax_before = max(0, profit * rate / 100)

			# Apply benefits
			exemption_pct = 0
			for b in self.detected_benefits or []:
				if b.enabled and b.applies_to == "Profit Tax (Mənfəət)" and b.condition_met == "Yes":
					exemption_pct += flt(b.exemption_percent)
			exemption_pct = min(exemption_pct, 100)
			reduction = tax_before * exemption_pct / 100
			tax = max(0, tax_before - reduction)

			total += tax
			details.append(f"Profit: Income={total_income:,.2f} | Expense={total_expense:,.2f} | Profit={profit:,.2f}")
			details.append(f"  Tax {rate}%={tax_before:,.2f} | Exemption {exemption_pct}%=-{reduction:,.2f} | Final={tax:,.2f}")

		# Simplified Tax
		if "Sadələşdirilmiş" in (step.taxes_included or ""):
			income_accs = frappe.get_all("Account", filters={
				"company": self.company, "root_type": "Income", "is_group": 0
			}, pluck="name")
			revenue = sum(abs(flt(get_balance_on(a, to_d, company=self.company, start_date=from_d))) for a in income_accs)
			rate = flt(settings.default_simplified_rate) or 4
			tax = revenue * rate / 100
			total += tax
			details.append(f"Simplified: Revenue={revenue:,.2f} × {rate}% = {tax:,.2f}")

		# Property Tax (1/4)
		if "Əmlak" in (step.taxes_included or ""):
			from taxes_az.taxes_az.doctype.tax_period_closing_wizard.tax_period_closing_wizard import TaxPeriodClosingWizard
			assets = frappe.get_all("Asset", filters={
				"company": self.company, "docstatus": 1,
				"status": ["not in", ["Scrapped", "Sold"]]
			}, fields=["total_asset_cost", "value_after_depreciation",
				"asset_category", "taxable_asset_type"])

			taxable_nbv = 0
			for a in assets:
				if (a.asset_category in TaxPeriodClosingWizard.EXEMPT_ASSET_CATEGORIES
					or a.taxable_asset_type in TaxPeriodClosingWizard.EXEMPT_ASSET_CATEGORIES):
					continue
				taxable_nbv += flt(a.value_after_depreciation)

			prop_rate = flt(settings.property_tax_rate) or 1
			annual_tax = taxable_nbv * prop_rate / 100
			quarterly_tax = annual_tax / 4
			total += quarterly_tax
			details.append(f"Property: Taxable NBV={taxable_nbv:,.2f} × {prop_rate}% / 4 = {quarterly_tax:,.2f}")

		return total, details

	def _calc_annual_step(self, step, settings, from_d, to_d):
		"""Annual closing — summary + period closing voucher."""
		details = []
		total = 0

		# Sum all completed steps
		for s in self.steps or []:
			if s.status == "Completed" and s != step:
				total += flt(s.tax_amount)

		details.append(f"Sum of all steps: {total:,.2f} AZN")
		details.append(_("Period Closing Voucher can be created"))

		return 0, details  # Annual step doesn't add new tax, just summarizes

	def complete_step(self, step_order):
		"""Create journal entries for the step and mark it complete."""
		step = self.get_step_by_order(step_order)
		if not step:
			frappe.throw(_("Step not found."))
		if step.status not in ("Calculated",):
			frappe.throw(_("Step must be calculated first."))

		settings = frappe.get_single("Tax Period Closing Settings")
		je_names = []
		accounting_method = frappe.db.get_value("Company", self.company, "accounting_method") or ""

		if step.step_type == "Monthly":
			if "Kassa" in accounting_method:
				# Kassa: FIFO allocation (542→601, 245.1→521.3)
				# Only if not already done (duplicate protection built into execute_fifo)
				fifo_result = self.execute_fifo_allocation(
					str(step.month_start), str(step.month_end))
				if fifo_result.get("created_jes"):
					je_names.extend(fifo_result["created_jes"])
					step.details = (step.details or "") + (
						f"\nFIFO: Revenue={fifo_result['total_revenue']:,.2f} → 601 | "
						f"VAT={fifo_result['total_vat']:,.2f} → 521.3")
			else:
				# Hesablama: VAT offset — only if no existing JE for this period
				if flt(step.tax_amount) > 0 and "ƏDV" in (step.taxes_included or ""):
					existing = frappe.db.count("Journal Entry", {
						"company": self.company, "docstatus": 1,
						"user_remark": ["like", f"%VAT offset — {step.period_label}%"],
					})
					if existing == 0:
						je = self._create_je(
							settings.vat_output_account, settings.vat_input_account,
							step.tax_amount, step.month_end,
							f"VAT offset — {step.period_label}. TYCW {self.name}",
							settings)
						je_names.append(je.name)

		elif step.step_type == "Quarterly":
			taxes_in_step = (step.taxes_included or "").split(",")
			for tax_label in taxes_in_step:
				tax_label = tax_label.strip()
				tax_type = None
				if "Mənfəət" in tax_label:
					tax_type = "Profit Tax (Mənfəət)"
				elif "Sadələşdirilmiş" in tax_label:
					tax_type = "Simplified Tax (Sadələşdirilmiş)"
				elif "Əmlak" in tax_label:
					tax_type = "Property Tax (Əmlak)"

				if not tax_type:
					continue

				accs = None
				for row in settings.account_map or []:
					if row.tax_type == tax_type:
						accs = {"debit": row.debit_account, "credit": row.credit_account}
						break
				if not accs:
					continue

				# Duplicate check for this specific tax + period
				existing = frappe.db.count("Journal Entry", {
					"company": self.company, "docstatus": 1,
					"user_remark": ["like", f"%{tax_label} — {step.period_label}. TYCW {self.name}%"],
				})
				if existing > 0:
					continue

				tax_amount = self._calc_single_quarterly_tax(
					step, settings, tax_type,
					str(step.month_start), str(step.month_end))

				if flt(tax_amount) > 0:
					je = self._create_je(
						accs["debit"], accs["credit"],
						tax_amount, step.month_end,
						f"{tax_label} — {step.period_label}. TYCW {self.name}",
						settings)
					je_names.append(je.name)

		if step.step_type == "Annual":
			# Annual closing: create Period Closing Voucher
			pcv_result = self._create_pcv(step, settings, accounting_method)
			if pcv_result.get("pcv_name"):
				je_names.append(f"PCV:{pcv_result['pcv_name']}")
				step.details = (step.details or "") + f"\n{pcv_result['details']}"

		step.journal_entries = ", ".join(je_names)
		step.status = "Completed"
		step.completed_date = frappe.utils.today()

		# Activate next step
		next_step = self.get_step_by_order(step.step_order + 1)
		if next_step and next_step.status == "Pending":
			next_step.status = "Active"

		# Update progress
		total_steps = len(self.steps)
		completed = sum(1 for s in self.steps if s.status in ("Completed", "Skipped"))
		self.overall_progress = (completed / total_steps * 100) if total_steps else 0
		self.overall_status = "Completed" if completed == total_steps else "In Progress"

		# Update totals
		self.total_taxes_created = sum(flt(s.tax_amount) for s in self.steps
			if s.status == "Completed")
		self.grand_total = self.total_taxes_created + flt(self.total_payroll_collected)

		self.add_log("Step Complete", "Success",
			f"Step {step.step_order} ({step.period_label}): {flt(step.tax_amount):,.2f} AZN | "
			f"JE: {step.journal_entries or 'none'}")

	def skip_step(self, step_order):
		"""Skip a step with a note."""
		step = self.get_step_by_order(step_order)
		if not step:
			frappe.throw(_("Step not found."))

		step.status = "Skipped"
		step.completed_date = frappe.utils.today()
		step.details = (step.details or "") + "\n[SKIPPED]"

		next_step = self.get_step_by_order(step.step_order + 1)
		if next_step and next_step.status == "Pending":
			next_step.status = "Active"

		total_steps = len(self.steps)
		completed = sum(1 for s in self.steps if s.status in ("Completed", "Skipped"))
		self.overall_progress = (completed / total_steps * 100) if total_steps else 0
		self.overall_status = "Completed" if completed == total_steps else "In Progress"

		self.add_log("Step Skipped", "Warning",
			f"Step {step.step_order} ({step.period_label}): SKIPPED")

	# ── SI Account Check ─────────────────────────────────────

	def check_si_accounts(self):
		"""Check if Sales Invoices use correct accounts for the accounting method."""
		accounting_method = frappe.db.get_value("Company", self.company, "accounting_method") or ""
		if "Kassa" not in accounting_method:
			return {"needs_fix": False, "method": accounting_method, "wrong": [], "ok": 0}

		settings = frappe.get_single("Tax Period Closing Settings")
		vat_output = settings.vat_output_account  # 521.3
		deferred_vat = settings.deferred_vat_sales_account  # 245.1

		if not vat_output or not deferred_vat:
			return {"needs_fix": False, "error": "Settings not configured"}

		year = cint(self.year)
		invoices = frappe.get_all("Sales Invoice", filters={
			"company": self.company, "docstatus": 1,
			"posting_date": ["between", [f"{year}-01-01", f"{year}-12-31"]],
		}, fields=["name", "posting_date", "grand_total", "total_taxes_and_charges"])

		wrong = []
		ok = 0
		for si in invoices:
			if flt(si.total_taxes_and_charges) == 0:
				continue

			vat_on_521 = flt(frappe.db.sql("""
				SELECT SUM(credit) FROM `tabGL Entry`
				WHERE voucher_type='Sales Invoice' AND voucher_no=%s
				AND account=%s AND is_cancelled=0
			""", (si.name, vat_output))[0][0] or 0)

			if vat_on_521 > 0:
				wrong.append({
					"invoice": si.name,
					"date": str(si.posting_date),
					"vat": vat_on_521,
					"total": flt(si.grand_total),
				})
			else:
				ok += 1

		return {
			"needs_fix": len(wrong) > 0,
			"method": accounting_method,
			"wrong": wrong,
			"ok": ok,
			"total": len(wrong) + ok,
		}

	def fix_si_accounts(self):
		"""Fix Sales Invoices: 521.3→245.1 and 601→542 in GL."""
		settings = frappe.get_single("Tax Period Closing Settings")
		vat_output = settings.vat_output_account
		deferred_vat = settings.deferred_vat_sales_account
		deferred_rev = settings.deferred_revenue_account

		if not vat_output or not deferred_vat:
			return {"fixed": 0, "error": "Settings not configured"}

		year = cint(self.year)
		invoices = frappe.get_all("Sales Invoice", filters={
			"company": self.company, "docstatus": 1,
			"posting_date": ["between", [f"{year}-01-01", f"{year}-12-31"]],
		}, pluck="name")

		fixed = 0
		for si_name in invoices:
			# Fix VAT: 521.3 → 245.1
			updated = frappe.db.sql("""
				UPDATE `tabGL Entry` SET account=%s
				WHERE voucher_type='Sales Invoice' AND voucher_no=%s
				AND account=%s AND is_cancelled=0
			""", (deferred_vat, si_name, vat_output))
			vat_rows = frappe.db.sql("SELECT ROW_COUNT()")[0][0]

			# Fix Revenue: 601% → 542
			if deferred_rev:
				income_accs = frappe.db.sql("""
					SELECT DISTINCT account FROM `tabGL Entry`
					WHERE voucher_type='Sales Invoice' AND voucher_no=%s
					AND is_cancelled=0 AND credit > 0 AND account LIKE '601%%'
				""", si_name, as_dict=True)

				for acc in income_accs:
					frappe.db.sql("""
						UPDATE `tabGL Entry` SET account=%s
						WHERE voucher_type='Sales Invoice' AND voucher_no=%s
						AND account=%s AND is_cancelled=0
					""", (deferred_rev, si_name, acc.account))

			# Fix SI internal fields
			frappe.db.sql("""
				UPDATE `tabSales Taxes and Charges` SET account_head=%s
				WHERE parent=%s AND account_head=%s
			""", (deferred_vat, si_name, vat_output))

			if deferred_rev:
				frappe.db.sql("""
					UPDATE `tabSales Invoice Item` SET income_account=%s
					WHERE parent=%s AND income_account LIKE '601%%'
				""", (deferred_rev, si_name))

			if vat_rows > 0:
				fixed += 1

		frappe.db.commit()
		return {"fixed": fixed, "total": len(invoices)}

	def _create_pcv(self, step, settings, accounting_method):
		"""Create Period Closing Voucher with smart logic based on method."""
		if not settings.enable_period_closing:
			return {"details": _("PCV: Disabled in settings")}

		closing_account = settings.closing_account
		if not closing_account:
			return {"details": _("PCV: Closing account not set (341)")}

		# Check if PCV already exists for this period
		existing_pcv = frappe.db.exists("Period Closing Voucher", {
			"company": self.company,
			"fiscal_year": self.fiscal_year,
			"docstatus": 1,
		})
		if existing_pcv:
			return {
				"pcv_name": existing_pcv,
				"details": _("PCV: Already exists — {0}").format(existing_pcv),
			}

		year = cint(self.year)
		from_d = date(year, 1, 1)
		to_d = date(year, 12, 31)

		# Pre-check: what will be closed
		from erpnext.accounts.utils import get_balance_on

		# Income balance
		income_accs = frappe.get_all("Account", filters={
			"company": self.company, "root_type": "Income", "is_group": 0
		}, pluck="name")
		total_income = sum(abs(flt(get_balance_on(a, str(to_d), company=self.company, start_date=str(from_d))))
			for a in income_accs)

		# Expense balance
		expense_accs = frappe.get_all("Account", filters={
			"company": self.company, "root_type": "Expense", "is_group": 0
		}, pluck="name")
		total_expense = sum(abs(flt(get_balance_on(a, str(to_d), company=self.company, start_date=str(from_d))))
			for a in expense_accs)

		net_pl = total_income - total_expense

		# Method-specific checks
		details_lines = []
		details_lines.append(f"Accounting method: {accounting_method or 'Accrual'}")
		details_lines.append(f"Income: {total_income:,.2f} | Expense: {total_expense:,.2f}")
		details_lines.append(f"Net profit/loss: {net_pl:,.2f}")

		if "Kassa" in (accounting_method or ""):
			# For Kassa: check if all FIFO allocations done
			bal_542 = abs(flt(frappe.db.sql("""
				SELECT COALESCE(SUM(credit)-SUM(debit), 0)
				FROM `tabGL Entry` WHERE account LIKE '542%%' AND company=%s AND is_cancelled=0
			""", self.company)[0][0] or 0))

			if bal_542 > 100:  # More than 100 AZN still deferred
				details_lines.append(f"⚠ Deferred revenue (542): {bal_542:,.2f} — FIFO not completed")
				details_lines.append(_("PCV will be created, but 542 balance will carry over to next year"))
			else:
				details_lines.append(f"✓ 542 balance: {bal_542:,.2f} — FIFO completed")

			# Check 245.1
			bal_245 = abs(flt(frappe.db.sql("""
				SELECT COALESCE(SUM(credit)-SUM(debit), 0)
				FROM `tabGL Entry` WHERE account LIKE '245.1%%' AND company=%s AND is_cancelled=0
			""", self.company)[0][0] or 0))

			if bal_245 > 10:
				details_lines.append(f"⚠ Deferred VAT (245.1): {bal_245:,.2f}")
		else:
			details_lines.append(_("Accrual method: standard PCV"))

		details_lines.append(f"Closing account: {closing_account}")

		try:
			pcv = frappe.new_doc("Period Closing Voucher")
			pcv.company = self.company
			pcv.posting_date = str(to_d)
			pcv.transaction_date = str(to_d)
			pcv.fiscal_year = self.fiscal_year
			pcv.closing_account_head = closing_account
			pcv.period_start_date = str(from_d)
			pcv.period_end_date = str(to_d)
			pcv.remarks = (
				f"Annual closing — {self.fiscal_year}. "
				f"Accounting method: {accounting_method or 'Accrual'}. "
				f"TYCW: {self.name}"
			)

			pcv.insert(ignore_permissions=True)
			pcv.submit()

			details_lines.append(f"✓ PCV created: {pcv.name}")
			self.add_log("Period Closing", "Success",
				f"Period Closing Voucher {pcv.name} created. "
				f"Income: {total_income:,.2f} | Expense: {total_expense:,.2f} | "
				f"Result: {net_pl:,.2f} → {closing_account}")

			return {
				"pcv_name": pcv.name,
				"details": "\n".join(details_lines),
			}

		except Exception as e:
			error_msg = str(e)
			details_lines.append(f"✗ PCV error: {error_msg}")
			self.add_log("Period Closing", "Error", f"PCV error: {error_msg}")
			return {"details": "\n".join(details_lines)}

	def _create_je(self, debit_acc, credit_acc, amount, posting_date, remark, settings):
		cost_center = getattr(settings, "default_cost_center", None) or frappe.db.get_value(
			"Company", self.company, "cost_center")

		je = frappe.new_doc("Journal Entry")
		je.company = self.company
		je.posting_date = posting_date
		je.voucher_type = "Journal Entry"
		je.user_remark = remark

		je.append("accounts", {
			"account": debit_acc,
			"debit_in_account_currency": flt(amount),
			"cost_center": cost_center,
		})
		je.append("accounts", {
			"account": credit_acc,
			"credit_in_account_currency": flt(amount),
			"cost_center": cost_center,
		})

		je.insert(ignore_permissions=True)

		if getattr(settings, "auto_submit_entries", False):
			je.submit()

		return je

	def _calc_single_quarterly_tax(self, step, settings, tax_type, from_d, to_d):
		"""Calculate a single quarterly tax amount."""
		from erpnext.accounts.utils import get_balance_on

		if "Profit" in tax_type:
			income_accs = frappe.get_all("Account", filters={
				"company": self.company, "root_type": "Income", "is_group": 0
			}, pluck="name")
			expense_accs = frappe.get_all("Account", filters={
				"company": self.company, "root_type": "Expense", "is_group": 0
			}, pluck="name")

			# For kassa method: use cash receipts as income
			accounting_method = frappe.db.get_value("Company", self.company, "accounting_method") or ""
			if "Kassa" in accounting_method:
				total_income = flt(frappe.db.sql("""
					SELECT COALESCE(SUM(paid_amount),0) FROM `tabPayment Entry`
					WHERE company=%s AND docstatus=1 AND payment_type='Receive'
					AND posting_date BETWEEN %s AND %s
				""", (self.company, from_d, to_d))[0][0])
				total_income += flt(frappe.db.sql("""
					SELECT COALESCE(SUM(jea.debit),0) FROM `tabJournal Entry` je
					JOIN `tabJournal Entry Account` jea ON jea.parent=je.name
					WHERE je.company=%s AND je.docstatus=1 AND jea.account LIKE '226%%' AND jea.debit>0
					AND je.posting_date BETWEEN %s AND %s AND je.user_remark NOT LIKE '%%FIFO%%'
				""", (self.company, from_d, to_d))[0][0])

				total_expense = flt(frappe.db.sql("""
					SELECT COALESCE(SUM(paid_amount),0) FROM `tabPayment Entry`
					WHERE company=%s AND docstatus=1 AND payment_type='Pay'
					AND posting_date BETWEEN %s AND %s
				""", (self.company, from_d, to_d))[0][0])
				# Add salary
				total_expense += flt(frappe.db.sql("""
					SELECT COALESCE(SUM(net_pay),0) FROM `tabSalary Slip`
					WHERE company=%s AND docstatus=1 AND posting_date BETWEEN %s AND %s
				""", (self.company, from_d, to_d))[0][0])
			else:
				total_income = sum(abs(flt(get_balance_on(a, to_d, company=self.company, start_date=from_d))) for a in income_accs)
				total_expense = sum(abs(flt(get_balance_on(a, to_d, company=self.company, start_date=from_d))) for a in expense_accs)

			profit = total_income - total_expense
			rate = flt(settings.profit_tax_rate) or 20
			tax_before = max(0, profit * rate / 100)

			# Apply benefits
			exemption_pct = 0
			for b in self.detected_benefits or []:
				if b.enabled and b.applies_to == "Profit Tax (Mənfəət)" and b.condition_met == "Yes":
					exemption_pct += flt(b.exemption_percent)
			exemption_pct = min(exemption_pct, 100)
			return max(0, tax_before - (tax_before * exemption_pct / 100))

		elif "Simplified" in tax_type:
			rate = flt(settings.default_simplified_rate) or 4
			income_accs = frappe.get_all("Account", filters={
				"company": self.company, "root_type": "Income", "is_group": 0
			}, pluck="name")
			revenue = sum(abs(flt(get_balance_on(a, to_d, company=self.company, start_date=from_d))) for a in income_accs)
			return revenue * rate / 100

		elif "Property" in tax_type:
			from taxes_az.taxes_az.doctype.tax_period_closing_wizard.tax_period_closing_wizard import TaxPeriodClosingWizard
			assets = frappe.get_all("Asset", filters={
				"company": self.company, "docstatus": 1,
				"status": ["not in", ["Scrapped", "Sold"]]
			}, fields=["total_asset_cost", "value_after_depreciation",
				"asset_category", "taxable_asset_type"])

			taxable_nbv = 0
			for a in assets:
				if (a.asset_category in TaxPeriodClosingWizard.EXEMPT_ASSET_CATEGORIES
					or a.taxable_asset_type in TaxPeriodClosingWizard.EXEMPT_ASSET_CATEGORIES):
					continue
				taxable_nbv += flt(a.value_after_depreciation)

			rate = flt(settings.property_tax_rate) or 1
			return taxable_nbv * rate / 100 / 4

		return 0

	# ── FIFO Allocation ──────────────────────────────────────

	def run_fifo_allocation(self, from_date, to_date):
		"""FIFO allocate payments to invoices for cash method recognition."""
		settings = frappe.get_single("Tax Period Closing Settings")
		deferred_vat = settings.deferred_vat_sales_account
		deferred_rev = settings.deferred_revenue_account
		vat_output = settings.vat_output_account

		if not deferred_vat or not deferred_rev or not vat_output:
			return {"error": _("Deferred accounts not configured in Settings.")}

		# 1. All SI with unrecognized revenue (has balance on 542/245.1), per customer FIFO
		si_balances = []
		all_sis = frappe.get_all("Sales Invoice", filters={
			"company": self.company, "docstatus": 1
		}, fields=["name", "posting_date", "customer", "grand_total",
			"net_total", "total_taxes_and_charges"],
			order_by="posting_date, name")

		for si in all_sis:
			# How much already recognized (transferred from 542→601)?
			recognized = abs(flt(frappe.db.sql("""
				SELECT SUM(debit) FROM `tabGL Entry`
				WHERE account=%s AND is_cancelled=0
				AND against_voucher_type='Sales Invoice' AND against_voucher=%s
			""", (deferred_rev, si.name))[0][0] or 0))

			remaining = flt(si.grand_total) - recognized
			if remaining > 0.01:
				si_balances.append({
					"name": si.name,
					"date": str(si.posting_date),
					"customer": si.customer,
					"grand_total": flt(si.grand_total),
					"net_total": flt(si.net_total),
					"vat": flt(si.total_taxes_and_charges),
					"recognized": recognized,
					"remaining": remaining,
				})

		# 2. Payments in period — combine PE + JE per customer per date
		combined = {}

		# PE Receive
		pes = frappe.db.sql("""
			SELECT name, posting_date, paid_amount, party as customer
			FROM `tabPayment Entry`
			WHERE company=%s AND docstatus=1 AND payment_type='Receive'
			AND party_type='Customer'
			AND posting_date BETWEEN %s AND %s
		""", (self.company, from_date, to_date), as_dict=True)

		for pe in pes:
			key = f"{pe.customer}_{pe.posting_date}"
			combined.setdefault(key, {"customer": pe.customer, "date": pe.posting_date, "total": 0, "sources": []})
			combined[key]["total"] += flt(pe.paid_amount)
			combined[key]["sources"].append(f"PE: {pe.name}")

		# JE with Дт 226
		jes = frappe.db.sql("""
			SELECT je.name, je.posting_date, jea.debit as amount
			FROM `tabJournal Entry` je
			JOIN `tabJournal Entry Account` jea ON jea.parent = je.name
			WHERE je.company=%s AND je.docstatus=1
			AND jea.account LIKE '226%%' AND jea.debit > 0
			AND je.posting_date BETWEEN %s AND %s
			AND je.user_remark NOT LIKE '%%FIFO%%'
		""", (self.company, from_date, to_date), as_dict=True)

		for je in jes:
			party = frappe.db.sql("""
				SELECT party FROM `tabJournal Entry Account`
				WHERE parent=%s AND account LIKE '211%%' AND credit > 0 LIMIT 1
			""", je.name)
			customer = party[0][0] if party else None
			if customer:
				key = f"{customer}_{je.posting_date}"
				combined.setdefault(key, {"customer": customer, "date": je.posting_date, "total": 0, "sources": []})
				combined[key]["total"] += flt(je.amount)
				combined[key]["sources"].append(f"JE: {je.name}")

		payments = sorted(combined.values(), key=lambda x: str(x["date"]))

		# 3. FIFO allocation
		allocations = []
		for payment in payments:
			remaining_pay = payment["total"]

			for si in si_balances:
				if remaining_pay <= 0.01:
					break
				if si["customer"] != payment["customer"]:
					continue
				if si["remaining"] <= 0.01:
					continue

				allocate = min(remaining_pay, si["remaining"])
				ratio = allocate / si["grand_total"] if si["grand_total"] else 0
				vat_part = flt(si["vat"] * ratio, 2)
				rev_part = flt(si["net_total"] * ratio, 2)

				allocations.append({
					"invoice": si["name"],
					"invoice_date": si["date"],
					"customer": si["customer"],
					"payment_date": str(payment["date"]),
					"sources": payment["sources"],
					"allocated": allocate,
					"ratio": ratio,
					"vat_transfer": vat_part,
					"revenue_transfer": rev_part,
				})

				si["remaining"] -= allocate
				remaining_pay -= allocate

		return {"allocations": allocations, "payments": payments, "si_balances": si_balances}

	def execute_fifo_allocation(self, from_date, to_date):
		"""Execute FIFO: create JE for 245.1→521.3 and 542→601 transfers."""
		# Duplicate protection: check if FIFO already done for this period + wizard
		existing = frappe.db.count("Journal Entry", {
			"company": self.company,
			"docstatus": 1,
			"user_remark": ["like", f"%TYCW {self.name}%"],
			"posting_date": ["between", [from_date, to_date]],
		})
		if existing > 0:
			return {
				"status": "skip",
				"created_jes": [],
				"total_vat": 0,
				"total_revenue": 0,
				"count": 0,
				"message": _("{0} FIFO journal entries already exist for this period.").format(existing),
			}

		result = self.run_fifo_allocation(from_date, to_date)
		if "error" in result:
			frappe.throw(result["error"])

		settings = frappe.get_single("Tax Period Closing Settings")
		deferred_vat = settings.deferred_vat_sales_account
		deferred_rev = settings.deferred_revenue_account
		vat_output = settings.vat_output_account
		cost_center = getattr(settings, "default_cost_center", None) or frappe.db.get_value(
			"Company", self.company, "cost_center")

		income_account = frappe.db.get_value("Account", {
			"company": self.company, "account_number": "601", "is_group": 0
		}, "name")
		if not income_account:
			income_account = frappe.db.get_value("Account", {
				"company": self.company, "root_type": "Income", "is_group": 0
			}, "name")

		created_jes = []
		total_vat = 0
		total_rev = 0

		for alloc in result.get("allocations", []):
			if alloc["vat_transfer"] <= 0.01 and alloc["revenue_transfer"] <= 0.01:
				continue

			je = frappe.new_doc("Journal Entry")
			je.company = self.company
			je.posting_date = alloc["payment_date"]
			je.voucher_type = "Journal Entry"
			je.user_remark = (
				f"FIFO Cash: {alloc['invoice']} ({alloc['ratio']*100:.0f}% recognized). "
				f"TYCW {self.name}"
			)

			if alloc["vat_transfer"] > 0:
				je.append("accounts", {
					"account": deferred_vat,
					"debit_in_account_currency": alloc["vat_transfer"],
					"cost_center": cost_center,
					"against_voucher_type": "Sales Invoice",
					"against_voucher": alloc["invoice"],
				})
				je.append("accounts", {
					"account": vat_output,
					"credit_in_account_currency": alloc["vat_transfer"],
					"cost_center": cost_center,
				})
				total_vat += alloc["vat_transfer"]

			if alloc["revenue_transfer"] > 0 and income_account:
				je.append("accounts", {
					"account": deferred_rev,
					"debit_in_account_currency": alloc["revenue_transfer"],
					"cost_center": cost_center,
					"against_voucher_type": "Sales Invoice",
					"against_voucher": alloc["invoice"],
				})
				je.append("accounts", {
					"account": income_account,
					"credit_in_account_currency": alloc["revenue_transfer"],
					"cost_center": cost_center,
				})
				total_rev += alloc["revenue_transfer"]

			je.insert(ignore_permissions=True)
			je.submit()
			created_jes.append(je.name)

		return {
			"status": "done",
			"created_jes": created_jes,
			"total_vat": total_vat,
			"total_revenue": total_rev,
			"count": len(created_jes),
			"allocations": result.get("allocations", []),
		}


# ── Whitelisted API ─────────────────────────────────────────

@frappe.whitelist()
def check_si_accounts(name):
	"""Check if SI accounts need fixing for cash method."""
	doc = frappe.get_doc("Tax Year Closing Wizard", name)
	doc.check_permission("read")
	return doc.check_si_accounts()


@frappe.whitelist()
def fix_si_accounts(name):
	"""Fix SI accounts: 521.3→245.1, 601→542."""
	doc = frappe.get_doc("Tax Year Closing Wizard", name)
	doc.check_permission("write")
	return doc.fix_si_accounts()


@frappe.whitelist()
def initialize_wizard(name):
	doc = frappe.get_doc("Tax Year Closing Wizard", name)
	doc.check_permission("write")
	doc.generate_steps()
	doc.save()
	frappe.db.commit()
	return {"status": "ok", "steps": len(doc.steps)}


@frappe.whitelist()
def calculate_step(name, step_order):
	doc = frappe.get_doc("Tax Year Closing Wizard", name)
	doc.check_permission("write")
	doc.calculate_step(cint(step_order))
	doc.save()
	frappe.db.commit()
	return {"status": "calculated", "step": cint(step_order)}


@frappe.whitelist()
def complete_step(name, step_order):
	doc = frappe.get_doc("Tax Year Closing Wizard", name)
	doc.check_permission("write")
	doc.complete_step(cint(step_order))
	doc.save()
	frappe.db.commit()

	# Return step data for report popup
	step = doc.get_step_by_order(cint(step_order))
	next_step = doc.get_step_by_order(cint(step_order) + 1)

	return {
		"status": "completed",
		"step": cint(step_order),
		"step_data": {
			"period_label": step.period_label if step else "",
			"step_type": step.step_type if step else "",
			"month_start": str(step.month_start) if step else "",
			"month_end": str(step.month_end) if step else "",
			"taxes_included": step.taxes_included if step else "",
			"tax_amount": step.tax_amount if step else 0,
			"journal_entries": step.journal_entries if step else "",
			"details": step.details if step else "",
			"deadline_declaration": str(step.deadline_declaration) if step and step.deadline_declaration else "",
			"deadline_payment": str(step.deadline_payment) if step and step.deadline_payment else "",
		},
		"next_step": next_step.period_label if next_step else None,
	}


@frappe.whitelist()
def skip_step(name, step_order):
	doc = frappe.get_doc("Tax Year Closing Wizard", name)
	doc.check_permission("write")
	doc.skip_step(cint(step_order))
	doc.save()
	frappe.db.commit()
	return {"status": "skipped", "step": cint(step_order)}


@frappe.whitelist()
def allocate_all(name):
	"""Run FIFO allocation for all monthly steps."""
	doc = frappe.get_doc("Tax Year Closing Wizard", name)
	doc.check_permission("write")

	total_jes = 0
	total_vat = 0
	total_revenue = 0
	periods = 0

	for step in doc.steps:
		if step.step_type != "Monthly":
			continue

		result = doc.execute_fifo_allocation(str(step.month_start), str(step.month_end))
		if result.get("created_jes"):
			existing_jes = step.journal_entries or ""
			new_jes = ", ".join(result["created_jes"])
			step.journal_entries = f"{existing_jes}, {new_jes}".strip(", ")
			step.details = (step.details or "") + (
				f"\nFIFO: VAT={result['total_vat']:,.2f} | "
				f"Revenue={result['total_revenue']:,.2f} | "
				f"JE: {len(result['created_jes'])}"
			)
			total_jes += len(result["created_jes"])
			total_vat += result["total_vat"]
			total_revenue += result["total_revenue"]
			periods += 1

	doc.save()
	frappe.db.commit()

	return {
		"status": "done",
		"total_jes": total_jes,
		"total_vat": total_vat,
		"total_revenue": total_revenue,
		"periods_processed": periods,
	}


@frappe.whitelist()
def get_step_report(name, step_order):
	"""Get detailed report for a specific step/month."""
	doc = frappe.get_doc("Tax Year Closing Wizard", name)
	doc.check_permission("read")

	step = doc.get_step_by_order(cint(step_order))
	if not step:
		frappe.throw(_("Step not found."))

	from erpnext.accounts.utils import get_balance_on
	company = doc.company
	from_d = str(step.month_start)
	to_d = str(step.month_end)
	settings = frappe.get_single("Tax Period Closing Settings")

	report = {
		"period": step.period_label,
		"type": step.step_type,
		"from": from_d,
		"to": to_d,
		"status": step.status,
	}

	# Income by account
	income_accs = frappe.get_all("Account", filters={
		"company": company, "root_type": "Income", "is_group": 0
	}, fields=["name", "account_name", "account_number"])

	income_lines = []
	total_income = 0
	for acc in income_accs:
		bal = abs(flt(get_balance_on(acc.name, to_d, company=company, start_date=from_d)))
		if bal > 0:
			income_lines.append({"account": acc.account_number, "name": acc.account_name, "amount": bal})
			total_income += bal
	report["income"] = income_lines
	report["total_income"] = total_income

	# Expense by account
	expense_accs = frappe.get_all("Account", filters={
		"company": company, "root_type": "Expense", "is_group": 0
	}, fields=["name", "account_name", "account_number"])

	expense_lines = []
	total_expense = 0
	for acc in expense_accs:
		bal = abs(flt(get_balance_on(acc.name, to_d, company=company, start_date=from_d)))
		if bal > 0:
			expense_lines.append({"account": acc.account_number, "name": acc.account_name, "amount": bal})
			total_expense += bal
	report["expenses"] = expense_lines
	report["total_expense"] = total_expense
	report["net_profit"] = total_income - total_expense

	# VAT
	vat_out = abs(flt(get_balance_on(settings.vat_output_account, to_d, company=company, start_date=from_d))) if settings.vat_output_account else 0
	vat_in = abs(flt(get_balance_on(settings.vat_input_account, to_d, company=company, start_date=from_d))) if settings.vat_input_account else 0
	report["vat_output"] = vat_out
	report["vat_input"] = vat_in
	report["vat_net"] = vat_out - vat_in

	# Payments received this month
	pe_received = flt(frappe.db.sql("""
		SELECT SUM(paid_amount) FROM `tabPayment Entry`
		WHERE company=%s AND docstatus=1 AND payment_type='Receive'
		AND posting_date BETWEEN %s AND %s
	""", (company, from_d, to_d))[0][0] or 0)

	je_226 = flt(frappe.db.sql("""
		SELECT SUM(jea.debit) FROM `tabJournal Entry` je
		JOIN `tabJournal Entry Account` jea ON jea.parent=je.name
		WHERE je.company=%s AND je.docstatus=1
		AND jea.account LIKE '226%%' AND jea.debit > 0
		AND je.posting_date BETWEEN %s AND %s
		AND je.user_remark NOT LIKE '%%FIFO%%'
	""", (company, from_d, to_d))[0][0] or 0)

	report["payments_received"] = pe_received
	report["vat_226_received"] = je_226
	report["total_cash_in"] = pe_received + je_226

	# Payments made this month
	pe_paid = flt(frappe.db.sql("""
		SELECT SUM(paid_amount) FROM `tabPayment Entry`
		WHERE company=%s AND docstatus=1 AND payment_type='Pay'
		AND posting_date BETWEEN %s AND %s
	""", (company, from_d, to_d))[0][0] or 0)
	report["payments_made"] = pe_paid

	# Payroll
	payroll = frappe.db.sql("""
		SELECT SUM(gross_pay) as gross, SUM(total_deduction) as ded, SUM(net_pay) as net, COUNT(*) as cnt
		FROM `tabSalary Slip`
		WHERE company=%s AND docstatus=1 AND posting_date BETWEEN %s AND %s
	""", (company, from_d, to_d), as_dict=True)
	if payroll and payroll[0].gross:
		report["payroll"] = {
			"gross": flt(payroll[0].gross),
			"deductions": flt(payroll[0].ded),
			"net": flt(payroll[0].net),
			"count": payroll[0].cnt,
		}

	# FIFO allocations done for this period
	fifo_jes = frappe.db.sql("""
		SELECT name, total_debit, posting_date FROM `tabJournal Entry`
		WHERE company=%s AND docstatus=1
		AND user_remark LIKE '%%FIFO%%'
		AND posting_date BETWEEN %s AND %s
	""", (company, from_d, to_d), as_dict=True)
	report["fifo_entries"] = [{"name": j.name, "amount": flt(j.total_debit)} for j in fifo_jes]

	# Step JEs
	report["step_jes"] = (step.journal_entries or "").split(",")
	report["step_jes"] = [j.strip() for j in report["step_jes"] if j.strip()]

	# Deadlines
	report["deadline_declaration"] = str(step.deadline_declaration) if step.deadline_declaration else ""
	report["deadline_payment"] = str(step.deadline_payment) if step.deadline_payment else ""

	return report


@frappe.whitelist()
def preview_allocation(name, step_order):
	"""Preview FIFO allocation for a step without executing."""
	doc = frappe.get_doc("Tax Year Closing Wizard", name)
	doc.check_permission("read")
	step = doc.get_step_by_order(cint(step_order))
	if not step:
		frappe.throw(_("Step not found."))
	return doc.run_fifo_allocation(str(step.month_start), str(step.month_end))


@frappe.whitelist()
def execute_allocation(name, step_order):
	"""Execute FIFO allocation for a step — create JEs."""
	doc = frappe.get_doc("Tax Year Closing Wizard", name)
	doc.check_permission("write")
	step = doc.get_step_by_order(cint(step_order))
	if not step:
		frappe.throw(_("Step not found."))

	result = doc.execute_fifo_allocation(str(step.month_start), str(step.month_end))

	# Update step details
	if result.get("created_jes"):
		existing_jes = step.journal_entries or ""
		new_jes = ", ".join(result["created_jes"])
		step.journal_entries = f"{existing_jes}, {new_jes}".strip(", ")
		step.details = (step.details or "") + (
			f"\nFIFO: VAT={result['total_vat']:,.2f} → 521.3 | "
			f"Revenue={result['total_revenue']:,.2f} → 601 | "
			f"JE: {len(result['created_jes'])}"
		)
		doc.add_log("Step Complete", "Success",
			f"FIFO allocation: {len(result['created_jes'])} JE | "
			f"VAT: {result['total_vat']:,.2f} | Revenue: {result['total_revenue']:,.2f}")

	doc.save()
	frappe.db.commit()
	return result
