import abc
import typing
from distutils.version import StrictVersion

from vault_client.client.conio_user import ConioUser
from vault_client.client.types import AcceptanceType, AcceptanceChoiceType

ExternalUserReference = typing.NewType('ExternalUserReference', str)
UserLanguage = typing.NewType('UserLanguage', str)
UserCredentials = typing.NamedTuple(
    'UserCredentials',
    (
        ('external_user_reference', ExternalUserReference),
        ('password', str)
    )
)


TermsAndConditionsAcceptances = typing.NamedTuple(
    'TermsAndConditionsAcceptances',
    (
        ('accepted_marketing', bool),
        ('accepted_client_support', bool),
        ('accepted_app_improvement', bool)
    )
)


AccessToken = typing.NewType('AccessToken', str)
RefreshToken = typing.NewType('RefreshToken', str)
TokenScope = typing.NewType('TokenScope', str)
ConioUserID = typing.NewType('ConioUserID', str)
AuthenticationData = typing.NamedTuple(
    'AuthenticationData',
    (
        ('access_token', AccessToken),
        ('refresh_token', RefreshToken),
        ('current_scope', TokenScope),
    )
)

AuthenticatedUser = typing.NamedTuple(
    'AuthenticatedUser',
    (
        ('authentication_data', AuthenticationData),
        ('conio_user', ConioUser)
    )
)


class UserService(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def create_user_if_does_not_exist(
            self, credentials: UserCredentials, email: str,
            first_name: typing.Optional[str],
            last_name: typing.Optional[str]) -> ConioUser:
        pass  # pragma: no cover

    @abc.abstractmethod
    def create_user(
            self, email: str, password: str,
            device_id: str, app_version: StrictVersion, user_agent: str, device_language: str,
            acceptances: typing.Dict[AcceptanceType, AcceptanceChoiceType],
            explicit_fees_ids: typing.Sequence[str]
    ) -> ConioUser:
        pass  # pragma: no cover

    @abc.abstractmethod
    def activate_user_if_not_yet_active(
            self, credentials: UserCredentials,
            terms_and_conditions: TermsAndConditionsAcceptances,
            user_language: typing.Optional[UserLanguage]) -> AuthenticatedUser:
        pass  # pragma: no cover

    @abc.abstractmethod
    def external_user_login(self, credentials: UserCredentials, user_language: typing.Optional[UserLanguage]) -> AuthenticationData:
        pass  # pragma: no cover

    @abc.abstractmethod
    def user_login(
            self, username: str, password: str, hw_id: str,
            user_language: typing.Optional[UserLanguage]) -> AuthenticationData:
        pass  # pragma: no cover

    @abc.abstractmethod
    def escalate_token(self, access_token: str, hw_id: str) -> AuthenticationData:
        pass  # pragma: no cover

    @abc.abstractmethod
    def get_user(self, external_user_reference: ExternalUserReference) -> typing.Optional[ConioUser]:
        pass  # pragma: no cover

    @abc.abstractmethod
    def refresh_session(self, refresh_token: str) -> AuthenticationData:
        pass  # pragma: no cover

    @abc.abstractmethod
    def logout(self, access_token: str) -> None:
        pass  # pragma: no cover
