import base64
import binascii
import datetime
import os
import uuid

from conio_sdk.generated_protobuf import v1_pb2
from conio_sdk.ioc import ioc_vault, ioc_bithustler
from test.integration.v1.base_integration_test import BaseV1IntegrationTest


class TestSignup(BaseV1IntegrationTest):
    def test_full_signup(self):
        self._test_full_signup(False)

    def test_full_signup_with_first_last_name(self):
        self._test_full_signup(True)

    def _test_full_signup(self, with_first_name: bool):
        now = datetime.datetime.now()
        if with_first_name:
            first_name = 'First Name {}'.format(now)
            last_name = 'Last Name {}'.format(now)
            signup = self.create_signup_message(first_name=first_name, last_name=last_name)
        else:
            first_name = last_name = ''
            signup = self.create_signup_message()
        resp = self.client.post(
            '/v1/signup',
            data=signup.SerializeToString(),
            headers={'Content-Type': 'application/octet-stream'})
        self.assertEqual(200, resp.status_code)

        self._check_user_level(signup.conioCredentials.externalUserID)
        login = self.create_login_message(
            signup.conioCredentials.externalUserID,
            signup.conioCredentials.hashedConioPassword)
        resp = self.client.post(
            '/v1/login',
            data=login.SerializeToString(),
            headers={'Content-Type': 'application/octet-stream'})
        self.assertEqual(200, resp.status_code)
        login_response = v1_pb2.LoginResponse()
        login_response.ParseFromString(base64.b64decode(resp.content))
        headers = {
            'Authorization': 'Bearer {}'.format(resp.headers['X-ConioAccessToken'])
        }
        refresh_headers = {
            'Authorization': 'Bearer {}'.format(resp.headers['X-ConioRefreshToken'])
        }

        assert signup.tc == login_response.tc
        assert signup.encryptedUserKey == login_response.encryptedUserKey
        resp = self.client.post('/v1/test', headers=headers)
        self.assertEqual(200, resp.status_code)
        resp = self.client.post('/v1/refresh_token', headers=headers)
        self.assertEqual(403, resp.status_code)
        resp = self.client.post('/v1/refresh_token', headers=refresh_headers)
        self.assertEqual(200, resp.status_code)
        resp = self.client.post('/v1/logout', headers=headers)
        self.assertEqual(200, resp.status_code)
        resp = self.client.post('/v1/test', headers=headers)
        self.assertEqual(401, resp.status_code)
        self.verify_user_limits(signup.conioCredentials.externalUserID)
        user = ioc_vault.vault_client.get_user_info_by_attributes(
            external_reference=signup.conioCredentials.externalUserID
        )
        sell_methods = ioc_bithustler.bithustler_client.get_sell_methods(user.seller_id)
        self.assertEqual(1, len(sell_methods))
        self.assertEqual(signup.cryptoRequest.iban, sell_methods[0].iban_bank_account.iban)
        self.assertEqual(first_name, user.first_name)
        self.assertEqual(last_name, user.last_name)

    def test_signup_with_first_name_last_name_set(self):
        signup = self.create_signup_message()
        external_user_id = signup.conioCredentials.externalUserID
        hashed_conio_password = signup.conioCredentials.hashedConioPassword
        resp = self.client.post(
            '/v1/signup',
            data=signup.SerializeToString(),
            headers={'Content-Type': 'application/octet-stream'})
        self.assertEqual(200, resp.status_code)
        signup_response = v1_pb2.SignupResponse()
        signup_response.ParseFromString(base64.b64decode(resp.content))

        user = ioc_vault.vault_client.get_user_info_by_attributes(
            external_reference=external_user_id
        )
        self.assertEqual('', user.first_name)
        self.assertEqual('', user.last_name)
        first_name = 'First name 2 {}'.format(datetime.datetime.now())
        last_name = 'Last name 2 {}'.format(datetime.datetime.now())
        signup = self.create_signup_message(
            email=signup.cryptoRequest.email,
            first_name=first_name,
            last_name=last_name
        )
        resp = self.client.post(
            '/v1/signup',
            data=signup.SerializeToString(),
            headers={'Content-Type': 'application/octet-stream'})
        self.assertEqual(409, resp.status_code)

        signup = self.create_signup_message(
            email=signup.cryptoRequest.email,
            first_name=first_name,
            last_name=last_name,
            hashed_conio_password=hashed_conio_password,
            external_user_id=external_user_id
        )
        for _ in range(2):
            resp = self.client.post(
                '/v1/signup',
                data=signup.SerializeToString(),
                headers={'Content-Type': 'application/octet-stream'})
            self.assertEqual(
                200, resp.status_code,
                msg='User external reference: {}'.format(signup.conioCredentials.externalUserID)
            )
            signup_response2 = v1_pb2.SignupResponse()
            signup_response2.ParseFromString(base64.b64decode(resp.content))
            self.assertEqual(signup_response, signup_response2)
            user = ioc_vault.vault_client.get_user_info_by_attributes(
                external_reference=external_user_id
            )
            self.assertEqual(first_name, user.first_name)
            self.assertEqual(last_name, user.last_name)

    def test_signup_with_first_name_last_name_change(self):
        first_name = 'First name 2 {}'.format(datetime.datetime.now())
        last_name = 'Last name 2 {}'.format(datetime.datetime.now())

        signup = self.create_signup_message(
            first_name=first_name,
            last_name=last_name
        )
        hashed_conio_password = signup.conioCredentials.hashedConioPassword
        external_user_id = signup.conioCredentials.externalUserID
        resp = self.client.post(
            '/v1/signup',
            data=signup.SerializeToString(),
            headers={'Content-Type': 'application/octet-stream'})
        self.assertEqual(
            200, resp.status_code,
            msg='User external reference: {}'.format(signup.conioCredentials.externalUserID)
        )
        signup_response = v1_pb2.SignupResponse()
        signup_response.ParseFromString(base64.b64decode(resp.content))
        user = ioc_vault.vault_client.get_user_info_by_attributes(
            external_reference=external_user_id
        )
        self.assertEqual(first_name, user.first_name)
        self.assertEqual(last_name, user.last_name)

        first_name = 'First name 3 {}'.format(datetime.datetime.now())
        last_name = 'Last name 3 {}'.format(datetime.datetime.now())
        signup = self.create_signup_message(
            email=signup.cryptoRequest.email,
            first_name=first_name,
            last_name=last_name,
            hashed_conio_password=hashed_conio_password,
            external_user_id=external_user_id
        )
        resp = self.client.post(
            '/v1/signup',
            data=signup.SerializeToString(),
            headers={'Content-Type': 'application/octet-stream'})
        self.assertEqual(
            200, resp.status_code,
            msg='User external reference: {}'.format(signup.conioCredentials.externalUserID)
        )
        signup_response = v1_pb2.SignupResponse()
        signup_response.ParseFromString(base64.b64decode(resp.content))
        user = ioc_vault.vault_client.get_user_info_by_attributes(
            external_reference=external_user_id
        )
        self.assertEqual(first_name, user.first_name)
        self.assertEqual(last_name, user.last_name)
    
    def test_multiple_signup(self):
        signup = self.create_signup_message()
        external_user_id = signup.conioCredentials.externalUserID

        resp = self.client.post(
            '/v1/signup',
            data=signup.SerializeToString(),
            headers={'Content-Type': 'application/octet-stream'})
        self.assertEqual(200, resp.status_code)
        user = ioc_vault.vault_client.get_user_info_by_attributes(
            external_reference=external_user_id
        )
        self.assertEqual('', user.first_name)
        self.assertEqual('', user.last_name)

        signup_response = v1_pb2.SignupResponse()
        signup_response.ParseFromString(base64.b64decode(resp.content))
        resp = self.client.post(
            '/v1/signup',
            data=signup.SerializeToString(),
            headers={'Content-Type': 'application/octet-stream'})
        self.assertEqual(200, resp.status_code)
        signup_response2 = v1_pb2.SignupResponse()
        signup_response2.ParseFromString(base64.b64decode(resp.content))
        self.assertEqual(signup_response, signup_response2)
        user = ioc_vault.vault_client.get_user_info_by_attributes(
            external_reference=signup.conioCredentials.externalUserID
        )
        self.assertEqual('', user.first_name)
        self.assertEqual('', user.last_name)

        signup_in_conflict = v1_pb2.Signup()
        signup_in_conflict.MergeFrom(signup)
        signup_in_conflict.conioCredentials.externalUserID = str(uuid.uuid4())
        resp = self.client.post(
            '/v1/signup',
            data=signup_in_conflict.SerializeToString(),
            headers={'Content-Type': 'application/octet-stream'})
        self.assertEqual(409, resp.status_code)

        signup_in_conflict = v1_pb2.Signup()
        signup_in_conflict.MergeFrom(signup)
        signup_in_conflict.conioCredentials.hashedConioPassword = binascii.hexlify(os.urandom(32)).decode()
        resp = self.client.post(
            '/v1/signup',
            data=signup_in_conflict.SerializeToString(),
            headers={'Content-Type': 'application/octet-stream'})
        self.assertEqual(403, resp.status_code)

    def test_signup_invalid_signature(self):
        signup = self.create_signup_message()
        signup.cryptoRequest.cryptoProof = os.urandom(32)
        resp = self.client.post(
            '/v1/signup',
            data=signup.SerializeToString(),
            headers={'Content-Type': 'application/octet-stream'})
        self.assertEqual(403, resp.status_code)

    def test_signup_expiration(self):
        signup = self.create_signup_message(expired=True)
        resp = self.client.post(
            '/v1/signup',
            data=signup.SerializeToString(),
            headers={'Content-Type': 'application/octet-stream'})
        self.assertEqual(403, resp.status_code)

    def test_signup_with_language(self):
        now = datetime.datetime.now()
        first_name = 'First Name {}'.format(now)
        last_name = 'Last Name {}'.format(now)
        signup = self.create_signup_message(first_name=first_name, last_name=last_name)
        external_user_id = signup.conioCredentials.externalUserID
        resp = self.client.post(
            '/v1/signup',
            data=signup.SerializeToString(),
            headers={'Content-Type': 'application/octet-stream'})
        self.assertEqual(200, resp.status_code)

        login = self.create_login_message(
            signup.conioCredentials.externalUserID,
            signup.conioCredentials.hashedConioPassword)
        resp = self.client.post(
            '/v1/login',
            data=login.SerializeToString(),
            headers={
                'Content-Type': 'application/octet-stream',
                'user-language': 'it'
            })
        self.assertEqual(200, resp.status_code)
        user = ioc_vault.vault_client.get_user_info_by_attributes(
            external_reference=external_user_id
        )
        self.assertEqual('IT', user.lang)
