from datetime import datetime
import logging
import pdb

from django import db
from django.core.cache import cache
from django.test import TestCase

from models import TestModelA, TestModelB, TestModelC

logger = logging.getLogger(__name__)

class SimpleTest(TestCase):
	"There should be one DB query to get the data, then any further attempts to get the same data should bypass the DB, unless invalidated."
	
	def setUp(self):
		with self.assertNumQueries(2):
			self.ta1 = TestModelA(name="foo")
			self.ta1.save()
			self.ta2 = TestModelA(name="bar")
			self.ta2.save()
	
	def test_basic_select_caching(self):
		"Test that a whole queryset is retrieved from the cache the second time it's needed."
		with self.assertNumQueries(1):
			tas1 = TestModelA.objects.all()
			self.assertEqual(len(tas1), 2)
		
		with self.assertNumQueries(0):
			tas2 = TestModelA.objects.all()
			self.assertEqual(len(tas2), 2)
		
		self.assertEqual(len(tas1), len(tas2))
		for i in xrange(0, len(tas1)):
			self.assertEqual(tas1[i], tas2[i])
	
	def test_caches_with_filter(self):
		"Test that a filtered queryset is retrieved from the cache the second time it's needed."
		with self.assertNumQueries(1):
			tas1 = TestModelA.objects.filter(name__contains='f')
			self.assertEqual(len(tas1), 1)
		
		with self.assertNumQueries(0):
			tas2 = TestModelA.objects.filter(name__contains='f')
			self.assertEqual(len(tas2), 1)
		
		self.assertEqual(tas1[0], tas2[0])
	
	def test_filtering_caches_two_seperate_entries(self):
		"Test that a filtered queryset is retrieved from the cache the second time it's needed, and that the cached values are different."
		with self.assertNumQueries(1):
			len(TestModelA.objects.filter(name__contains='f'))
		
		with self.assertNumQueries(1):
			len(TestModelA.objects.filter(name__contains='b'))
		
		with self.assertNumQueries(0):
			tas1 = TestModelA.objects.filter(name__contains='f')
			self.assertEqual(len(tas1), 1)
		
		with self.assertNumQueries(0):
			tas2 = TestModelA.objects.filter(name__contains='b')
			self.assertEqual(len(tas2), 1)
		
		self.assertNotEqual(tas1[0], tas2[0])
	
	def test_cache_invalidation_on_single_table_when_saving_after_get(self):
		"Test that a call to save() invalidates the cache and results in a new DB query."
		with self.assertNumQueries(1):
			tas1a = TestModelA.objects.get(id=self.ta1.id)
		
		with self.assertNumQueries(0):
			tas1a = TestModelA.objects.get(id=self.ta1.id)
		
		with self.assertNumQueries(2):
			#the save() method triggers a SELECT using the ID. Even though we already did a get(), the query generated by save() is slightly different.
			tas1a.save()
		
		with self.assertNumQueries(1):
			tas1a = TestModelA.objects.get(id=self.ta1.id)
		
		with self.assertNumQueries(0):
			tas1a = TestModelA.objects.get(id=self.ta1.id)
	
	def test_cache_invalidation_on_single_table_when_saving_after_filter(self):
		"Test that a call to save() invalidates the cache and results in a new DB query."
		with self.assertNumQueries(1):
			tas1 = TestModelA.objects.filter(name__contains='f')
			self.assertEqual(len(tas1), 1)
		
		with self.assertNumQueries(0):
			tas1 = TestModelA.objects.filter(name__contains='f')
			self.assertEqual(len(tas1), 1)
		
		with self.assertNumQueries(2):
			#the save() method triggers a SELECT using the ID, and that specific query hasn't been stored yet
			tas1[0].save()
		
		with self.assertNumQueries(1):
			tas1 = TestModelA.objects.filter(name__contains='f')
			self.assertEqual(len(tas1), 1)
		
		with self.assertNumQueries(0):
			tas1 = TestModelA.objects.filter(name__contains='f')
			self.assertEqual(len(tas1), 1)
	
	def test_cache_invalidation_on_single_table_when_updating(self):
		"Test that a call to update() invalidates the cache and results in a new DB query."
		with self.assertNumQueries(1):
			tas1 = TestModelA.objects.filter(name__contains='f')
			self.assertEqual(len(tas1), 1)
		
		with self.assertNumQueries(0):
			tas1 = TestModelA.objects.filter(name__contains='f')
			self.assertEqual(len(tas1), 1)
		
		with self.assertNumQueries(1):
			TestModelA.objects.update(last_edited=datetime.now())
		
		with self.assertNumQueries(1):
			tas1 = TestModelA.objects.filter(name__contains='f')
			self.assertEqual(len(tas1), 1)
		
		with self.assertNumQueries(0):
			tas1 = TestModelA.objects.filter(name__contains='f')
			self.assertEqual(len(tas1), 1)
		
	#TODO: test select_related(), invalidation from related model
	#TODO: test one type of query on a model doesn't screw up other queries
	#TODO: test that updating one entry will invalidate all cached entries for that table
	#TODO: more testing for utility functions
	#TODO: test extra(), annotate(), aggregate(), and raw SQL
	#TODO: use proper func wrapping when patching
	#TODO: test size limiting and table exclusion features
	#TODO: test transaction rollback
	
	def tearDown(self):
		#TODO: add code to automatically clear cache between tests when installed
		cache.clear()
