import unittest
import uuid
from unittest import mock
from unittest.mock import patch

# noinspection PyPackageRequirements
import requests
from bithustler_client.client.client import BithustlerClient
from bithustler_client.generated_protobuf import bithustler_pb2
from cards_client.client.client import CardsClient
from cards_client.generated_protobuf import base_pb2
# noinspection PyPackageRequirements
from requests import HTTPError, Response
from vault_client.client.auth_client import VaultAuthClient
from vault_client.client.client_definition import VaultClient
from vault_client.client.conio_user import ConioUser
from vault_client.client.types import AcceptanceType, AcceptanceChoiceType, UserStatus
from vault_client.generated_protobuf import vault_pb2

from conio_sdk.common import exceptions
from conio_sdk.services.conio.user.user_service import UserCredentials, TermsAndConditionsAcceptances, UserLanguage
from conio_sdk.services.conio.user.vault_service import VaultService
from etc.settings import VaultAuthCredentials


class TestUserServiceCreateIfNotExists(unittest.TestCase):
    def _change_user_info(self, user_info: dict):
        self.user_info = user_info
        self.pb2_user = vault_pb2.User(**self.user_info)
        self.user = ConioUser(data=self.user_info)

    def setUp(self):
        self.external_reference = 'an_external_reference'
        self._change_user_info({
            'reference_key_id': 'a reference key id',
            'external_references': [self.external_reference],
            'status': vault_pb2.LOCKED
        })
        self.vault_auth_credentials = \
            VaultAuthCredentials(
                'http://test.vault.com',
                'vault_auth_username',
                'vault_auth_password'
            )
        self.cards_client = mock.create_autospec(CardsClient)
        self.payer = base_pb2.MsgCreatePayerResponse()
        self.payer.data.payer_id = 'a payer id'
        self.cards_client.create_payer.return_value = self.payer
        self.bithustler_client = mock.create_autospec(BithustlerClient)
        self.seller = bithustler_pb2.Seller(id='a seller id')
        self.bithustler_client.create_seller.return_value = self.seller
        self.vault_client = mock.create_autospec(VaultClient)
        self.vault_auth_client = mock.create_autospec(VaultAuthClient)
        self.sut = VaultService(
            self.vault_client,
            self.vault_auth_client,
            self.vault_auth_credentials,
            self.cards_client,
            self.bithustler_client
        )

    def test_create_new_user(self):
        self._test_create_new_user(False, False)

    def test_create_new_user_payer_exists(self):
        self._test_create_new_user(True, False)

    def test_create_new_user_seller_exists(self):
        self._test_create_new_user(False, True)

    def test_create_new_user_payer_and_seller_exist(self):
        self._test_create_new_user(True, True)

    @patch.object(requests, 'post')
    def _test_create_new_user(self, payer_exists: bool, seller_exists: bool, post_mock):
        if payer_exists or seller_exists:
            user_info = dict(self.user_info)
            if payer_exists:
                user_info['cards_integration_id'] = 'a_payer_id'
            if seller_exists:
                user_info['seller_id'] = 'a_seller_id'
            self._change_user_info(user_info)

        def _create_new_user(*a, **kw):
            self.vault_client.get_user_info_by_attributes.return_value = self.pb2_user

        self.vault_client.get_user_info_by_attributes.return_value = None
        self.vault_client.create_user_v2.side_effect = _create_new_user

        first_name = str(uuid.uuid4())
        last_name = str(uuid.uuid4())
        self.sut.create_user_if_does_not_exist(
            UserCredentials(self.external_reference, 'a_password'),
            'an_email@test_conio.com',
            first_name, last_name
        )
        self.sut.activate_user_if_not_yet_active(
            UserCredentials(self.external_reference, 'a_password'),
            TermsAndConditionsAcceptances(True, False, True),
            UserLanguage('it')
        )
        self.vault_client.get_user_info_by_attributes.assert_has_calls(
            [
                mock.call(external_reference=self.external_reference),
                mock.call(external_reference=self.external_reference)
                ]
        )
        self.vault_client.create_user_v2.assert_called_once_with(
            {
                'external_references': [self.external_reference],
                'password': 'a_password',
                'email': 'an_email@test_conio.com',
                'first_name': first_name,
                'last_name': last_name
                # 'acceptances': {
                #     AcceptanceType.MARKETING: AcceptanceChoiceType.ACCEPTED,
                #     AcceptanceType.APP_IMPROVEMENT: AcceptanceChoiceType.ACCEPTED,
                #     AcceptanceType.CLIENT_SUPPORT: AcceptanceChoiceType.REJECTED,
                # }
            }
        )
        not seller_exists and self.bithustler_client.create_seller.assert_called_once_with(self.user.reference_key_id)
        not payer_exists and self.cards_client.create_payer.assert_called_once_with(
            base_pb2.MsgCreatePayer(payer_descr=self.user.reference_key_id)
        )

        calls = [
            mock.call({'status': UserStatus.ACTIVE.value}, reference_user_id=self.user.reference_key_id)
        ]
        if not payer_exists or not seller_exists:
            data = {}
            if not payer_exists:
                data['cards_integration_id'] = self.payer.data.payer_id
            if not seller_exists:
                data['seller_id'] = self.seller.id
            calls.append(mock.call(data, self.user.reference_key_id))
        self.vault_client.save_user_info_v2.assert_has_calls(calls, any_order=False)

    def test_create_new_user_payer_failed(self):
        self._test_create_new_user_failed(True, False)

    def test_create_new_user_seller_failed(self):
        self._test_create_new_user_failed(False, True)

    @patch.object(requests, 'post')
    def _test_create_new_user_failed(
            self, payer_fails: bool, seller_fails: bool, post_mock):
        def _create_new_user(*a, **kw):
            self.vault_client.get_user_info_by_attributes.return_value = self.pb2_user

        self.vault_client.get_user_info_by_attributes.return_value = None
        self.vault_client.create_user_v2.side_effect = _create_new_user
        if payer_fails:
            response = Response()
            response.status_code = 500
            self.cards_client.create_payer.side_effect = HTTPError(response=response)
        if seller_fails:
            response = Response()
            response.status_code = 500
            self.bithustler_client.create_seller.side_effect = HTTPError(response=response)
        exception = exceptions.CardsServiceCouldNotCreatePayerException if payer_fails\
            else exceptions.BithustlerServiceCouldNotCreateSellerException

        first_name = str(uuid.uuid4())
        last_name = str(uuid.uuid4())
        with self.assertRaises(exception):
            self.sut.create_user_if_does_not_exist(
                UserCredentials(self.external_reference, 'a_password'),
                'an_email@test_conio.com',
                first_name, last_name
            )
            self.sut.activate_user_if_not_yet_active(
                UserCredentials(self.external_reference, 'a_password'),
                TermsAndConditionsAcceptances(True, False, True),
                UserLanguage('it')
            )
        self.vault_client.get_user_info_by_attributes.assert_has_calls(
            [
                mock.call(external_reference=self.external_reference),
                mock.call(external_reference=self.external_reference)
            ]
        )
        self.vault_client.create_user_v2.assert_called_once_with(
            {
                'external_references': [self.external_reference],
                'password': 'a_password',
                'email': 'an_email@test_conio.com',
                'first_name': first_name,
                'last_name': last_name
                # 'acceptances': {
                #     AcceptanceType.MARKETING: AcceptanceChoiceType.ACCEPTED,
                #     AcceptanceType.APP_IMPROVEMENT: AcceptanceChoiceType.ACCEPTED,
                #     AcceptanceType.CLIENT_SUPPORT: AcceptanceChoiceType.REJECTED,
                # }
            }
        )
        self.cards_client.create_payer.assert_called_once_with(
            base_pb2.MsgCreatePayer(payer_descr=self.user.reference_key_id)
        )
        not payer_fails and self.bithustler_client.create_seller.assert_called_once_with(self.user.reference_key_id)

        self.vault_client.save_user_info_v2.assert_has_calls(
            [
                mock.call({'status': UserStatus.ACTIVE.value}, reference_user_id=self.user.reference_key_id)
            ], any_order=False)

    def test_create_existing_user(self):
        self._test_create_existing_user(False, False)

    def test_create_existing_user_existing_payer(self):
        self._test_create_existing_user(True, False)

    def test_create_existing_user_existing_seller(self):
        self._test_create_existing_user(False, True)

    def test_create_existing_user_existing_payer_existing_seller(self):
        self._test_create_existing_user(True, True)

    @patch.object(requests, 'post')
    def _test_create_existing_user(self, payer_exists: bool, seller_exists: bool, post_mock):
        if payer_exists or seller_exists:
            user_info = dict(self.user_info)
            if payer_exists:
                user_info['cards_integration_id'] = 'a_payer_id'
            if seller_exists:
                user_info['seller_id'] = 'a_seller_id'
            self._change_user_info(user_info)

        self.vault_client.get_user_info_by_attributes.return_value = self.pb2_user

        first_name = str(uuid.uuid4())
        last_name = str(uuid.uuid4())
        self.sut.create_user_if_does_not_exist(
            UserCredentials(self.external_reference, 'a_password'),
            'an_email@test_conio.com',
            first_name, last_name
        )
        self.sut.activate_user_if_not_yet_active(
            UserCredentials(self.external_reference, 'a_password'),
            TermsAndConditionsAcceptances(True, False, True),
            UserLanguage('it')
        )
        self.vault_client.get_user_info_by_attributes.assert_has_calls(
            [mock.call(external_reference=self.external_reference)] * 2
        )
        self.vault_client.create_user_v2.assert_not_called()

        not seller_exists and self.bithustler_client.create_seller.assert_called_once_with(self.user.reference_key_id)
        not payer_exists and self.cards_client.create_payer.assert_called_once_with(
            base_pb2.MsgCreatePayer(payer_descr=self.user.reference_key_id)
        )

        calls = []
        if not payer_exists or not seller_exists:
            data = {}
            if not payer_exists:
                data['cards_integration_id'] = self.payer.data.payer_id
            if not seller_exists:
                data['seller_id'] = self.seller.id
            calls.append(mock.call(data, self.user.reference_key_id))
        calls.append(
            mock.call(
                {
                    'acceptances': {
                        AcceptanceType.MARKETING: AcceptanceChoiceType.ACCEPTED,
                        AcceptanceType.APP_IMPROVEMENT: AcceptanceChoiceType.ACCEPTED,
                        AcceptanceType.CLIENT_SUPPORT: AcceptanceChoiceType.REJECTED,
                    }
                },
                reference_user_id=self.user.reference_key_id)
        )
        self.vault_client.save_user_info_v2.assert_has_calls(calls, any_order=False)

    @patch.object(requests, 'post')
    def test_create_user_existing_wrong_password(self, post_mock):
        user_info = dict(self.user_info)
        user_info['status'] = vault_pb2.ACTIVE
        self._change_user_info(user_info)

        def _post(url: str, *_, **__):
            self.assertEqual('http://test.vault.com/token', url)
            response = Response()
            response.status_code=401
            return response
        post_mock.side_effect = _post
        self.vault_client.get_user_info_by_attributes.return_value = self.pb2_user
        first_name = str(uuid.uuid4())
        last_name = str(uuid.uuid4())

        with self.assertRaises(exceptions.NotAuthenticatedException):
            self.sut.create_user_if_does_not_exist(
                UserCredentials(self.external_reference, 'a_password'),
                'an_email@test_conio.com',
                first_name, last_name
            )
            self.sut.activate_user_if_not_yet_active(
                UserCredentials(self.external_reference, 'a_password'),
                TermsAndConditionsAcceptances(True, False, True),
                UserLanguage('it')
            )
        self.vault_client.get_user_info_by_attributes.assert_has_calls(
            [mock.call(external_reference=self.external_reference)] * 2
        )
        self.vault_client.create_user_v2.assert_not_called()
        self.bithustler_client.create_seller.assert_not_called()
        self.cards_client.create_payer.assert_not_called()
        self.vault_client.save_user_info_v2.assert_has_calls(
            [
                mock.call(
                    {
                        'email': 'an_email@test_conio.com',
                        'first_name': first_name,
                        'last_name': last_name
                    },
                    reference_user_id=self.user.reference_key_id)
            ])

    @patch.object(requests, 'post')
    def test_create_user_wrong_token(self, post_mock):
        user_info = dict(self.user_info)
        user_info['status'] = vault_pb2.ACTIVE
        self._change_user_info(user_info)

        def _post(url: str, *_, **__):
            self.assertEqual('http://test.vault.com/token', url)
            response = Response()
            response.status_code = 400
            return response

        post_mock.side_effect = _post
        self.vault_client.get_user_info_by_attributes.return_value = self.pb2_user
        first_name = str(uuid.uuid4())
        last_name = str(uuid.uuid4())
        with self.assertRaises(exceptions.InvalidTokenException):
            self.sut.create_user_if_does_not_exist(
                UserCredentials(self.external_reference, 'a_password'),
                'an_email@test_conio.com',
                first_name, last_name
            )
            self.sut.activate_user_if_not_yet_active(
                UserCredentials(self.external_reference, 'a_password'),
                TermsAndConditionsAcceptances(True, False, True),
                UserLanguage('it')
            )
        self.vault_client.get_user_info_by_attributes.assert_has_calls(
            [mock.call(external_reference=self.external_reference)] * 2
        )
        self.vault_client.create_user_v2.assert_not_called()
        self.bithustler_client.create_seller.assert_not_called()
        self.cards_client.create_payer.assert_not_called()
        self.vault_client.save_user_info_v2.assert_has_calls(
            [
                mock.call(
                    {
                        'email': 'an_email@test_conio.com',
                        'first_name': first_name,
                        'last_name': last_name
                    },
                    reference_user_id=self.user.reference_key_id)
            ], any_order=False)
