import datetime
import sys
from datetime import timedelta
from decimal import Decimal

import pytest
from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.files.storage import default_storage
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase
from django.utils.timezone import now

from pretix.base.models import (
    CachedFile, CartPosition, Event, Item, ItemCategory, ItemVariation, Order,
    OrderPosition, Organizer, Question, Quota, User, Voucher, WaitingListEntry,
)
from pretix.base.services.orders import (
    OrderError, cancel_order, mark_order_paid, perform_order,
)


class UserTestCase(TestCase):
    def test_name(self):
        u = User.objects.create_user('test@foo.bar', 'test')
        u.fullname = "Christopher Nolan"
        u.set_password("test")
        u.save()
        self.assertEqual(u.get_full_name(), 'Christopher Nolan')
        self.assertEqual(u.get_short_name(), 'Christopher Nolan')
        u.fullname = None
        u.save()
        self.assertEqual(u.get_full_name(), 'test@foo.bar')
        self.assertEqual(u.get_short_name(), 'test@foo.bar')


class BaseQuotaTestCase(TestCase):
    @classmethod
    def setUpTestData(cls):
        o = Organizer.objects.create(name='Dummy', slug='dummy')
        cls.event = Event.objects.create(
            organizer=o, name='Dummy', slug='dummy',
            date_from=now(),
        )

    def setUp(self):
        self.quota = Quota.objects.create(name="Test", size=2, event=self.event)
        self.item1 = Item.objects.create(event=self.event, name="Ticket", default_price=23,
                                         admission=True)
        self.item2 = Item.objects.create(event=self.event, name="T-Shirt", default_price=23)
        self.item3 = Item.objects.create(event=self.event, name="Goodie", default_price=23)
        self.var1 = ItemVariation.objects.create(item=self.item2, value='S')
        self.var2 = ItemVariation.objects.create(item=self.item2, value='M')
        self.var3 = ItemVariation.objects.create(item=self.item3, value='Fancy')


class QuotaTestCase(BaseQuotaTestCase):
    def test_available(self):
        self.quota.items.add(self.item1)
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 2))
        self.quota.items.add(self.item2)
        self.quota.variations.add(self.var1)
        try:
            self.item2.check_quotas()
            self.assertTrue(False)
        except:
            pass
        self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_OK, 2))

    def test_sold_out(self):
        self.quota.items.add(self.item1)
        order = Order.objects.create(event=self.event, status=Order.STATUS_PAID,
                                     expires=now() + timedelta(days=3),
                                     total=4)
        OrderPosition.objects.create(order=order, item=self.item1, price=2)
        OrderPosition.objects.create(order=order, item=self.item1, price=2)
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_GONE, 0))

        self.quota.items.add(self.item2)
        self.quota.variations.add(self.var1)
        self.quota.size = 3
        self.quota.save()
        self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_OK, 1))

        order = Order.objects.create(event=self.event, status=Order.STATUS_PAID,
                                     expires=now() + timedelta(days=3),
                                     total=4)
        OrderPosition.objects.create(order=order, item=self.item2, variation=self.var1, price=2)
        self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_GONE, 0))

    def test_ordered(self):
        self.quota.items.add(self.item1)
        order = Order.objects.create(event=self.event, status=Order.STATUS_PAID,
                                     expires=now() + timedelta(days=3),
                                     total=4)
        OrderPosition.objects.create(order=order, item=self.item1, price=2)
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))

        order = Order.objects.create(event=self.event, status=Order.STATUS_PENDING,
                                     expires=now() + timedelta(days=3),
                                     total=4)
        OrderPosition.objects.create(order=order, item=self.item1, price=2)
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_ORDERED, 0))

        order.expires = now() - timedelta(days=3)
        order.save()
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_ORDERED, 0))

        order.status = Order.STATUS_EXPIRED
        order.save()
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))

    def test_ordered_multi_quota(self):
        quota2 = Quota.objects.create(name="Test", size=2, event=self.event)
        quota2.items.add(self.item2)
        quota2.variations.add(self.var1)
        self.quota.items.add(self.item2)
        self.quota.variations.add(self.var1)

        order = Order.objects.create(event=self.event, status=Order.STATUS_PAID,
                                     expires=now() + timedelta(days=3),
                                     total=4)
        OrderPosition.objects.create(order=order, item=self.item2, variation=self.var1, price=2)

        self.assertEqual(quota2.availability(), (Quota.AVAILABILITY_OK, 1))

    def test_reserved(self):
        self.quota.items.add(self.item1)
        self.quota.size = 3
        self.quota.save()
        order = Order.objects.create(event=self.event, status=Order.STATUS_PAID,
                                     expires=now() + timedelta(days=3),
                                     total=4)
        OrderPosition.objects.create(order=order, item=self.item1, price=2)
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 2))

        order = Order.objects.create(event=self.event, status=Order.STATUS_PENDING,
                                     expires=now() + timedelta(days=3),
                                     total=4)
        OrderPosition.objects.create(order=order, item=self.item1, price=2)
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))

        cp = CartPosition.objects.create(event=self.event, item=self.item1, price=2,
                                         expires=now() + timedelta(days=3))
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_RESERVED, 0))

        cp.expires = now() - timedelta(days=3)
        cp.save()
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))

        self.quota.items.add(self.item2)
        self.quota.variations.add(self.var1)
        cp = CartPosition.objects.create(event=self.event, item=self.item2, variation=self.var1,
                                         price=2, expires=now() + timedelta(days=3))
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_RESERVED, 0))

    def test_multiple(self):
        self.quota.items.add(self.item1)
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 2))

        quota2 = Quota.objects.create(event=self.event, name="Test 2", size=1)
        quota2.items.add(self.item1)
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))

        quota2.size = 0
        quota2.save()
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_GONE, 0))

    def test_ignore_quotas(self):
        self.quota.items.add(self.item1)
        quota2 = Quota.objects.create(event=self.event, name="Test 2", size=0)
        quota2.items.add(self.item1)
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_GONE, 0))
        self.assertEqual(self.item1.check_quotas(ignored_quotas=[quota2]), (Quota.AVAILABILITY_OK, 2))
        self.assertEqual(self.item1.check_quotas(ignored_quotas=[self.quota, quota2]),
                         (Quota.AVAILABILITY_OK, sys.maxsize))

    def test_unlimited(self):
        self.quota.items.add(self.item1)
        order = Order.objects.create(event=self.event, status=Order.STATUS_PAID,
                                     expires=now() + timedelta(days=3),
                                     total=2)
        OrderPosition.objects.create(order=order, item=self.item1, price=2)
        OrderPosition.objects.create(order=order, item=self.item1, price=2)
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_GONE, 0))

        self.quota.size = None
        self.quota.save()
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, None))

    def test_voucher_product(self):
        self.quota.items.add(self.item1)
        self.quota.size = 1
        self.quota.save()

        v = Voucher.objects.create(item=self.item1, event=self.event)
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
        self.assertTrue(v.is_active())

        v.block_quota = True
        v.save()
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_RESERVED, 0))

    def test_voucher_variation(self):
        self.quota.variations.add(self.var1)
        self.quota.size = 1
        self.quota.save()

        v = Voucher.objects.create(item=self.item2, variation=self.var1, event=self.event)
        self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
        self.assertTrue(v.is_active())

        v.block_quota = True
        v.save()
        self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_RESERVED, 0))

    def test_voucher_quota(self):
        self.quota.variations.add(self.var1)
        self.quota.size = 1
        self.quota.save()

        v = Voucher.objects.create(quota=self.quota, event=self.event)
        self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
        self.assertTrue(v.is_active())

        v.block_quota = True
        v.save()
        self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_RESERVED, 0))

    def test_voucher_quota_multiuse(self):
        self.quota.size = 5
        self.quota.variations.add(self.var1)
        self.quota.save()
        Voucher.objects.create(quota=self.quota, event=self.event, block_quota=True, max_usages=5, redeemed=2)
        self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_OK, 2))
        Voucher.objects.create(quota=self.quota, event=self.event, block_quota=True, max_usages=2)
        self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_RESERVED, 0))

    def test_voucher_multiuse_count_overredeemed(self):
        if 'sqlite' not in settings.DATABASES['default']['ENGINE']:
            pytest.xfail('This should raise a type error on most databases')
        Voucher.objects.create(quota=self.quota, event=self.event, block_quota=True, max_usages=2, redeemed=4)
        self.assertEqual(self.quota.count_blocking_vouchers(), 0)

    def test_voucher_quota_multiuse_multiproduct(self):
        q2 = Quota.objects.create(event=self.event, name="foo", size=10)
        q2.items.add(self.item1)
        self.quota.size = 5
        self.quota.items.add(self.item1)
        self.quota.items.add(self.item2)
        self.quota.items.add(self.item3)
        self.quota.variations.add(self.var1)
        self.quota.variations.add(self.var2)
        self.quota.variations.add(self.var3)
        self.quota.save()
        Voucher.objects.create(item=self.item1, event=self.event, block_quota=True, max_usages=5, redeemed=2)
        Voucher.objects.create(item=self.item2, variation=self.var2, event=self.event, block_quota=True, max_usages=5,
                               redeemed=2)
        Voucher.objects.create(item=self.item2, variation=self.var2, event=self.event, block_quota=True, max_usages=5,
                               redeemed=2)
        self.assertEqual(self.quota.count_blocking_vouchers(), 9)

    def test_voucher_quota_expiring_soon(self):
        self.quota.variations.add(self.var1)
        self.quota.size = 1
        self.quota.save()
        Voucher.objects.create(quota=self.quota, event=self.event, valid_until=now() + timedelta(days=5),
                               block_quota=True)
        self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_RESERVED, 0))

    def test_voucher_quota_expired(self):
        self.quota.variations.add(self.var1)
        self.quota.size = 1
        self.quota.save()
        v = Voucher.objects.create(quota=self.quota, event=self.event, valid_until=now() - timedelta(days=5),
                                   block_quota=True)
        self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
        self.assertFalse(v.is_active())

    def test_blocking_voucher_in_cart(self):
        self.quota.items.add(self.item1)
        v = Voucher.objects.create(quota=self.quota, event=self.event, valid_until=now() + timedelta(days=5),
                                   block_quota=True)
        CartPosition.objects.create(event=self.event, item=self.item1, price=2,
                                    expires=now() + timedelta(days=3), voucher=v)
        self.assertTrue(v.is_in_cart())
        self.assertEqual(self.quota.count_blocking_vouchers(), 1)
        self.assertEqual(self.quota.count_in_cart(), 0)
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))

    def test_blocking_voucher_in_cart_inifinitely_valid(self):
        self.quota.items.add(self.item1)
        v = Voucher.objects.create(quota=self.quota, event=self.event, block_quota=True)
        CartPosition.objects.create(event=self.event, item=self.item1, price=2,
                                    expires=now() + timedelta(days=3), voucher=v)
        self.assertEqual(self.quota.count_blocking_vouchers(), 1)
        self.assertEqual(self.quota.count_in_cart(), 0)
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))

    def test_blocking_expired_voucher_in_cart(self):
        self.quota.items.add(self.item1)
        v = Voucher.objects.create(quota=self.quota, event=self.event, valid_until=now() - timedelta(days=5),
                                   block_quota=True)
        CartPosition.objects.create(event=self.event, item=self.item1, price=2,
                                    expires=now() + timedelta(days=3), voucher=v)
        self.assertEqual(self.quota.count_blocking_vouchers(), 0)
        self.assertEqual(self.quota.count_in_cart(), 1)
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))

    def test_nonblocking_voucher_in_cart(self):
        self.quota.items.add(self.item1)
        v = Voucher.objects.create(quota=self.quota, event=self.event)
        CartPosition.objects.create(event=self.event, item=self.item1, price=2,
                                    expires=now() + timedelta(days=3), voucher=v)
        self.assertEqual(self.quota.count_blocking_vouchers(), 0)
        self.assertEqual(self.quota.count_in_cart(), 1)
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_OK, 1))

    def test_waitinglist_item_active(self):
        self.quota.items.add(self.item1)
        self.quota.size = 1
        self.quota.save()
        WaitingListEntry.objects.create(
            event=self.event, item=self.item1, email='foo@bar.com'
        )
        self.assertEqual(self.item1.check_quotas(), (Quota.AVAILABILITY_RESERVED, 0))
        self.assertEqual(self.item1.check_quotas(count_waitinglist=False), (Quota.AVAILABILITY_OK, 1))

    def test_waitinglist_variation_active(self):
        self.quota.variations.add(self.var1)
        self.quota.size = 1
        self.quota.save()
        WaitingListEntry.objects.create(
            event=self.event, item=self.item2, variation=self.var1, email='foo@bar.com'
        )
        self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_RESERVED, 0))
        self.assertEqual(self.var1.check_quotas(count_waitinglist=False), (Quota.AVAILABILITY_OK, 1))

    def test_waitinglist_variation_fulfilled(self):
        self.quota.variations.add(self.var1)
        self.quota.size = 1
        self.quota.save()
        v = Voucher.objects.create(quota=self.quota, event=self.event, block_quota=True, redeemed=1)
        WaitingListEntry.objects.create(
            event=self.event, item=self.item2, variation=self.var1, email='foo@bar.com', voucher=v
        )
        self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
        self.assertEqual(self.var1.check_quotas(count_waitinglist=False), (Quota.AVAILABILITY_OK, 1))

    def test_waitinglist_variation_other(self):
        self.quota.variations.add(self.var1)
        self.quota.size = 1
        self.quota.save()
        WaitingListEntry.objects.create(
            event=self.event, item=self.item2, variation=self.var2, email='foo@bar.com'
        )
        self.assertEqual(self.var1.check_quotas(), (Quota.AVAILABILITY_OK, 1))
        self.assertEqual(self.var1.check_quotas(count_waitinglist=False), (Quota.AVAILABILITY_OK, 1))

    def test_quota_cache(self):
        self.quota.variations.add(self.var1)
        self.quota.size = 1
        self.quota.save()
        WaitingListEntry.objects.create(
            event=self.event, item=self.item2, variation=self.var1, email='foo@bar.com'
        )

        cache = {}

        self.assertEqual(self.var1.check_quotas(_cache=cache), (Quota.AVAILABILITY_RESERVED, 0))

        with self.assertNumQueries(1):
            self.assertEqual(self.var1.check_quotas(_cache=cache), (Quota.AVAILABILITY_RESERVED, 0))

        # Do not reuse cache for count_waitinglist=False
        self.assertEqual(self.var1.check_quotas(_cache=cache, count_waitinglist=False), (Quota.AVAILABILITY_OK, 1))

        with self.assertNumQueries(1):
            self.assertEqual(self.var1.check_quotas(_cache=cache, count_waitinglist=False), (Quota.AVAILABILITY_OK, 1))


class WaitingListTestCase(BaseQuotaTestCase):

    def test_duplicate(self):
        w1 = WaitingListEntry.objects.create(
            event=self.event, item=self.item2, variation=self.var1, email='foo@bar.com'
        )
        w1.clean()
        w2 = WaitingListEntry(
            event=self.event, item=self.item2, variation=self.var1, email='foo@bar.com'
        )
        with self.assertRaises(ValidationError):
            w2.clean()

    def test_duplicate_of_successful(self):
        v = Voucher.objects.create(quota=self.quota, event=self.event, block_quota=True, redeemed=1)
        w1 = WaitingListEntry.objects.create(
            event=self.event, item=self.item2, variation=self.var1, email='foo@bar.com',
            voucher=v
        )
        w1.clean()
        w2 = WaitingListEntry(
            event=self.event, item=self.item2, variation=self.var1, email='foo@bar.com'
        )
        w2.clean()

    def test_missing_variation(self):
        w2 = WaitingListEntry(
            event=self.event, item=self.item2, email='foo@bar.com'
        )
        with self.assertRaises(ValidationError):
            w2.clean()


class VoucherTestCase(BaseQuotaTestCase):

    def test_voucher_reuse(self):
        self.quota.items.add(self.item1)
        v = Voucher.objects.create(quota=self.quota, event=self.event, valid_until=now() + timedelta(days=5))
        self.assertTrue(v.is_active())
        self.assertFalse(v.is_in_cart())
        self.assertFalse(v.is_ordered())

        # use a voucher normally
        cart = CartPosition.objects.create(event=self.event, item=self.item1, price=self.item1.default_price,
                                           expires=now() + timedelta(days=3), voucher=v)
        self.assertTrue(v.is_active())
        self.assertTrue(v.is_in_cart())
        self.assertFalse(v.is_ordered())

        order = perform_order(event=self.event.id, payment_provider='free', positions=[cart.id])
        v.refresh_from_db()
        self.assertFalse(v.is_active())
        self.assertFalse(v.is_in_cart())
        self.assertTrue(v.is_ordered())

        # assert that the voucher cannot be reused
        cart = CartPosition.objects.create(event=self.event, item=self.item1, price=self.item1.default_price,
                                           expires=now() + timedelta(days=3), voucher=v)
        self.assertRaises(OrderError, perform_order, event=self.event.id, payment_provider='free', positions=[cart.id])

        # assert that the voucher can be re-used after cancelling the successful order
        cancel_order(order)
        v.refresh_from_db()
        self.assertTrue(v.is_active())
        self.assertFalse(v.is_in_cart())
        self.assertTrue(v.is_ordered())

        cart = CartPosition.objects.create(event=self.event, item=self.item1, price=self.item1.default_price,
                                           expires=now() + timedelta(days=3), voucher=v)
        perform_order(event=self.event.id, payment_provider='free', positions=[cart.id])

    def test_voucher_applicability_quota(self):
        self.quota.items.add(self.item1)
        v = Voucher.objects.create(quota=self.quota, event=self.event)
        self.assertTrue(v.applies_to(self.item1))
        self.assertFalse(v.applies_to(self.item2))

    def test_voucher_applicability_item(self):
        v = Voucher.objects.create(item=self.var1.item, event=self.event)
        self.assertFalse(v.applies_to(self.item1))
        self.assertTrue(v.applies_to(self.var1.item))
        self.assertTrue(v.applies_to(self.var1.item, self.var1))

    def test_voucher_applicability_variation(self):
        v = Voucher.objects.create(item=self.var1.item, variation=self.var1, event=self.event)
        self.assertFalse(v.applies_to(self.item1))
        self.assertFalse(v.applies_to(self.var1.item))
        self.assertTrue(v.applies_to(self.var1.item, self.var1))
        self.assertFalse(v.applies_to(self.var1.item, self.var2))

    def test_voucher_no_item_with_quota(self):
        with self.assertRaises(ValidationError):
            v = Voucher(quota=self.quota, item=self.item1, event=self.event)
            v.clean()

    def test_voucher_item_with_no_variation(self):
        with self.assertRaises(ValidationError):
            v = Voucher(item=self.item1, variation=self.var1, event=self.event)
            v.clean()

    def test_voucher_item_does_not_match_variation(self):
        with self.assertRaises(ValidationError):
            v = Voucher(item=self.item2, variation=self.var3, event=self.event)
            v.clean()

    def test_voucher_specify_variation_for_block_quota(self):
        with self.assertRaises(ValidationError):
            v = Voucher(item=self.item2, block_quota=True, event=self.event)
            v.clean()

    def test_voucher_no_item_but_variation(self):
        with self.assertRaises(ValidationError):
            v = Voucher(variation=self.var1, event=self.event)
            v.clean()

    def test_calculate_price_none(self):
        v = Voucher.objects.create(event=self.event, price_mode='none', value=Decimal('10.00'))
        v.calculate_price(Decimal('23.42')) == Decimal('23.42')

    def test_calculate_price_set_empty(self):
        v = Voucher.objects.create(event=self.event, price_mode='set')
        v.calculate_price(Decimal('23.42')) == Decimal('23.42')

    def test_calculate_price_set(self):
        v = Voucher.objects.create(event=self.event, price_mode='set', value=Decimal('10.00'))
        v.calculate_price(Decimal('23.42')) == Decimal('10.00')

    def test_calculate_price_set_zero(self):
        v = Voucher.objects.create(event=self.event, price_mode='set', value=Decimal('0.00'))
        v.calculate_price(Decimal('23.42')) == Decimal('0.00')

    def test_calculate_price_subtract(self):
        v = Voucher.objects.create(event=self.event, price_mode='subtract', value=Decimal('10.00'))
        v.calculate_price(Decimal('23.42')) == Decimal('13.42')

    def test_calculate_price_percent(self):
        v = Voucher.objects.create(event=self.event, price_mode='percent', value=Decimal('23.00'))
        v.calculate_price(Decimal('100.00')) == Decimal('77.00')


class OrderTestCase(BaseQuotaTestCase):
    def setUp(self):
        super().setUp()
        self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
        self.order = Order.objects.create(
            status=Order.STATUS_PENDING, event=self.event,
            datetime=now() - timedelta(days=5),
            expires=now() + timedelta(days=5), total=46
        )
        self.quota.items.add(self.item1)
        OrderPosition.objects.create(order=self.order, item=self.item1,
                                     variation=None, price=23)
        OrderPosition.objects.create(order=self.order, item=self.item1,
                                     variation=None, price=23)

    def test_paid_in_time(self):
        self.quota.size = 0
        self.quota.save()
        mark_order_paid(self.order)
        self.order = Order.objects.get(id=self.order.id)
        self.assertEqual(self.order.status, Order.STATUS_PAID)

    def test_paid_expired_available(self):
        self.event.settings.payment_term_last = (now() + timedelta(days=2)).strftime('%Y-%m-%d')
        self.order.status = Order.STATUS_EXPIRED
        self.order.expires = now() - timedelta(days=2)
        self.order.save()
        mark_order_paid(self.order)
        self.order = Order.objects.get(id=self.order.id)
        self.assertEqual(self.order.status, Order.STATUS_PAID)

    def test_paid_expired_after_last_date(self):
        self.event.settings.payment_term_last = (now() - timedelta(days=2)).strftime('%Y-%m-%d')
        self.order.status = Order.STATUS_EXPIRED
        self.order.expires = now() - timedelta(days=2)
        self.order.save()
        with self.assertRaises(Quota.QuotaExceededException):
            mark_order_paid(self.order)
        self.order = Order.objects.get(id=self.order.id)
        self.assertEqual(self.order.status, Order.STATUS_EXPIRED)

    def test_paid_expired_late_not_allowed(self):
        self.event.settings.payment_term_accept_late = False
        self.order.status = Order.STATUS_EXPIRED
        self.order.expires = now() - timedelta(days=2)
        self.order.save()
        with self.assertRaises(Quota.QuotaExceededException):
            mark_order_paid(self.order)
        self.order = Order.objects.get(id=self.order.id)
        self.assertEqual(self.order.status, Order.STATUS_EXPIRED)

    def test_paid_expired_unavailable(self):
        self.order.expires = now() - timedelta(days=2)
        self.order.status = Order.STATUS_EXPIRED
        self.order.save()
        self.quota.size = 0
        self.quota.save()
        with self.assertRaises(Quota.QuotaExceededException):
            mark_order_paid(self.order)
        self.order = Order.objects.get(id=self.order.id)
        self.assertIn(self.order.status, (Order.STATUS_PENDING, Order.STATUS_EXPIRED))

    def test_paid_after_deadline_but_not_expired(self):
        self.order.expires = now() - timedelta(days=2)
        self.order.save()
        mark_order_paid(self.order)
        self.order = Order.objects.get(id=self.order.id)
        self.assertEqual(self.order.status, Order.STATUS_PAID)

    def test_paid_expired_unavailable_force(self):
        self.order.expires = now() - timedelta(days=2)
        self.order.save()
        self.quota.size = 0
        self.quota.save()
        mark_order_paid(self.order, force=True)
        self.order = Order.objects.get(id=self.order.id)
        self.assertEqual(self.order.status, Order.STATUS_PAID)

    def test_can_modify_answers(self):
        self.event.settings.set('invoice_address_asked', False)
        self.event.settings.set('attendee_names_asked', True)
        assert self.order.can_modify_answers
        self.event.settings.set('attendee_names_asked', False)
        assert not self.order.can_modify_answers
        self.event.settings.set('invoice_address_asked', True)
        assert self.order.can_modify_answers
        q = Question.objects.create(question='Foo', type=Question.TYPE_BOOLEAN, event=self.event)
        self.item1.questions.add(q)
        assert self.order.can_modify_answers
        self.order.status = Order.STATUS_REFUNDED
        assert not self.order.can_modify_answers
        self.order.status = Order.STATUS_PAID
        assert self.order.can_modify_answers
        self.event.settings.set('last_order_modification_date', now() - timedelta(days=1))
        assert not self.order.can_modify_answers

    def test_can_cancel_order(self):
        item1 = Item.objects.create(event=self.event, name="Ticket", default_price=23,
                                    admission=True, allow_cancel=True)
        OrderPosition.objects.create(order=self.order, item=item1,
                                     variation=None, price=23)
        assert self.order.can_user_cancel

    def test_can_cancel_order_multiple(self):
        item1 = Item.objects.create(event=self.event, name="Ticket", default_price=23,
                                    admission=True, allow_cancel=True)
        item2 = Item.objects.create(event=self.event, name="Ticket", default_price=23,
                                    admission=True, allow_cancel=True)
        OrderPosition.objects.create(order=self.order, item=item1,
                                     variation=None, price=23)
        OrderPosition.objects.create(order=self.order, item=item2,
                                     variation=None, price=23)
        assert self.order.can_user_cancel

    def test_can_not_cancel_order(self):
        item1 = Item.objects.create(event=self.event, name="Ticket", default_price=23,
                                    admission=True, allow_cancel=False)
        OrderPosition.objects.create(order=self.order, item=item1,
                                     variation=None, price=23)
        assert self.order.can_user_cancel is False

    def test_can_not_cancel_order_multiple(self):
        item1 = Item.objects.create(event=self.event, name="Ticket", default_price=23,
                                    admission=True, allow_cancel=False)
        item2 = Item.objects.create(event=self.event, name="Ticket", default_price=23,
                                    admission=True, allow_cancel=True)
        OrderPosition.objects.create(order=self.order, item=item1,
                                     variation=None, price=23)
        OrderPosition.objects.create(order=self.order, item=item2,
                                     variation=None, price=23)
        assert self.order.can_user_cancel is False

    def test_can_not_cancel_order_multiple_mixed(self):
        item1 = Item.objects.create(event=self.event, name="Ticket", default_price=23,
                                    admission=True, allow_cancel=False)
        item2 = Item.objects.create(event=self.event, name="Ticket", default_price=23,
                                    admission=True, allow_cancel=True)
        OrderPosition.objects.create(order=self.order, item=item1,
                                     variation=None, price=23)
        OrderPosition.objects.create(order=self.order, item=item2,
                                     variation=None, price=23)
        assert self.order.can_user_cancel is False

    def test_no_duplicate_position_secret(self):
        item1 = Item.objects.create(event=self.event, name="Ticket", default_price=23,
                                    admission=True, allow_cancel=False)
        p1 = OrderPosition.objects.create(order=self.order, item=item1, secret='ABC',
                                          variation=None, price=23)
        p2 = OrderPosition.objects.create(order=self.order, item=item1, secret='ABC',
                                          variation=None, price=23)
        assert p1.secret != p2.secret
        assert self.order.can_user_cancel is False


class ItemCategoryTest(TestCase):
    """
    This test case tests various methods around the category model.
    """

    @classmethod
    def setUpTestData(cls):
        o = Organizer.objects.create(name='Dummy', slug='dummy')
        cls.event = Event.objects.create(
            organizer=o, name='Dummy', slug='dummy',
            date_from=now(),
        )

    def test_sorting(self):
        c1 = ItemCategory.objects.create(event=self.event)
        c2 = ItemCategory.objects.create(event=self.event)
        assert c1 < c2
        c1.position = 2
        c2.position = 1
        assert c2 < c1
        assert not c1 < c2
        assert c1 > c2
        c1.position = 1
        c2.position = 2
        assert c1 < c2
        assert c2 > c1


class ItemTest(TestCase):
    """
    This test case tests various methods around the item model.
    """

    @classmethod
    def setUpTestData(cls):
        o = Organizer.objects.create(name='Dummy', slug='dummy')
        cls.event = Event.objects.create(
            organizer=o, name='Dummy', slug='dummy',
            date_from=now(),
        )

    def test_is_available(self):
        i = Item.objects.create(
            event=self.event, name="Ticket", default_price=23,
            active=True, available_until=now() + timedelta(days=1),
        )
        assert i.is_available()
        i.available_from = now() - timedelta(days=1)
        assert i.is_available()
        i.available_from = now() + timedelta(days=1)
        i.available_until = None
        assert not i.is_available()
        i.available_from = None
        i.available_until = now() - timedelta(days=1)
        assert not i.is_available()
        i.available_from = None
        i.available_until = None
        assert i.is_available()
        i.active = False
        assert not i.is_available()


class EventTest(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.organizer = Organizer.objects.create(name='Dummy', slug='dummy')

    def test_event_end_before_start(self):
        event = Event(
            organizer=self.organizer, name='Dummy', slug='dummy',
            date_from=now(), date_to=now() - timedelta(hours=1)
        )
        with self.assertRaises(ValidationError) as context:
            event.clean()

        self.assertIn('date_to', str(context.exception))

    def test_presale_end_before_start(self):
        event = Event(
            organizer=self.organizer, name='Dummy', slug='dummy',
            presale_start=now(), presale_end=now() - timedelta(hours=1)
        )
        with self.assertRaises(ValidationError) as context:
            event.clean()

        self.assertIn('presale_end', str(context.exception))

    def test_slug_validation(self):
        event = Event(
            organizer=self.organizer, name='Download', slug='download',
            date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc)
        )
        with self.assertRaises(ValidationError) as context:
            event.full_clean()

        self.assertIn('slug', str(context.exception))


class CachedFileTestCase(TestCase):
    def test_file_handling(self):
        cf = CachedFile()
        val = SimpleUploadedFile("testfile.txt", b"file_content")
        cf.file.save("testfile.txt", val)
        cf.type = "text/plain"
        cf.filename = "testfile.txt"
        cf.save()
        assert default_storage.exists(cf.file.name)
        with default_storage.open(cf.file.name, 'r') as f:
            assert f.read().strip() == "file_content"
        cf.delete()
        assert not default_storage.exists(cf.file.name)
