from collections.abc import Sequence
from types import MappingProxyType
from typing import overload

from pyidentity.exc import ArgumentNullError, InvalidOperationError
from pyidentity.security.authentication.scheme import AuthenticationScheme
from pyidentity.security.authentication.scheme_builder import AuthenticationSchemeBuilder
from pyidentity.security.authentication.types import ConfigureScheme

__all__ = ("AuthenticationOptions",)


class AuthenticationOptions:
    __slots__ = (
        "__scheme_map",
        "default_authentication_scheme",
        "default_scheme",
        "default_sign_in_scheme",
        "default_sign_out_scheme",
        "required_authenticated_signin",
    )

    def __init__(self) -> None:
        self.__scheme_map: dict[str, AuthenticationScheme] = {}
        self.default_scheme: str = ""
        """Used as the fallback default scheme for all the other defaults."""
        self.default_authentication_scheme: str = ""
        """Used as the default scheme by *authenticate(HttpContext, str)*."""
        self.default_sign_in_scheme: str = ""
        """Used as the default scheme *sign_in(HttpContext, str, ClaimsPrincipal, dict[str, ...])*."""
        self.default_sign_out_scheme: str = ""
        """Used as the default scheme by *sign_out(HttpContext, str, dict[str, ...])*."""
        self.required_authenticated_signin: bool = True
        """If true, sign_in should throw if attempted with a user is not authenticated.
        A user is considered authenticated if *is_authenticated* returns *True* for the *ClaimsPrincipal*
        associated with the HTTP request."""

    @property
    def scheme_map(self) -> MappingProxyType[str, AuthenticationScheme]:
        """Maps schemes by name."""
        return MappingProxyType(self.__scheme_map)

    @property
    def schemes(self) -> Sequence[AuthenticationScheme]:
        """Returns the schemes in the order they were added."""
        return tuple(s for s in self.__scheme_map.values())

    @overload
    def add_scheme(self, name: str, scheme: AuthenticationScheme, /) -> None: ...

    @overload
    def add_scheme(self, name: str, configure_builder: ConfigureScheme, /) -> None: ...

    def add_scheme(
        self,
        name: str,
        scheme_or_configure_builder: AuthenticationScheme | ConfigureScheme,
        /,
    ) -> None:
        """Add an authentication scheme or built from a delegate with the provided name.

        :param name: The name of the scheme.
        :param scheme_or_configure_builder: The scheme or the configure scheme.
        :return:
        """
        if name is None:
            raise ArgumentNullError("name")
        if scheme_or_configure_builder is None:
            raise ArgumentNullError("scheme_or_configure_builder")

        if isinstance(scheme_or_configure_builder, AuthenticationScheme):
            scheme = scheme_or_configure_builder
        elif callable(scheme_or_configure_builder):
            builder = AuthenticationSchemeBuilder(name)
            scheme_or_configure_builder(builder)
            scheme = builder.build()
        else:
            raise InvalidOperationError("Unsupported function arguments.")

        if name in self.__scheme_map:
            raise InvalidOperationError(f"Scheme already exists: {name}.")

        self.__scheme_map[name] = scheme
