# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt

import operator
from collections.abc import Callable

import frappe
from frappe.database.utils import NestedSetHierarchy
from frappe.model.db_query import get_timespan_date_range
from frappe.query_builder import Field
from frappe.utils import cstr


def like(key: Field, value: str) -> frappe.qb:
	"""Wrapper method for `LIKE`

	Args:
	        key (str): field
	        value (str): criterion

	Return:
	        frappe.qb: `frappe.qb` object with `LIKE`
	"""
	return key.like(value)


def ilike(key: Field, value: str) -> frappe.qb:
	"""Wrapper method for `ILIKE`
	Args:
	        key (str): field
	        value (str): criterion
	Return:
	        frappe.qb: `frappe.qb` object with `ILIKE`
	"""
	return key.ilike(value)


def func_in(key: Field, value: list | tuple) -> frappe.qb:
	"""Wrapper method for `IN`.

	Args:
	        key (str): field
	        value (Union[int, str]): criterion

	Return:
	        frappe.qb: `frappe.qb` object with `IN`
	"""
	if isinstance(value, str):
		value = value.split(",")

	value = ["" if v is None else v for v in value]
	if "" in value:
		return key.isin(value) | key.isnull()
	return key.isin(value)


def not_like(key: Field, value: str) -> frappe.qb:
	"""Wrapper method for `NOT LIKE`.

	Args:
	        key (str): field
	        value (str): criterion

	Return:
	        frappe.qb: `frappe.qb` object with `NOT LIKE`
	"""
	return key.not_like(value)


def func_not_in(key: Field, value: list | tuple | str):
	"""Wrapper method for `NOT IN`.

	Args:
	        key (str): field
	        value (Union[int, str]): criterion

	Return:
	        frappe.qb: `frappe.qb` object with `NOT IN`
	"""
	if isinstance(value, str):
		value = value.split(",")
	return key.notin(value)


def func_regex(key: Field, value: str) -> frappe.qb:
	"""Wrapper method for `REGEX`

	Args:
	        key (str): field
	        value (str): criterion

	Return:
	        frappe.qb: `frappe.qb` object with `REGEX`
	"""
	return key.regex(value)


def func_between(key: Field, value: list | tuple) -> frappe.qb:
	"""Wrapper method for `BETWEEN`.

	Args:
	        key (str): field
	        value (Union[int, str]): criterion

	Return:
	        frappe.qb: `frappe.qb` object with `BETWEEN`
	"""
	return key[slice(*value)]


def func_is(key, value):
	"Wrapper for IS"

	match cstr(value).lower():
		case "set":
			return key != ""
		case "not set":
			return key.isnull() | (key == "")
		case _:
			raise ValueError("`is` operator only supports `set` and `not set` as value")


def func_timespan(key: Field, value: str) -> frappe.qb:
	"""Wrapper method for `TIMESPAN`.

	Args:
	        key (str): field
	        value (str): criterion

	Return:
	        frappe.qb: `frappe.qb` object with `TIMESPAN`
	"""

	return func_between(key, get_timespan_date_range(value))


# default operators
OPERATOR_MAP: dict[str, Callable] = {
	"+": operator.add,
	"=": operator.eq,
	"-": operator.sub,
	"!=": operator.ne,
	"<": operator.lt,
	">": operator.gt,
	"<=": operator.le,
	"=<": operator.le,
	">=": operator.ge,
	"=>": operator.ge,
	"/": operator.truediv,
	"*": operator.mul,
	"in": func_in,
	"not in": func_not_in,
	"like": like,
	"ilike": ilike,
	"not like": not_like,
	"regex": func_regex,
	"between": func_between,
	"is": func_is,
	"timespan": func_timespan,
	# TODO: Add support for custom operators (WIP) - via filters_config hooks
}

NESTED_SET_OPERATORS = frozenset(NestedSetHierarchy)
