#!/usr/bin/env python
# -*- coding: utf-8 -*-
# File:                Ampel-interface/ampel/util/logicschema.py
# License:             BSD-3-Clause
# Author:              valery brinnel <firstname.lastname@gmail.com>
# Date:                03.04.2021
# Last Modified Date:  03.04.2021
# Last Modified By:    valery brinnel <firstname.lastname@gmail.com>

from collections.abc import Sequence
from typing import Any, Literal

from ampel.model.operator.AllOf import AllOf
from ampel.model.operator.AnyOf import AnyOf
from ampel.model.operator.OneOf import OneOf
from ampel.types import T, strict_iterable
from ampel.util.collections import check_seq_inner_type


def to_logical_dict(v, field_name: int | str | dict[str, Any]) -> dict[str, Any]:
	"""
	Converts str/int into {'any_of': int/str}.
	Checks structure in case dict is provided
	(how deep potential nested logic are, raises error in case of incorrect logic).
	:raises ValueError: in case provided argument is incorrect
	"""

	if isinstance(v, list):
		raise ValueError(
			f"stock->select->{field_name} config error\n" +
			f"'{field_name}' parameter cannot be a list. " +
			"Please use the following syntax:\n" +
			" -> {'any_of': ['Ab', 'Cd']} or\n" +
			" -> {'all_of': ['Ab', 'Cd']} or\n" +
			" -> {'one_of': ['Ab', 'Cd']} or\n" +
			"One level nesting is allowed, please see\n" +
			"conditional_expr_converter(..) docstring for more info"
		)

	if isinstance(v, str | int):
		return {'any_of': [v]}

	if isinstance(v, dict):

		if len(v) != 1:
			raise ValueError(
				f"stock->select->{field_name} config error\n" +
				f"Unsupported dict format {v}"
			)

		if 'any_of' in v:

			if not isinstance(v['any_of'], strict_iterable):
				raise ValueError(
					f"stock->select->{field_name}:any_of config error\n" +
					"Invalid dict value type: {}. Must be a sequence".format(type(v['any_of']))
				)

			# 'any_of' supports only a list of dicts and str/int
			if not check_seq_inner_type(v['any_of'], (str, int, dict), multi_type=True):
				raise ValueError(
					f"stock->select->{field_name}:any_of config error\n" +
					"Unsupported nesting (err 2)"
				)

			if not check_seq_inner_type(v['any_of'], (int, str)) and len(v['any_of']) < 2:
				raise ValueError(
					f"stock->select->{field_name}:any_of config error\n" +
					"any_of list must contain more than one element when containing all_of\n" +
					f"Offending value: {v}"
				)

			for el in v['any_of']:

				if isinstance(el, dict):

					if 'any_of' in el:
						raise ValueError(
							f"stock->select->{field_name}:any_of.any_of config error\n" +
							"Unsupported nesting (any_of in any_of)"
						)

					if 'all_of' in el:

						# 'all_of' closes nesting
						if not check_seq_inner_type(el['all_of'], (int, str)):
							raise ValueError(
								f"stock->select->{field_name}:any_of.all_of config error\n" +
								"Unsupported nesting (all_of list content must be str/int)"
							)

						if len(set(el['all_of'])) < 2:
							raise ValueError(
								f"stock->select->{field_name}:all_of config error\n" +
								"Please do not use all_of with just one element\n" +
								f"Offending value: {el}"
							)

					else:
						raise ValueError(
							f"stock->select->{field_name}:any_of config error\n" +
							f"Unsupported nested dict: {el}"
						)

		elif 'all_of' in v:

			# 'all_of' closes nesting
			if (
				not isinstance(v['all_of'], strict_iterable) or
				not check_seq_inner_type(v['all_of'], (int, str))
			):
				raise ValueError(
					f"stock->select->{field_name}:all_of config error\n"
					f"Invalid type for value {v['all_of']}\n(must be a sequence, is: {type(v['all_of'])})\n"
					"Note: no nesting is allowed below 'all_of'"
				)

			if len(set(v['all_of'])) < 2:
				raise ValueError(
					f"stock->select->{field_name}:all_of config error\n"
					"Please do not use all_of with just one element\n"
					f"Offending value: {v}"
				)

		elif 'one_of' in v:

			# 'one_of' closes nesting
			if (
				not isinstance(v['one_of'], strict_iterable) or
				not check_seq_inner_type(v['one_of'], (int, str))
			):
				raise ValueError(
					f"stock->select->{field_name}:one_of config error\n"
					f"Invalid type for value {v['one_of']}\n(must be a sequence, is: {type(v['one_of'])})\n"
					"Note: no nesting is allowed below 'one_of'"
				)

		else:
			raise ValueError(
				f"stock->select->{field_name} config error\n" +
				"Invalid dict key (only 'any_of', 'all_of', 'one_of' are allowed)"
			)

	return v


def reduce_to_set(
	arg: (
		T |
		# unsure if mypy understands unions of dicts with different key literals actually
		dict[Literal['all_of', 'one_of'], Sequence[T]] |
		dict[Literal['any_of'], Sequence[T] | dict[Literal['all_of'], Sequence[T]]] |
		AllOf[T] | AnyOf[T] | OneOf[T]
	),
	in_type: tuple[type, ...] = (str, int)
) -> set[T]:
	"""
	.. sourcecode:: python\n
		for schema in (a,b,c,d,e):
			print("Schema: %s" % schema)
			print("Reduced set: %s" % reduce_to_set(schema))

		Schema: 'a'
		Reduced set: {'a'}
		Schema: {'any_of': ['a', 'b', 'c']}
		Reduced set: {'b', 'a', 'c'}
		Schema: {'all_of': [1, 2, 3]}
		Reduced set: {1, 2, 3}
		Schema: {'any_of': [{'all_of': ['a', 'b']}, 'c']}
		Reduced set: {'b', 'a', 'c'}
		Schema: {'any_of': [{'all_of': ['a', 'b']}, {'all_of': ['a', 'c']}, 'd']}
		Reduced set: {'d', 'b', 'a', 'c'}
	"""

	if isinstance(arg, in_type):
		return {arg} # type: ignore[arg-type]

	if isinstance(arg, AllOf | AnyOf | OneOf):
		v: Any = arg.dict()

	elif isinstance(arg, dict):
		v = arg

	else:
		raise ValueError(f"Unsupported arg type: {type(arg)}")

	if "any_of" in v:
		s: set[T] = set()
		for el in v['any_of']:
			if isinstance(el, in_type):
				s.add(el) # type: ignore[arg-type]
			elif isinstance(el, dict):
				for ell in next(iter(el.values())):
					s.add(ell)
			else:
				raise ValueError("unsupported format (1)")
		return s

	if 'all_of' in v:
		return set(v['all_of'])

	if 'one_of' in v:
		return set(v['one_of'])

	raise ValueError("unsupported format (2)")
