"""Public classes related to handling two-factor authentication."""

from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Generic, TypeVar

from typing_extensions import override

from findmy.util.types import MaybeCoro

from .state import LoginState

if TYPE_CHECKING:
    # noinspection PyUnresolvedReferences
    from .account import AppleAccount, AsyncAppleAccount, BaseAppleAccount

_AccType = TypeVar("_AccType", bound="BaseAppleAccount")


class BaseSecondFactorMethod(ABC, Generic[_AccType]):
    """Base class for a second-factor authentication method for an Apple account."""

    def __init__(self, account: _AccType) -> None:
        """Initialize the second-factor method."""
        self._account: _AccType = account

    @property
    def account(self) -> _AccType:
        """The account associated with the second-factor method."""
        return self._account

    @abstractmethod
    def request(self) -> MaybeCoro[None]:
        """
        Put in a request for the second-factor challenge.

        Exact meaning is up to the implementing class.
        """
        raise NotImplementedError

    @abstractmethod
    def submit(self, code: str) -> MaybeCoro[LoginState]:
        """Submit a code to complete the second-factor challenge."""
        raise NotImplementedError


class AsyncSecondFactorMethod(BaseSecondFactorMethod, ABC):
    """
    An asynchronous implementation of a second-factor authentication method.

    Intended as a base class for actual implementations to inherit from.
    """

    def __init__(self, account: "AsyncAppleAccount") -> None:
        """Initialize the second-factor method."""
        super().__init__(account)

    @property
    @override
    def account(self) -> "AsyncAppleAccount":
        """The account associated with the second-factor method."""
        return self._account

    @override
    @abstractmethod
    async def request(self) -> None:
        """See :meth:`BaseSecondFactorMethod.request`."""
        raise NotImplementedError

    @override
    @abstractmethod
    async def submit(self, code: str) -> LoginState:
        """See :meth:`BaseSecondFactorMethod.submit`."""
        raise NotImplementedError


class SyncSecondFactorMethod(BaseSecondFactorMethod, ABC):
    """
    A synchronous implementation of a second-factor authentication method.

    Intended as a base class for actual implementations to inherit from.
    """

    def __init__(self, account: "AppleAccount") -> None:
        """Initialize the second-factor method."""
        super().__init__(account)

    @property
    @override
    def account(self) -> "AppleAccount":
        """The account associated with the second-factor method."""
        return self._account

    @override
    @abstractmethod
    def request(self) -> None:
        """See :meth:`BaseSecondFactorMethod.request`."""
        raise NotImplementedError

    @override
    @abstractmethod
    def submit(self, code: str) -> LoginState:
        """See :meth:`BaseSecondFactorMethod.submit`."""
        raise NotImplementedError


class SmsSecondFactorMethod(BaseSecondFactorMethod, ABC):
    """Base class for SMS-based two-factor authentication."""

    @property
    @abstractmethod
    def phone_number_id(self) -> int:
        """The phone number's ID. You most likely don't need this."""
        raise NotImplementedError

    @property
    @abstractmethod
    def phone_number(self) -> str:
        """
        The 2FA method's phone number.

        May be masked using unicode characters; should only be used for identification purposes.
        """
        raise NotImplementedError


class TrustedDeviceSecondFactorMethod(BaseSecondFactorMethod, ABC):
    """Base class for trusted device-based two-factor authentication."""


class AsyncSmsSecondFactor(AsyncSecondFactorMethod, SmsSecondFactorMethod):
    """An async implementation of :meth:`SmsSecondFactorMethod`."""

    def __init__(
        self,
        account: "AsyncAppleAccount",
        number_id: int,
        phone_number: str,
    ) -> None:
        """
        Initialize the second factor method.

        Should not be done manually; use :meth:`AsyncAppleAccount.get_2fa_methods` instead.
        """
        super().__init__(account)

        self._phone_number_id: int = number_id
        self._phone_number: str = phone_number

    @property
    @override
    def phone_number_id(self) -> int:
        """The phone number's ID. You most likely don't need this."""
        return self._phone_number_id

    @property
    @override
    def phone_number(self) -> str:
        """
        The 2FA method's phone number.

        May be masked using unicode characters; should only be used for identification purposes.
        """
        return self._phone_number

    @override
    async def request(self) -> None:
        """Request an SMS to the corresponding phone number containing a 2FA code."""
        return await self.account.sms_2fa_request(self._phone_number_id)

    @override
    async def submit(self, code: str) -> LoginState:
        """Submit the 2FA code as received over SMS."""
        return await self.account.sms_2fa_submit(self._phone_number_id, code)


class SyncSmsSecondFactor(SyncSecondFactorMethod, SmsSecondFactorMethod):
    """A sync implementation of :meth:`SmsSecondFactorMethod`."""

    def __init__(
        self,
        account: "AppleAccount",
        number_id: int,
        phone_number: str,
    ) -> None:
        """See :meth:`AsyncSmsSecondFactor.__init__`."""
        super().__init__(account)

        self._phone_number_id: int = number_id
        self._phone_number: str = phone_number

    @property
    @override
    def phone_number_id(self) -> int:
        """See :meth:`AsyncSmsSecondFactor.phone_number_id`."""
        return self._phone_number_id

    @property
    @override
    def phone_number(self) -> str:
        """See :meth:`AsyncSmsSecondFactor.phone_number`."""
        return self._phone_number

    @override
    def request(self) -> None:
        """See :meth:`AsyncSmsSecondFactor.request`."""
        return self.account.sms_2fa_request(self._phone_number_id)

    @override
    def submit(self, code: str) -> LoginState:
        """See :meth:`AsyncSmsSecondFactor.submit`."""
        return self.account.sms_2fa_submit(self._phone_number_id, code)


class AsyncTrustedDeviceSecondFactor(AsyncSecondFactorMethod, TrustedDeviceSecondFactorMethod):
    """An async implementation of :meth:`TrustedDeviceSecondFactorMethod`."""

    @override
    async def request(self) -> None:
        return await self.account.td_2fa_request()

    @override
    async def submit(self, code: str) -> LoginState:
        return await self.account.td_2fa_submit(code)


class SyncTrustedDeviceSecondFactor(SyncSecondFactorMethod, TrustedDeviceSecondFactorMethod):
    """A sync implementation of :meth:`TrustedDeviceSecondFactorMethod`."""

    @override
    def request(self) -> None:
        """See :meth:`AsyncTrustedDeviceSecondFactor.request`."""
        return self.account.td_2fa_request()

    @override
    def submit(self, code: str) -> LoginState:
        """See :meth:`AsyncTrustedDeviceSecondFactor.submit`."""
        return self.account.td_2fa_submit(code)
