from django.test import TestCase
from django.contrib.auth import get_user_model
from datetime import datetime, date, timedelta
from django.utils import timezone

from .models import *
from uncloud_service.models import GenericServiceProduct
from uncloud.models import UncloudProvider, UncloudNetwork

import json

vm_product_config = {
    'features': {
        'cores':
        { 'min': 1,
          'max': 48
         },
        'ram_gb':
        { 'min': 1,
          'max': 256
         },
    },
}

vm_order_config = json.dumps({
    'cores': 1,
    'memory': 2,
    'storage': 100
})

vm_order_downgrade_config = {
    'features': {
        'cores': 1,
        'ram_gb': 1
    }
}

vm_order_upgrade_config = {
    'features': {
        'cores': 4,
        'ram_gb': 4
    }
}


class ProductTestCase(TestCase):
    """
    Test products and products <-> order interaction
    """

    def setUp(self):
        self.user = get_user_model().objects.create(
            username='random_user',
            email='jane.random@domain.tld')

        self.ba = BillingAddress.objects.create(
            owner=self.user,
            organization = 'Test org',
            street="unknown",
            city="unknown",
            postal_code="somewhere else",
            active=True)

        RecurringPeriod.populate_db_defaults()
        self.default_recurring_period = RecurringPeriod.objects.get(name="Per 30 days")
    
    def test_create_product(self):
        """
        Create a sample product
        """
        p = Product.objects.create(name="Testproduct",
                                   description="Only for testing",
                                   config=vm_product_config)

        p.recurring_periods.add(self.default_recurring_period,
                                through_defaults= { 'is_default': True })


class OrderTestCase(TestCase):
    """
    The heart of ordering products
    """

    def setUp(self):
        self.pricing_plan = PricingPlan.objects.create(name="PricingSample", set_up_fees=35, cores_unit_price=3, 
                                                        ram_unit_price=4, storage_unit_price=0.02)
        self.user = get_user_model().objects.create(
            username='random_user',
            email='jane.random@domain.tld')

        self.ba = BillingAddress.objects.create(
            owner=self.user,
            organization = 'Test org',
            street="unknown",
            city="unknown",
            postal_code="somewhere else",
            active=True)

        self.product = Product.objects.create(name="Testproduct",
                                   description="Only for testing",
                                   config=vm_product_config)

        RecurringPeriod.populate_db_defaults()
        self.default_recurring_period = RecurringPeriod.objects.get(name="Per 30 days")

        self.product.recurring_periods.add(self.default_recurring_period,
                                           through_defaults= { 'is_default': True })


    def test_order_invalid_recurring_period(self):
        """
        Order a products with a recurringperiod that is not added to the product
        """

        order_config = json.dumps({
            'cores': 1,
            'memory':2,
            'storage': 100
        })
        o = Order.objects.create(owner=self.user,
                                 billing_address=self.ba,
                                 pricing_plan = self.pricing_plan, 
                                 product=self.product,
                                 config=order_config)


    def test_order_product(self):
        """
        Order a product, ensure the order has correct price setup
        """
        order_config = json.dumps({
            'cores': 1,
            'memory':2,
            'storage': 100
        })
        o = Order.objects.create(owner=self.user,
                                 billing_address=self.ba,
                                 pricing_plan = self.pricing_plan, 
                                 product=self.product,
                                 config=order_config)

        self.assertEqual(o.one_time_price, 0)
        self.assertEqual(o.recurring_price, 13.0)

    def test_change_order(self):
        """
        Change an order and ensure that
        - a new order is created
        - the price is correct in the new order
        """
        order_config = json.dumps({
            'cores': 2,
            'memory':4,
            'storage': 200,
        })
        order1 = Order.objects.create(owner=self.user,
                                 billing_address=self.ba,
                                 pricing_plan = self.pricing_plan, 
                                 product=self.product,
                                 config=order_config)

        self.assertEqual(order1.one_time_price, 0)
        self.assertEqual(order1.recurring_price, 26.0)


class ModifyOrderTestCase(TestCase):
    """
    Test typical order flows like
    - cancelling
    - downgrading
    - upgrading
    """

    def setUp(self):
        self.user = get_user_model().objects.create(
            username='random_user',
            email='jane.random@domain.tld')
        self.pricing_plan = PricingPlan.objects.create(name="PricingSample", set_up_fees=35, cores_unit_price=3, 
                                                        ram_unit_price=4, storage_unit_price=0.02)
        self.order1_config = json.dumps({
            'cores': 2,
            'memory':4,
            'storage': 200
        })
        self.order2_config = json.dumps({
            'cores': 1,
            'memory':2,
            'storage': 100
        })
        self.ba = BillingAddress.objects.create(
            owner=self.user,
            organization = 'Test org',
            street="unknown",
            city="unknown",
            postal_code="somewhere else",
            active=True)

        self.product = Product.objects.create(name="Testproduct",
                                              description="Only for testing",
                                              config=vm_product_config)

        RecurringPeriod.populate_db_defaults()
        self.default_recurring_period = RecurringPeriod.objects.get(name="Per 30 days")

        self.product.recurring_periods.add(self.default_recurring_period,
                                through_defaults= { 'is_default': True })


    def test_change_order(self):
        """
        Test changing an order

        Expected result:

        - Old order should be closed before new order starts
        - New order should start at starting data
        """

        user = self.user

        starting_price = 16
        downgrade_price = 8

        starting_date = timezone.make_aware(datetime.datetime(2019,3,3))
        ending1_date = starting_date + datetime.timedelta(days=15)
        change1_date  = start_after(ending1_date)

        bill_ending_date = change1_date + datetime.timedelta(days=1)


        order1 = Order.objects.create(owner=self.user,
                                     billing_address=BillingAddress.get_address_for(self.user),
                                     product=self.product,
                                     config=self.order1_config,
                                     pricing_plan=self.pricing_plan,
                                     starting_date=starting_date)

        order1.update_order(self.order2_config, starting_date=change1_date)

        bills = Bill.create_next_bills_for_user(user, ending_date=bill_ending_date)

        bill = bills[0]
        bill_records = BillRecord.objects.filter(bill=bill)

        self.assertEqual(len(bill_records), 2)

        self.assertEqual(bill_records[0].starting_date, starting_date)
        self.assertEqual(bill_records[0].ending_date, ending1_date)

        self.assertEqual(bill_records[1].starting_date, change1_date)



    def test_downgrade_product(self):
        """
        Test downgrading behaviour:

        We create a recurring product (recurring time: 30 days) and downgrade after 15 days.

        We create the bill right AFTER the end of the first order.

        Expected result:

        - First bill record for 30 days
        - Second bill record starting after 30 days
        - Bill contains two bill records

        """

        user = self.user

        starting_price = 16
        downgrade_price = 8

        starting_date = timezone.make_aware(datetime.datetime(2019,3,3))
        first_order_should_end_at = starting_date + datetime.timedelta(days=30)
        change1_date  = start_after(starting_date + datetime.timedelta(days=15))
        bill_ending_date = change1_date + datetime.timedelta(days=1)
        order1 = Order.objects.create(owner=self.user,
                                     billing_address=BillingAddress.get_address_for(self.user),
                                     product=self.product,
                                     pricing_plan=self.pricing_plan,
                                     config=self.order1_config,
                                     starting_date=starting_date)

        bills = Bill.create_next_bills_for_user(user, ending_date=bill_ending_date)

        bill = bills[0]
        bill_records = BillRecord.objects.filter(bill=bill)

        self.assertEqual(len(bill_records), 1)
        self.assertEqual(bill_records[0].starting_date, starting_date)

        order1.update_order(self.order2_config, starting_date=change1_date)
        bills = Bill.create_next_bills_for_user(user, ending_date=bill_ending_date)
        bill_records = BillRecord.objects.filter(bill=bill)
        self.assertEqual(len(bill_records), 2)
        self.assertEqual(bill_records[0].order.ending_date.date(), change1_date.date())


class BillTestCase(TestCase):
    """
    Test aspects of billing / creating a bill
    """

    def setUp(self):
        RecurringPeriod.populate_db_defaults()

        self.pricing_plan = PricingPlan.objects.create(name="PricingSample", set_up_fees=35, cores_unit_price=3, 
                                                        ram_unit_price=4, storage_unit_price=0.02)

        self.user_without_address = get_user_model().objects.create(
            username='no_home_person',
            email='far.away@domain.tld')

        self.user = get_user_model().objects.create(
            username='jdoe',
            email='john.doe@domain.tld')

        self.recurring_user = get_user_model().objects.create(
            username='recurrent_product_user',
            email='jane.doe@domain.tld')

        self.user_addr = BillingAddress.objects.create(
            owner=self.user,
            organization = 'Test org',
            street="unknown",
            city="unknown",
            postal_code="unknown",
            active=True)

        self.recurring_user_addr = BillingAddress.objects.create(
            owner=self.recurring_user,
            organization = 'Test org',
            street="Somewhere",
            city="Else",
            postal_code="unknown",
            active=True)

        self.order_meta = {}
        self.order_meta[1] = {
            'starting_date': timezone.make_aware(datetime.datetime(2020,3,3)),
            'ending_date': timezone.make_aware(datetime.datetime(2020,4,17)),
            'price': 15,
            'description': ''
        }

        self.product = Product.objects.create(name="Product Sample",
                                              description="Not only for testing, but for joy",
                                              config=vm_product_config)


        self.vm = Product.objects.create(name="Super Fast VM",
                                              description="Zooooom",
                                              config=vm_product_config)


        RecurringPeriod.populate_db_defaults()
        self.default_recurring_period = RecurringPeriod.objects.get(name="Per 30 days")

        self.onetime_recurring_period = RecurringPeriod.objects.get(name="Onetime")

        self.product.recurring_periods.add(self.onetime_recurring_period,
                                through_defaults= { 'is_default': True })

        self.vm.recurring_periods.add(self.default_recurring_period,
                                through_defaults= { 'is_default': True })


        # used for generating multiple bills
        self.bill_dates = [
            timezone.make_aware(datetime.datetime(2020,3,31)),
            timezone.make_aware(datetime.datetime(2020,4,30)),
            timezone.make_aware(datetime.datetime(2020,5,31)),
        ]


    def order_product(self):
        return Order.objects.create(
            owner=self.user,
            recurring_period=RecurringPeriod.objects.get(name="Onetime"),
            product=self.product,
            billing_address=BillingAddress.get_address_for(self.user),
            starting_date=self.order_meta[1]['starting_date'],
            ending_date=self.order_meta[1]['ending_date'],
            pricing_plan=self.pricing_plan,
            config=vm_order_config)

    def order_vm(self, owner=None):

        if not owner:
            owner = self.recurring_user

        return Order.objects.create(
            owner=owner,
            product=self.vm,
            config=vm_order_config,
            pricing_plan=self.pricing_plan,
            billing_address=BillingAddress.get_address_for(self.recurring_user),
            starting_date=timezone.make_aware(datetime.datetime(2020,3,3)),
        )

    def test_bill_one_time_with_recurring(self):
        """
        Validate that if the order contains one_time_price and recurring_pricing
        One Bill records should be created
        """

        order = Order.objects.create(
            owner=self.user,
            product=self.vm,
            config=vm_order_config,
            pricing_plan=self.pricing_plan,
            one_time_price = 35,
            billing_address=BillingAddress.get_address_for(self.user),
            starting_date=timezone.make_aware(datetime.datetime(2020,3,3)),
        )
        
        bill = Bill.create_next_bill_for_user_address(self.user_addr)

        self.assertEqual(order.billrecord_set.count(), 1)
        record = order.billrecord_set.first()
        self.assertEqual(record.is_recurring_record, False)
        self.assertEqual(record.price, 35)
        self.assertEqual(record.quantity, 1)
        self.assertEqual(record.sum, 35)
        #close the bill as it has been paid
        bill.close()
        bill2 = Bill.create_next_bill_for_user_address(self.user_addr)
        self.assertNotEqual(bill.id, bill2.id)
        self.assertEqual(order.billrecord_set.count(), 2)
        record = BillRecord.objects.filter(bill=bill2, order=order).first()
        self.assertEqual(record.is_recurring_record, True)
        self.assertEqual(record.price, 13)
        self.assertEqual(record.quantity, 1)
        self.assertEqual(record.sum, 13)

    def test_bill_one_time_one_bill_record(self):
        """
        Ensure there is only 1 bill record per order
        """

        order = self.order_product()

        bill = Bill.create_next_bill_for_user_address(self.user_addr)

        self.assertEqual(order.billrecord_set.count(), 1)

    def test_bill_sum_onetime(self):
        """
        Check the bill sum for a single one time order
        """

        order = self.order_product()
        self.assertEqual(order.recurring_price, 13.0)
        bill = Bill.create_next_bill_for_user_address(self.user_addr)
        self.assertEqual(order.billrecord_set.count(), 1)
        record = order.billrecord_set.first()
        self.assertEqual(record.price, 13)
        self.assertEqual(record.quantity, 1)
        self.assertEqual(bill.sum, 13)


    def test_bill_creates_record_for_recurring_order(self):
        """
        Ensure there is only 1 bill record per order
        """

        order = self.order_vm()
        bill = Bill.create_next_bill_for_user_address(self.recurring_user_addr)

        self.assertEqual(order.billrecord_set.count(), 1)
        self.assertEqual(bill.bill_records.count(), 1)


    def test_new_bill_after_closing(self):
        """
        After closing a bill and the user has a recurring product,
        the next bill run should create e new bill
        """

        order = self.order_vm()

        for ending_date in self.bill_dates:
            b = Bill.create_next_bill_for_user_address(self.recurring_user_addr, ending_date)
            b.close()

        bill_count = Bill.objects.filter(owner=self.recurring_user).count()

        self.assertEqual(len(self.bill_dates), bill_count)



class BillingAddressTestCase(TestCase):
    def setUp(self):
        self.user = get_user_model().objects.create(
            username='random_user',
            email='jane.random@domain.tld')


    def test_user_no_address(self):
        """
        Raise an error, when there is no address
        """

        self.assertRaises(BillingAddress.DoesNotExist,
                          BillingAddress.get_address_for,
                          self.user)

class VATRatesTestCase(TestCase):
    def setUp(self):
        self.user = get_user_model().objects.create(
            username='random_user',
            email='jane.random@domain.tld')

        self.user_addr = BillingAddress.objects.create(
            owner=self.user,
            organization = 'Test org',
            street="unknown",
            city="unknown",
            postal_code="unknown",
            active=True)
        
        UncloudNetwork.populate_db_defaults()
        UncloudProvider.populate_db_defaults()



    def test_get_rate_for_user(self):
        """
        Raise an error, when there is no address
        """
