import datetime as _dt
import math as _math
from calendar import monthrange
from datetime import date

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

import erpnext

from hrms.payroll.doctype.salary_slip.salary_slip import _safe_eval

salary_slip = frappe.qb.DocType("Salary Slip")
salary_detail = frappe.qb.DocType("Salary Detail")

EARNING_TYPES = [
	"DSMF (xərc şirkət)",
	"Məzuniyyət Kompensasiyası",
	"İcbari tibbi sığorta (xərc şirkət 2%)",
	"İşsizlikdən sığorta haqqı (xərc şirkət 0,5%)",
	"Əsas əmək haqqı",
]

DEDUCTION_TYPES = [
	"DSMF (işçi)",
	"DSMF (şirkət)",
	"İcbari tibbi sığorta (işçi 2%)",
	"İcbari tibbi sığorta (şirkət 2%)",
	"İşsizlikdən sığorta haqqı (işçi 0,5%)",
	"İşsizlikdən sığorta haqqı (şirkət 0,5%)",
	"Gəlir vergisi",
]

EH_COMPONENT = "Əsas əmək haqqı"
MK_COMPONENT = "Məzuniyyət Kompensasiyası"

# Social-contribution deductions (everything except Gəlir vergisi) get an extra
# column per month: the contribution recomputed on gross_pay net of its own
# "cəlb olunmayan gəlir" amount. Maps deduction name → Salary Slip exempt field.
CONTRIBUTION_EXEMPT_FIELD = {
	"DSMF (işçi)": "custom_mdss_amount",
	"DSMF (şirkət)": "custom_mdss_amount",
	"İcbari tibbi sığorta (işçi 2%)": "custom_medical_insurance_amount",
	"İcbari tibbi sığorta (şirkət 2%)": "custom_medical_insurance_amount",
	"İşsizlikdən sığorta haqqı (işçi 0,5%)": "custom_unemployment_insurance_amount",
	"İşsizlikdən sığorta haqqı (şirkət 0,5%)": "custom_unemployment_insurance_amount",
}

# Контекст для безопасного eval формул Salary Component. Зеркалит whitelisted_globals из HRMS.
FORMULA_GLOBALS = {
	"int": int,
	"float": float,
	"long": int,
	"round": round,
	"date": _dt.date,
	"getdate": getdate,
	"ceil": _math.ceil,
	"floor": _math.floor,
}

TAX_FREE_FIELDS = [
	("custom_vergi_indicator", "custom_vergi_amount", "Vergi tutulmayan gəlir"),
	("custom_mdss_indicator", "custom_mdss_amount", "MDSS cəlb olunmayan gəlir"),
	(
		"custom_unemployment_insurance_indicator",
		"custom_unemployment_insurance_amount",
		"İşsizlik sığortasına cəlb olunmayan gəlir",
	),
	(
		"custom_medical_insurance_indicator",
		"custom_medical_insurance_amount",
		"İcbari tibbi sığortaya cəlb olunmayan gəlir",
	),
]

MONTHS = (1, 2, 3)


def get_deduction_specs(names):
	specs = {}
	for name in names:
		specs[name] = frappe.db.get_value(
			"Salary Component",
			name,
			["formula", "condition", "amount_based_on_formula", "disabled"],
			as_dict=True,
		)
	return specs


def eval_deduction(spec, base_val, le_val, ss):
	"""Запускает формулу Salary Component с заданными base и LE.
	Источник истины — текущая формула в Salary Component, читается на лету.
	Возвращает 0 если: компонент отсутствует/disabled/без формулы/condition не сработал/eval упал.
	"""
	if not spec or spec.get("disabled") or not spec.get("amount_based_on_formula") or not spec.get("formula"):
		return 0.0

	context = {
		"base": flt(base_val),
		"LE": flt(le_val),
		"start_date": ss.start_date,
		"end_date": ss.end_date,
	}
	try:
		condition = spec.get("condition")
		if condition and condition.strip():
			if not _safe_eval(condition, FORMULA_GLOBALS, context):
				return 0.0
		return flt(_safe_eval(spec.formula, FORMULA_GLOBALS, context))
	except Exception:
		frappe.log_error(
			frappe.get_traceback(),
			"Employee Payroll Register: deduction formula eval failed",
		)
		return 0.0


def execute(filters=None):
	if not filters:
		filters = {}

	if filters.get("from_date"):
		fd = getdate(filters["from_date"])
	else:
		today = date.today()
		quarter = (today.month - 1) // 3 + 1
		fd = date(today.year, (quarter - 1) * 3 + 1, 1)
	start_month = (fd.month - 1) // 3 * 3 + 1
	end_month = start_month + 2
	from_date = date(fd.year, start_month, 1)
	to_date = date(fd.year, end_month, monthrange(fd.year, end_month)[1])

	currency = "AZN"
	company_currency = erpnext.get_company_currency(filters.get("company"))

	filters["currency"] = currency
	filters["docstatus"] = "Submitted"
	filters["from_date"] = from_date
	filters["to_date"] = to_date

	columns = get_columns()
	salary_slips = get_salary_slips(filters, company_currency)
	if not salary_slips:
		return columns, []

	ss_earning_map = get_salary_slip_details(salary_slips, currency, company_currency, "earnings")
	ss_ded_map = get_salary_slip_details(salary_slips, currency, company_currency, "deductions")
	doj_map = get_employee_doj_map()
	ded_specs = get_deduction_specs(DEDUCTION_TYPES)

	employee_rows = {}
	for ss in salary_slips:
		emp = ss.employee
		row = employee_rows.get(emp)
		if row is None:
			row = build_empty_row(ss, doj_map, currency, company_currency)
			employee_rows[emp] = row

		month_index = ss.start_date.month - start_month + 1
		if month_index not in MONTHS:
			continue
		prefix = f"ay_{month_index}_"

		row[prefix + "salary_slip_id"] = ss.name
		row[prefix + "start_date"] = ss.start_date
		row[prefix + "end_date"] = ss.end_date

		for field in ("leave_without_pay", "absent_days", "payment_days"):
			row[prefix + field] += flt(ss.get(field))

		for e in EARNING_TYPES:
			row[prefix + frappe.scrub(e)] += flt(ss_earning_map.get(ss.name, {}).get(e, 0))

		for d in DEDUCTION_TYPES:
			row[prefix + frappe.scrub(d)] += flt(ss_ded_map.get(ss.name, {}).get(d, 0))

		# Удержания, рассчитанные отдельно от ƏH и от MK (LE).
		# Формула удержания в проде = f(base) + g(LE). Чтобы корректно разделить
		# вклад каждой части — вычитаем baseline = formula(0, 0). Для линейных
		# формул baseline = 0 (no-op), для Gəlir vergisi baseline = -6 из-за
		# порога 200 AZN в base-части. Без вычитания MK-колонка для GV давала
		# бы -3 вместо 3 (см. формулу `(base - 200) * 0.03 + LE * 0.03`).
		rate = flt(ss.exchange_rate) if currency == company_currency else 1.0
		eh_amount = flt(ss_earning_map.get(ss.name, {}).get(EH_COMPONENT, 0))
		mk_amount = flt(ss_earning_map.get(ss.name, {}).get(MK_COMPONENT, 0))
		for d in DEDUCTION_TYPES:
			slug = frappe.scrub(d)
			spec = ded_specs.get(d)
			baseline = eval_deduction(spec, 0, 0, ss)
			row[prefix + "ded_eh_" + slug] += eval_deduction(spec, eh_amount, 0, ss)
			row[prefix + "ded_mk_" + slug] += eval_deduction(spec, 0, mk_amount, ss) - baseline

			# Contribution recomputed on Əsas əmək haqqı net of its own
			# "cəlb olunmayan gəlir" amount.
			exempt_field = CONTRIBUTION_EXEMPT_FIELD.get(d)
			if exempt_field:
				base_net = max(eh_amount - flt(ss.get(exempt_field) or 0) * rate, 0.0)
				row[prefix + "ded_cg_" + slug] += eval_deduction(spec, base_net, 0, ss) - baseline

		row[prefix + "gross_pay"] += flt(ss.gross_pay) * rate
		row[prefix + "total_deduction"] += flt(ss.total_deduction) * rate
		row[prefix + "net_pay"] += flt(ss.net_pay) * rate

		for indicator_field, amount_field, _label in TAX_FREE_FIELDS:
			row[prefix + indicator_field] = ss.get(indicator_field)
			amount = flt(ss.get(amount_field))
			if currency == company_currency:
				amount *= flt(ss.exchange_rate)
			row[prefix + amount_field] += amount

	return columns, list(employee_rows.values())


def build_empty_row(ss, doj_map, currency, company_currency):
	row = {
		"employee": ss.employee,
		"employee_name": ss.employee_name,
		"date_of_joining": doj_map.get(ss.employee),
		"branch": ss.branch,
		"department": ss.department,
		"designation": ss.designation,
		"company": ss.company,
		"currency": currency or company_currency,
	}
	for i in MONTHS:
		prefix = f"ay_{i}_"
		row[prefix + "salary_slip_id"] = None
		row[prefix + "start_date"] = None
		row[prefix + "end_date"] = None
		row[prefix + "leave_without_pay"] = 0.0
		row[prefix + "absent_days"] = 0.0
		row[prefix + "payment_days"] = 0.0
		row[prefix + "gross_pay"] = 0.0
		row[prefix + "total_deduction"] = 0.0
		row[prefix + "net_pay"] = 0.0
		for e in EARNING_TYPES:
			row[prefix + frappe.scrub(e)] = 0.0
		for d in DEDUCTION_TYPES:
			slug = frappe.scrub(d)
			row[prefix + slug] = 0.0
			row[prefix + "ded_eh_" + slug] = 0.0
			row[prefix + "ded_mk_" + slug] = 0.0
			if d in CONTRIBUTION_EXEMPT_FIELD:
				row[prefix + "ded_cg_" + slug] = 0.0
		for indicator_field, amount_field, _lbl in TAX_FREE_FIELDS:
			row[prefix + indicator_field] = None
			row[prefix + amount_field] = 0.0
	return row


def get_columns():
	columns = [
		{
			"label": _("Employee"),
			"fieldname": "employee",
			"fieldtype": "Link",
			"options": "Employee",
			"width": 120,
		},
		{
			"label": _("Employee Name"),
			"fieldname": "employee_name",
			"fieldtype": "Data",
			"width": 140,
		},
		{
			"label": _("Date of Joining"),
			"fieldname": "date_of_joining",
			"fieldtype": "Date",
			"width": 100,
		},
		{
			"label": _("Branch"),
			"fieldname": "branch",
			"fieldtype": "Link",
			"options": "Branch",
			"width": 120,
		},
		{
			"label": _("Department"),
			"fieldname": "department",
			"fieldtype": "Link",
			"options": "Department",
			"width": 120,
		},
		{
			"label": _("Designation"),
			"fieldname": "designation",
			"fieldtype": "Link",
			"options": "Designation",
			"width": 120,
		},
		{
			"label": _("Company"),
			"fieldname": "company",
			"fieldtype": "Link",
			"options": "Company",
			"width": 120,
		},
		{
			"label": _("Currency"),
			"fieldtype": "Data",
			"fieldname": "currency",
			"options": "Currency",
			"hidden": 1,
		},
	]

	for i in MONTHS:
		prefix = f"ay_{i}_"
		suffix = f" ({i}ci ay)"
		columns.extend(
			[
				{
					"label": _("Salary Slip ID") + suffix,
					"fieldname": prefix + "salary_slip_id",
					"fieldtype": "Link",
					"options": "Salary Slip",
					"width": 150,
				},
				{
					"label": _("Start Date") + suffix,
					"fieldname": prefix + "start_date",
					"fieldtype": "Date",
					"width": 100,
				},
				{
					"label": _("End Date") + suffix,
					"fieldname": prefix + "end_date",
					"fieldtype": "Date",
					"width": 100,
				},
				{
					"label": _("Leave Without Pay") + suffix,
					"fieldname": prefix + "leave_without_pay",
					"fieldtype": "Float",
					"width": 120,
				},
				{
					"label": _("Absent Days") + suffix,
					"fieldname": prefix + "absent_days",
					"fieldtype": "Float",
					"width": 120,
				},
				{
					"label": _("Payment Days") + suffix,
					"fieldname": prefix + "payment_days",
					"fieldtype": "Float",
					"width": 120,
				},
			]
		)
		for earning in EARNING_TYPES:
			columns.append(
				{
					"label": earning + suffix,
					"fieldname": prefix + frappe.scrub(earning),
					"fieldtype": "Currency",
					"options": "currency",
					"width": 140,
				}
			)
		columns.append(
			{
				"label": _("Gross Pay") + suffix,
				"fieldname": prefix + "gross_pay",
				"fieldtype": "Currency",
				"options": "currency",
				"width": 140,
			}
		)
		for deduction in DEDUCTION_TYPES:
			columns.append(
				{
					"label": deduction + suffix,
					"fieldname": prefix + frappe.scrub(deduction),
					"fieldtype": "Currency",
					"options": "currency",
					"width": 140,
				}
			)
			if deduction in CONTRIBUTION_EXEMPT_FIELD:
				columns.append(
					{
						"label": deduction + " (cəlb edilən gəlirdən)" + suffix,
						"fieldname": prefix + "ded_cg_" + frappe.scrub(deduction),
						"fieldtype": "Currency",
						"options": "currency",
						"width": 200,
					}
				)
		for deduction in DEDUCTION_TYPES:
			columns.append(
				{
					"label": deduction + " (ƏH)" + suffix,
					"fieldname": prefix + "ded_eh_" + frappe.scrub(deduction),
					"fieldtype": "Currency",
					"options": "currency",
					"width": 160,
				}
			)
		for deduction in DEDUCTION_TYPES:
			columns.append(
				{
					"label": deduction + " (MK)" + suffix,
					"fieldname": prefix + "ded_mk_" + frappe.scrub(deduction),
					"fieldtype": "Currency",
					"options": "currency",
					"width": 160,
				}
			)
		columns.extend(
			[
				{
					"label": _("Total Deduction") + suffix,
					"fieldname": prefix + "total_deduction",
					"fieldtype": "Currency",
					"options": "currency",
					"width": 140,
				},
				{
					"label": _("Net Pay") + suffix,
					"fieldname": prefix + "net_pay",
					"fieldtype": "Currency",
					"options": "currency",
					"width": 140,
				},
			]
		)
		for indicator_field, amount_field, label in TAX_FREE_FIELDS:
			columns.append(
				{
					"label": _(label) + " - Göstərici" + suffix,
					"fieldname": prefix + indicator_field,
					"fieldtype": "Link",
					"options": "Tax Free Indicator",
					"width": 200,
				}
			)
			columns.append(
				{
					"label": _(label) + suffix,
					"fieldname": prefix + amount_field,
					"fieldtype": "Currency",
					"options": "currency",
					"width": 180,
				}
			)

	return columns


def get_salary_slips(filters, company_currency):
	doc_status = {"Draft": 0, "Submitted": 1, "Cancelled": 2}

	query = frappe.qb.from_(salary_slip).select(salary_slip.star)

	if filters.get("docstatus"):
		query = query.where(salary_slip.docstatus == doc_status[filters.get("docstatus")])

	if filters.get("from_date"):
		query = query.where(salary_slip.start_date >= filters.get("from_date"))

	if filters.get("to_date"):
		query = query.where(salary_slip.end_date <= filters.get("to_date"))

	if filters.get("company"):
		query = query.where(salary_slip.company == filters.get("company"))

	salary_slips = query.run(as_dict=1)

	return salary_slips or []


def get_employee_doj_map():
	employee = frappe.qb.DocType("Employee")
	result = (frappe.qb.from_(employee).select(employee.name, employee.date_of_joining)).run()
	return frappe._dict(result)


def get_salary_slip_details(salary_slips, currency, company_currency, component_type):
	salary_slip_names = [ss.name for ss in salary_slips]

	result = (
		frappe.qb.from_(salary_slip)
		.join(salary_detail)
		.on(salary_slip.name == salary_detail.parent)
		.where((salary_detail.parent.isin(salary_slip_names)) & (salary_detail.parentfield == component_type))
		.select(
			salary_detail.parent,
			salary_detail.salary_component,
			salary_detail.amount,
			salary_slip.exchange_rate,
		)
	).run(as_dict=1)

	ss_map = {}
	for d in result:
		ss_map.setdefault(d.parent, frappe._dict()).setdefault(d.salary_component, 0.0)
		if currency == company_currency:
			ss_map[d.parent][d.salary_component] += flt(d.amount) * flt(d.exchange_rate or 1)
		else:
			ss_map[d.parent][d.salary_component] += flt(d.amount)

	return ss_map
