import datetime

from django.test import TestCase
from django.core.exceptions import ValidationError

from ..models import (
    Cost, CostUse, Document, Good, GoodUse, Service, ServiceUse)

THREE_YEARS = datetime.timedelta(days=365*3)

class ServiceTests(TestCase):
    def setUp(self):
        self.doc = Document.objects.create(name='budget')
        self.electricity_cost = Cost.objects.create(
            name='electricity',
            price=10,
            document=self.doc,
        )
        self.server = Good.objects.create(
            name="Computer",
            price=10,
            document=self.doc,
            provisioning_duration=THREE_YEARS,
        )

    def test_get_prices_zero(self):
        s = Service.objects.create(name='Foo', document=self.doc)
        self.assertEqual(s.get_prices(), {
            'total_recurring_price': 0,
            'unit_recurring_price': 0,
            'unit_goods_value_share': 0,
            'total_goods_value_share': 0,
        })

    def test_get_prices_w_costs(self):
        s = Service.objects.create(name='Foo', document=self.doc)

        CostUse.objects.create(
            service=s, resource=self.electricity_cost, share=0.4)

        self.assertEqual(s.get_prices(), {
            'total_recurring_price': 10,
            'unit_recurring_price': 0,
            'unit_goods_value_share': 0,
            'total_goods_value_share': 0,
        })

        s.subscriptions_count = 2
        s.save()

        self.assertEqual(s.get_prices(), {
            'total_recurring_price': 10,
            'unit_recurring_price': 5,
            'unit_goods_value_share': 0,
            'total_goods_value_share': 0,
        })

    def test_get_prices_w_goods(self):
        s = Service.objects.create(
            name='Foo', document=self.doc, subscriptions_count=0)

        GoodUse.objects.create(
            service=s, resource=self.server, share=0.4)

        self.assertEqual(s.get_prices(), {
            'total_recurring_price': 10/(365*3)*365.25/12,
            'unit_recurring_price': 0,
            'unit_goods_value_share': 0,
            'total_goods_value_share': 10.0,
        })

        s.subscriptions_count = 2
        s.save()

        self.assertEqual(s.get_prices(), {
            'total_recurring_price': 10/(365*3)*365.25/12,
            'unit_recurring_price': 10/(365*3)*365.25/12/2,
            'unit_goods_value_share': 5,
            'total_goods_value_share': 10,
        })


class AbstractUseTests(TestCase):
    """ Testing AbstractUseTests through CostUse
    """
    def setUp(self):
        self.doc = Document.objects.create(name='budget')
        self.hosting_service = Service.objects.create(
            name='Physical hosting', document=self.doc)
        self.mailbox_service = Service.objects.create(
            name='Mailbox', document=self.doc)

        self.electricity_cost = Cost.objects.create(
            name='electricity',
            price=10,
            document=self.doc,
            capacity_unit='A',
            total_capacity=4,
        )

        self.carrier_connection = Cost.objects.create(
            name='carrier connection',
            price=100,
            document=self.doc,
        )

    def test_can_add_service_share(self):
        use = CostUse(
            service=self.hosting_service,
            resource=self.electricity_cost,
            share=0.4)

        use.full_clean()
        use.save()

    def test_can_add_service_share_with_custom_unity(self):
        use = CostUse(
            service=self.hosting_service,
            resource=self.electricity_cost,
            share=2)  # means 2 Amps

        self.hosting_service.subscriptions_count = 2
        self.hosting_service.save()

        use.full_clean()
        use.save()

        self.assertEqual(use.share, 2.0)
        self.assertEqual(use.real_share(), 4.0)
        self.assertEqual(use.cost_share(), 10)
        self.assertEqual(use.unit_cost_share(), 5)

    def test_cannot_add_excess_share_one(self):
        use = CostUse(
            service=self.hosting_service,
            resource=self.electricity_cost,
            share=40.1)

        with self.assertRaises(ValidationError):
            use.full_clean()
            use.save()

    def test_add_several_service_share(self):
        u1 = CostUse(
            service=self.hosting_service,
            resource=self.electricity_cost,
            share=0.4)

        u1.full_clean()
        u1.save()

        u2 = CostUse(
            service=self.mailbox_service,
            resource=self.electricity_cost,
            share=0.6)

        u2.full_clean()
        u2.save()

    def test_add_several_service_share_excessive_sum(self):
        u1 = CostUse(
            service=self.hosting_service,
            resource=self.electricity_cost,
            share=3)

        u1.full_clean()
        u1.save()

        u2 = CostUse(
            service=self.mailbox_service,
            resource=self.electricity_cost,
            share=1.1)

        # Would be 4.1 out of 4 amp...

        with self.assertRaises(ValidationError):
            u2.full_clean()
            u2.save()

    def test_modify_service_share_no_error(self):
        u1 = CostUse(
            service=self.hosting_service,
            resource=self.electricity_cost,
            share=1)

        u1.full_clean()
        u1.save()
        u1.full_clean()
        u1.save()

    def test_real_shares(self):
        u1 = CostUse.objects.create(
            service=self.hosting_service,
            resource=self.electricity_cost,
            share=1.6)
        u2 = CostUse.objects.create(
            service=self.hosting_service,
            resource=self.electricity_cost,
            share=0.6)

        self.assertEqual(u1.real_share() + u2.real_share(), 4)
        self.assertEqual(u1.share/u2.share, u1.real_share()/u2.real_share())

    def test_unit_value_share(self):
        self.mailbox_service.subscriptions_count = 2
        self.mailbox_service.share = 0.5
        self.hosting_service.save()

        self.hosting_service.subscriptions_count = 1
        self.hosting_service.share = 0.5
        self.hosting_service.save()

        mailbox_use = CostUse.objects.create(
            service=self.mailbox_service,
            resource=self.electricity_cost,
            share=2)

        hosting_use = CostUse.objects.create(
            service=self.hosting_service,
            resource=self.electricity_cost,
            share=2)

        self.assertEqual(mailbox_use.value_share(), 5)
        self.assertEqual(mailbox_use.unit_value_share(), 2.5)

        self.assertEqual(hosting_use.value_share(), 5)
        self.assertEqual(hosting_use.unit_value_share(), 5)

    def test_used(self):
        CostUse.objects.create(
            service=self.mailbox_service,
            resource=self.electricity_cost,
            share=0.5)

        self.assertEqual(self.electricity_cost.used(), 0.5)
        self.assertEqual(self.electricity_cost.unused(), 3.5)

    def test_service_using_service(self):
        """
        Wifi+VPN is a service, but using VPN access
        So there is a service using another service
        """

        vpn_service = Service.objects.create(
            name="VPN",
            document=self.doc,
            subscriptions_count=20,  # includes wifi+vpn subscribers
            reusable=True,
        )
        # both should be auto-set
        self.assertEqual(vpn_service.capacity_unit, 'services')
        self.assertEqual(vpn_service.total_capacity, 20)

        wifi_service = Service.objects.create(
            name="Wifi, via VPN",
            document=self.doc,
            subscriptions_count=2,
            reusable=True,
        )
        self.assertEqual(vpn_service.capacity_unit, 'services')

        # To simplify, VPN is only using electricity
        CostUse.objects.create(
            service=vpn_service,
            resource=self.electricity_cost,
            share=0.5,  # Amp
        )

        # Wifi is using VPN + a carrier connection
        wifi_vpn_use = ServiceUse.objects.create(
            service=wifi_service,
            resource=vpn_service,
            share=2,
        )
        CostUse.objects.create(
            service=wifi_service,
            resource=self.carrier_connection,
            share=1,  # 100%
        )

        self.assertEqual(wifi_vpn_use.share, 0.5*4*10/20*2)
        self.assertEqual(wifi_vpn_use.unit_share(), 0.5*4*10/20)

        # VPN this is the only service using electricity
        self.assertEqual(wifi_vpn_use.unit_real_share(), 10)

        # VPN is now using some gear, with deprecation provisioning
        hosting_access_fee = Good.objects.create(
            name='hosting access fee', price=360,
            provisioning_duration=THREE_YEARS, document=self.doc)
        GoodUse.objects.create(
            service=vpn_service, resource=hosting_access_fee, share=2)
        self.assertEqual(
            wifi_service.get_prices()['total_goods_value_share'], 36)
        self.assertEqual(
            wifi_service.get_prices()['unit_goods_value_share'], 18)

    def test_service_using_non_usable_service(self):
        serva = Service.objects.create(
            name='A', document=self.doc,
            subscriptions_count=4,
            reusable=False,
        )

        with self.assertRaises(ValidationError):
            su = ServiceUse(
                service=self.mailbox_service,
                resource=serva,
                share=1,
            )
            su.full_clean()
            su.save()

    def test_service_using_cyclic_service(self):
        """ We should not save any service dependency building a cycle
        """
        a = Service.objects.create(
            name='a', document=self.doc,
            reusable=True,
        )

        b = Service.objects.create(
            name='b', document=self.doc,
            reusable=True,
        )

        c = Service.objects.create(
            name='c', document=self.doc,
            reusable=True,
        )

        def create_clean(user, used):
            new_use = ServiceUse(service=user, resource=used, share=1)
            new_use.full_clean()
            new_use.save()

        create_clean(a, b)

        with self.assertRaises(ValidationError):
            create_clean(b, a)

        create_clean(b, c)

        with self.assertRaises(ValidationError):
            create_clean(c, a)


class TestDocuments(TestCase):
    fixtures = ['full_example.yaml']

    _all_models = [Document, Cost, Good, Service, GoodUse, CostUse, ServiceUse]

    def _count(self):
        return [i.objects.count() for i in self._all_models]

    def test_copy(self):
        initial_counts = self._count()

        old_doc = Document.objects.first()
        new_doc = old_doc.copy()

        self.assertNotEqual(new_doc.pk, old_doc.pk)
        self.assertEqual(self._count(), [i*2 for i in initial_counts])
        old_doc.delete()
        self.assertEqual(self._count(), initial_counts)
