from __future__ import annotations

from typing import Any, Callable, Optional, Sequence, TypeGuard, Union, cast
from types import NotImplementedType
from pathlib import Path
from .utils import Undefined, _undef, PathType
from .config_var_def import ConfigVarDef
from .configvar_expression import (
    ConfigVarExpression,
    ConfigVarOperator,
    PathOperand,
    NumericOperand,
    StringOperand,
    UnaryOperator,
    BinaryOperator,
)
from numbers import Number
import operator as op


class ConfigVar(ConfigVarDef):
    """This class extends ConfigVarDef to add operations on ConfigVar for their composition"""

    def __init__(self, **kwargs: Any) -> None:
        super().__init__(**kwargs)

    def _numeric_binary(
        self,
        other: PathOperand | NumericOperand | StringOperand,
        operator_fn: BinaryOperator,
    ) -> ConfigVarExpression:
        """Build a SegmentedNumeric binary expression with this ConfigVar as left operand."""
        return ConfigVarExpression(self, other, operator=operator_fn)

    def _numeric_rbinary(
        self,
        other: PathOperand | NumericOperand | StringOperand,
        operator_fn: BinaryOperator,
    ) -> ConfigVarExpression:
        """Build a SegmentedNumeric binary expression with this ConfigVar as right operand."""
        return ConfigVarExpression(other, self, operator=operator_fn)

    def __truediv__(
        self, other: PathOperand | NumericOperand | StringOperand
    ) -> ConfigVarExpression | NotImplementedType:
        """allow using operator / with ConfigVar on the left.
        The true nature of the operation that can be either a number division or a path composition is delayed until
        resolution time because the VarDef type_info is not known when the operator is applied.
        """
        return self._numeric_binary(other, op.truediv)

    def __rtruediv__(
        self,
        other: PathOperand | NumericOperand | StringOperand,
    ) -> ConfigVarExpression | NotImplementedType:
        """allow using operator / with ConfigVar on the right.
        The true nature of the operation that can be either a number division or a path composition is delayed until
        resolution time because the VarDef type_info is not known when the operator is applied.
        """

        return self._numeric_rbinary(other, op.truediv)

    def __add__(
        self, other: Number | ConfigVarDef | ConfigVarExpression
    ) -> ConfigVarExpression | NotImplementedType:
        """allow using operator + with ConfigVar on the left.
        The true nature of the operation that can be either a number addition or a string concatenation is delayed until
        resolution time because the VarDef type_info is not known when the operator is applied.
        """
        return self._numeric_binary(other, op.add)

    def __radd__(
        self, other: Number | ConfigVarDef | ConfigVarExpression
    ) -> ConfigVarExpression | NotImplementedType:
        """allow using operator + with ConfigVar on the right.
        The true nature of the operation that can be either a number addition or a string concatenation is delayed until
        resolution time because the VarDef type_info is not known when the operator is applied.
        """
        return self._numeric_rbinary(other, op.add)

    def __sub__(
        self, other: Number | ConfigVarDef | ConfigVarExpression
    ) -> ConfigVarExpression | NotImplementedType:
        """allow using operator - with ConfigVar on the left.
        The validity of the operation regarding operand type is delayed until
        resolution time because the VarDef type_info is not known when the operator is applied.
        """
        return self._numeric_binary(other, op.sub)

    def __rsub__(
        self, other: Number | ConfigVarDef | ConfigVarExpression
    ) -> ConfigVarExpression | NotImplementedType:
        """allow using operator - with ConfigVar on the right.
        The validity of the operation regarding operand type is delayed until
        resolution time because the VarDef type_info is not known when the operator is applied.
        """
        return self._numeric_rbinary(other, op.sub)

    def __mul__(
        self, other: Number | ConfigVarDef | ConfigVarExpression
    ) -> ConfigVarExpression | NotImplementedType:
        """allow using operator * with ConfigVar on the left.
        The validity of the operation regarding operand type is delayed until
        resolution time because the VarDef type_info is not known when the operator is applied.
        """
        return self._numeric_binary(other, op.mul)

    def __rmul__(
        self, other: Number | ConfigVarDef | ConfigVarExpression
    ) -> ConfigVarExpression | NotImplementedType:
        """allow using operator * with ConfigVar on the right.
        The validity of the operation regarding operand type is delayed until
        resolution time because the VarDef type_info is not known when the operator is applied.
        """
        return self._numeric_rbinary(other, op.mul)

    def __floordiv__(
        self, other: Number | ConfigVarDef | ConfigVarExpression
    ) -> ConfigVarExpression | NotImplementedType:
        """allow using operator // with ConfigVar on the left.
        The validity of the operation regarding operand type is delayed until
        resolution time because the VarDef type_info is not known when the operator is applied.
        """
        return self._numeric_binary(other, op.floordiv)

    def __rfloordiv__(
        self, other: Number | ConfigVarDef | ConfigVarExpression
    ) -> ConfigVarExpression | NotImplementedType:
        """allow using operator // with ConfigVar on the right.
        The validity of the operation regarding operand type is delayed until
        resolution time because the VarDef type_info is not known when the operator is applied.
        """
        return self._numeric_rbinary(other, op.floordiv)

    def __mod__(
        self, other: Number | ConfigVarDef | ConfigVarExpression
    ) -> ConfigVarExpression | NotImplementedType:
        """allow using operator % with ConfigVar on the left.
        The validity of the operation regarding operand type is delayed until
        resolution time because the VarDef type_info is not known when the operator is applied.
        """
        return self._numeric_binary(other, op.mod)

    def __rmod__(
        self, other: Number | ConfigVarDef | ConfigVarExpression
    ) -> ConfigVarExpression | NotImplementedType:
        """allow using operator % with ConfigVar on the right.
        The validity of the operation regarding operand type is delayed until
        resolution time because the VarDef type_info is not known when the operator is applied.
        """
        return self._numeric_rbinary(other, op.mod)

    def __pow__(
        self, other: Number | ConfigVarDef | ConfigVarExpression
    ) -> ConfigVarExpression | NotImplementedType:
        """allow using operator ** with ConfigVar on the left.
        The validity of the operation regarding operand type is delayed until
        resolution time because the VarDef type_info is not known when the operator is applied.
        """
        return self._numeric_binary(other, op.pow)

    def __rpow__(
        self, other: Number | ConfigVarDef | ConfigVarExpression
    ) -> ConfigVarExpression | NotImplementedType:
        """allow using operator ** with ConfigVar on the right.
        The validity of the operation regarding operand type is delayed until
        resolution time because the VarDef type_info is not known when the operator is applied.
        """
        return self._numeric_rbinary(other, op.pow)

    def __neg__(self) -> ConfigVarExpression | NotImplementedType:
        """Return unary negation expression for ConfigVar."""
        return ConfigVarExpression(
            self,
            None,
            operator=cast(UnaryOperator, op.neg),
            is_unary=True,
        )

    def __pos__(self) -> ConfigVarExpression | NotImplementedType:
        """Return unary plus expression for ConfigVar."""
        return ConfigVarExpression(
            self,
            None,
            operator=cast(UnaryOperator, op.pos),
            is_unary=True,
        )

    def __abs__(self) -> ConfigVarExpression | NotImplementedType:
        """Return absolute value expression for ConfigVar."""
        return ConfigVarExpression(
            self,
            None,
            operator=cast(UnaryOperator, op.abs),
            is_unary=True,
        )


def configvar(
    *,
    allow_override: Optional[bool] = None,
    auto_prolog: Optional[bool] = None,
    can_be_relative_to: Optional[Path | str] = None,
    default: Any | Callable[[str, Any, Any], Any] | Undefined = _undef,
    env_keys: Optional[str | Sequence[str]] = None,
    file_keys: Optional[str | Sequence[str]] = None,
    help: str = "",
    make_dirs: Optional[PathType] = None,
    mandatory: Optional[bool] = None,
    no_conffile_search: Optional[bool] = None,
    no_dir_processing: Optional[bool] = None,
    no_env_search: Optional[bool] = None,
    no_search: Optional[bool] = None,
    no_value_search: Optional[bool] = None,
    scopes: Optional[Sequence[str]] = None,
    split_to_list: Optional[bool | str] = None,
    transform: Optional[Union[Callable[[str, Any, Any], Any], str]] = None,
    type_info: Any = _undef,
    cli_keys: Optional[str | Sequence[str]] = None,
) -> Any:
    """Create a declarative configuration variable definition.

    Use it as a class attribute on an AppConfig subclass:

        class MyCfg(AppConfig):
            my_var: int = configvar(default=1)

    The variable fully qualified name defaults to the attribute name and its type is inferred from the type hint of the attribute declaration.

    This function allows to assign a ConfigVarDef objects to an attribute definitions of any type by telling the type checkers
    that the type of the assignation is Any instead of ConfigVarDef.
    Indeed ConfigVarDef is a generic class and my_var: int = ConfigVarDef(...) or my_var: ConfigVarDef[int] = ConfigVarDef(...)
    would not be accepted by type checkers. configvar() declares returning Any which deactivates type checking.

    """

    return ConfigVar(
        allow_override=allow_override,
        auto_prolog=auto_prolog,
        can_be_relative_to=can_be_relative_to,
        default=default,
        env_keys=env_keys,
        file_keys=file_keys,
        help=help,
        make_dirs=make_dirs,
        mandatory=mandatory,
        no_conffile_search=no_conffile_search,
        no_dir_processing=no_dir_processing,
        no_env_search=no_env_search,
        no_search=no_search,
        no_value_search=no_value_search,
        scopes=scopes,
        split_to_list=split_to_list,
        transform=transform,
        type_info=type_info,
        cli_keys=cli_keys,
    )
