import uuid
from time import sleep
from unittest.mock import patch

import stripe
from celery.result import AsyncResult
from django.conf import settings
from django.http.request import HttpRequest
from django.test import Client
from django.test import TestCase, override_settings
from unittest import skipIf
from model_mommy import mommy

from datacenterlight.models import StripePlan
from membership.models import StripeCustomer
from utils.stripe_utils import StripeUtils
from .tasks import save_ssh_key


class BaseTestCase(TestCase):
    """
    Base class to initialize the test cases
    """

    def setUp(self):
        # Password
        self.dummy_password = 'test_password'

        # Users
        self.customer, self.another_customer = mommy.make(
            'membership.CustomUser', validated=1, _quantity=2
        )
        self.customer.set_password(self.dummy_password)
        self.customer.save()
        self.another_customer.set_password(self.dummy_password)
        self.another_customer.save()

        # Stripe mocked data
        self.stripe_mocked_customer = self.customer_stripe_mocked_data()

        #  Clients
        self.customer_client = self.get_client(self.customer)
        self.another_customer_client = self.get_client(self.another_customer)

        # Request Object
        self.request = HttpRequest()
        self.request.META['SERVER_NAME'] = 'ungleich.ch'
        self.request.META['SERVER_PORT'] = '80'

    def get_client(self, user):
        """
        Authenticate a user and return the client
        """
        client = Client()
        client.login(email=user.email, password=self.dummy_password)
        return client

    def customer_stripe_mocked_data(self):
        return {
            "id": "cus_8R1y9UWaIIjZqr",
            "object": "customer",
            "currency": "usd",
            "default_source": "card_18A9up2eZvKYlo2Cq2RJMGeF",
            "email": "vmedixtodd+1@gmail.com",
            "livemode": False,
            "metadata": {
            },
            "shipping": None,
            "sources": {
                "object": "list",
                "data": [{
                    "id": "card_18A9up2eZvKYlo2Cq2RJMGeF",
                    "object": "card",
                    "brand": "Visa",
                    "country": "US",
                    "customer": "cus_8R1y9UWaIIjZqr",
                    "cvc_check": "pass",
                    "dynamic_last4": None,
                    "exp_month": 12,
                    "exp_year": 2018,
                    "funding": "credit",
                    "last4": "4242",
                }]
            }
        }

    def setup_view(self, view, *args, **kwargs):
        """Mimic as_view() returned callable, but returns view instance.

        args and kwargs are the same you would pass to ``reverse()``

        """
        view.request = self.request
        view.args = args
        view.kwargs = kwargs
        view.config = None
        return view


@skipIf(settings.STRIPE_API_PRIVATE_KEY_TEST is None or
        settings.STRIPE_API_PRIVATE_KEY_TEST is "",
        """Skip because STRIPE_API_PRIVATE_KEY_TEST is not set""")
class TestStripeCustomerDescription(TestCase):
    """
    A class to test setting the description field of the stripe customer
    https://stripe.com/docs/api#metadata
    """

    def setUp(self):
        self.customer_password = 'test_password'
        self.customer_email = 'test@ungleich.ch'
        self.customer_name = "Monty Python"
        self.customer = mommy.make('membership.CustomUser')
        self.customer.set_password(self.customer_password)
        self.customer.email = self.customer_email
        self.customer.save()
        self.stripe_utils = StripeUtils()
        stripe.api_key = settings.STRIPE_API_PRIVATE_KEY_TEST
        self.token = stripe.Token.create(
            card={
                "number": '4111111111111111',
                "exp_month": 12,
                "exp_year": 2022,
                "cvc": '123'
            },
        )
        self.failed_token = stripe.Token.create(
            card={
                "number": '4000000000000341',
                "exp_month": 12,
                "exp_year": 2022,
                "cvc": '123'
            },
        )

    def test_creating_stripe_customer(self):
        stripe_data = self.stripe_utils.create_customer(self.token.id,
                                                        self.customer.email,
                                                        self.customer_name)
        self.assertEqual(stripe_data.get('error'), None)
        customer_data = stripe_data.get('response_object')
        self.assertEqual(customer_data.description, self.customer_name)


@skipIf(settings.STRIPE_API_PRIVATE_KEY_TEST == "" or
        settings.TEST_MANAGE_SSH_KEY_HOST == "",
        """Skipping test_save_ssh_key_add because either host
         or public key were not specified or were empty""")
class StripePlanTestCase(TestStripeCustomerDescription):
    """
    A class to test Stripe plans
    """

    def test_get_stripe_plan_id_string(self):
        plan_id_string = StripeUtils.get_stripe_plan_id(cpu=2, ram=20, ssd=100,
                                                        version=1, app='dcl')
        self.assertEqual(plan_id_string, 'dcl-v1-cpu-2-ram-20gb-ssd-100gb')
        plan_id_string = StripeUtils.get_stripe_plan_id(cpu=2, ram=20, ssd=100,
                                                        version=1, app='dcl',
                                                        hdd=200)
        self.assertEqual(plan_id_string,
                         'dcl-v1-cpu-2-ram-20gb-ssd-100gb-hdd-200gb')

    def test_get_or_create_plan(self):
        stripe_plan = self.stripe_utils.get_or_create_stripe_plan(2000,
                                                                  "test plan 1",
                                                                  stripe_plan_id='test-plan-1')
        self.assertIsNone(stripe_plan.get('error'))
        self.assertIsInstance(stripe_plan.get('response_object'), StripePlan)

    @skipIf(settings.TEST_MANAGE_SSH_KEY_PUBKEY == "" or
            settings.TEST_MANAGE_SSH_KEY_HOST == "",
            """Skipping test_save_ssh_key_add because either host
             or public key were not specified or were empty""")
    @patch('utils.stripe_utils.logger')
    def test_create_duplicate_plans_error_handling(self, mock_logger):
        """
        Test details:
            1. Create a test plan in Stripe with a particular id
            2. Try to recreate the plan with the same id
            3. This creates a Stripe error, the code should be able to handle the error

        :param mock_logger:
        :return:
        """
        unique_id = str(uuid.uuid4().hex)
        new_plan_id_str = 'test-plan-{}'.format(unique_id)
        stripe_plan = self.stripe_utils.get_or_create_stripe_plan(2000,
                                                                  "test plan {}".format(
                                                                      unique_id),
                                                                  stripe_plan_id=new_plan_id_str)
        self.assertIsInstance(stripe_plan.get('response_object'), StripePlan)
        self.assertEqual(stripe_plan.get('response_object').stripe_plan_id,
                         new_plan_id_str)

        # Test creating the same plan again and expect the PLAN_EXISTS_ERROR_MSG
        # We first delete the local Stripe Plan, so that the code tries to create a new plan in Stripe
        StripePlan.objects.filter(
            stripe_plan_id=new_plan_id_str).all().delete()
        stripe_plan_1 = self.stripe_utils.get_or_create_stripe_plan(2000,
                                                                    "test plan {}".format(
                                                                        unique_id),
                                                                    stripe_plan_id=new_plan_id_str)
        mock_logger.debug.assert_called_with(
            self.stripe_utils.PLAN_EXISTS_ERROR_MSG.format(new_plan_id_str))
        self.assertIsInstance(stripe_plan_1.get('response_object'), StripePlan)
        self.assertEqual(stripe_plan_1.get('response_object').stripe_plan_id,
                         new_plan_id_str)

        # Delete the test stripe plan that we just created
        delete_result = self.stripe_utils.delete_stripe_plan(new_plan_id_str)
        self.assertIsInstance(delete_result, dict)
        self.assertEqual(delete_result.get('response_object'), True)

    @patch('utils.stripe_utils.logger')
    def test_delete_unexisting_plan_should_fail(self, mock_logger):
        plan_id = 'crazy-plan-id-that-does-not-exist'
        result = self.stripe_utils.delete_stripe_plan(plan_id)
        self.assertIsInstance(result, dict)
        self.assertEqual(result.get('response_object'), False)
        mock_logger.debug.assert_called_with(
            self.stripe_utils.PLAN_DOES_NOT_EXIST_ERROR_MSG.format(plan_id))

    def test_subscribe_customer_to_plan(self):
        stripe_plan = self.stripe_utils.get_or_create_stripe_plan(2000,
                                                                  "test plan 1",
                                                                  stripe_plan_id='test-plan-1')
        stripe_customer = StripeCustomer.get_or_create(
            email=self.customer_email,
            token=self.token)
        result = self.stripe_utils.subscribe_customer_to_plan(
            stripe_customer.stripe_id,
            [{"plan": stripe_plan.get(
                'response_object').stripe_plan_id}])
        self.assertIsInstance(result.get('response_object'),
                              stripe.Subscription)
        self.assertIsNone(result.get('error'))
        self.assertEqual(result.get('response_object').get('status'), 'active')

    def test_subscribe_customer_to_plan_failed_payment(self):
        stripe_plan = self.stripe_utils.get_or_create_stripe_plan(2000,
                                                                  "test plan 1",
                                                                  stripe_plan_id='test-plan-1')
        stripe_customer = StripeCustomer.get_or_create(
            email=self.customer_email,
            token=self.failed_token)
        result = self.stripe_utils.subscribe_customer_to_plan(
            stripe_customer.stripe_id,
            [{"plan": stripe_plan.get(
                'response_object').stripe_plan_id}])
        self.assertIsNone(result.get('response_object'), None)
        self.assertIsNotNone(result.get('error'))


class SaveSSHKeyTestCase(TestCase):
    """
    A test case to test the celery save_ssh_key task
    """

    @override_settings(
        task_eager_propagates=True,
        task_always_eager=True,
    )
    def setUp(self):
        self.public_key = settings.TEST_MANAGE_SSH_KEY_PUBKEY
        self.hosts = settings.TEST_MANAGE_SSH_KEY_HOST

    @skipIf(settings.TEST_MANAGE_SSH_KEY_PUBKEY is "" or
            settings.TEST_MANAGE_SSH_KEY_PUBKEY is None or
            settings.TEST_MANAGE_SSH_KEY_HOST is "" or
            settings.TEST_MANAGE_SSH_KEY_HOST is None,
            """Skipping test_save_ssh_key_add because either host
             or public key were not specified or were empty""")
    def test_save_ssh_key_add(self):
        async_task = save_ssh_key.delay([self.hosts],
                                        [{'value': self.public_key,
                                          'state': True}])
        save_ssh_key_result = None
        for i in range(0, 10):
            sleep(5)
            res = AsyncResult(async_task.task_id)
            if type(res.result) is bool:
                save_ssh_key_result = res.result
                break
        self.assertIsNotNone(save_ssh_key, "save_ssh_key_result is None")
        self.assertTrue(save_ssh_key_result, "save_ssh_key_result is False")

    @skipIf(settings.TEST_MANAGE_SSH_KEY_PUBKEY is None or
            settings.TEST_MANAGE_SSH_KEY_PUBKEY == "" or
            settings.TEST_MANAGE_SSH_KEY_HOST is None or
            settings.TEST_MANAGE_SSH_KEY_HOST is "",
            """Skipping test_save_ssh_key_add because either host
             or public key were not specified or were empty""")
    def test_save_ssh_key_remove(self):
        async_task = save_ssh_key.delay([self.hosts],
                                        [{'value': self.public_key,
                                          'state': False}])
        save_ssh_key_result = None
        for i in range(0, 10):
            sleep(5)
            res = AsyncResult(async_task.task_id)
            if type(res.result) is bool:
                save_ssh_key_result = res.result
                break
        self.assertIsNotNone(save_ssh_key, "save_ssh_key_result is None")
        self.assertTrue(save_ssh_key_result, "save_ssh_key_result is False")