#!/usr/bin/env GoodTests.py
'''
    test_Update - General "Update" test
'''

import copy
import subprocess
import sys
import traceback
import uuid

import LocalConfig


import ichorORM

from ichorORM.model import DatabaseModel
from ichorORM.query import UpdateQuery, SelectQuery, QueryStr

from ichor_test_models.all import Person, Meal

class TestUpdateQuery(object):
    '''
        Test class for a UpdateQuery
    '''

    def setup_class(self):
        '''
            setup_class - ensure this test is setup.
                Executed prior to any of the tests in this class.
        '''
        LocalConfig.ensureTestSetup()

        self.datasetUid = str(uuid.uuid4())

    def _deleteDataset(self, tableName):
        '''
            _deleteDataset - Delete all records in a given table which have a field, "datasetuid",
                which is generated at the start of this test and is unique to this run-through

                @param tableName <str> - The name of the SQL table
        '''
        try:
            dbConn = ichorORM.getDatabaseConnection()
            dbConn.executeSql("DELETE FROM %s WHERE datasetUid = '%s'" %(tableName, self.datasetUid, ))
        except Exception as e:
            sys.stderr.write('Error deleting all %s objects with dataset uid "%s": %s  %s\n' % 
                (tableName, self.datasetUid, str(type(e)), str(e) )
            )

    def _deleteGlobalDatasets(self):
        '''
            _deleteGlobalDatasets - Deletes all the global datasets matching this test's dataset uid

        '''
        # First, delete from Meal which refrences Person
        self._deleteDataset(Meal.TABLE_NAME)
        
        # Now can delete the Person from this dataset
        self._deleteDataset(Person.TABLE_NAME)


    def teardown_class(self):
        '''
            teardown_class - Destroy any data generated by this test.
                Ran after all tests have completed
        '''
        self._deleteGlobalDatasets()


    def setup_method(self, meth):
        '''
            setup_method - Called prior to each method to perform setup specific to it.

                @param meth <built-in method> - The method being tested (compare meth == self.someMethod)
        '''
        
        if meth in ( self.test_generalUpdate, self.test_updateTransaction, self.test_updateWithQueryStr ):

            # self.DEFAULT_PERSON_DATASET - A sample dataset of field -> value for Person model
            self.DEFAULT_PERSON_DATASET = [
                { "first_name" : "John", "last_name" : "Smith", "eye_color" : "blue",
                    'age' : 35, "birth_month" : 4, "birth_day" : 16 },
                { "first_name" : "John", "last_name" : "Doe", "eye_color" : "blue",
                    'age' : 22, "birth_month" : 4, "birth_day" : 26 },
                { "first_name" : "Jane", "last_name" : "Doe", "eye_color" : "green",
                    'age' : 19, "birth_month" : 6, "birth_day" : 24 },
                { "first_name" : "Bill", "last_name" : "Johnson", "eye_color" : "brown",
                    'age' : 19, "birth_month" : 1, "birth_day" : 30 },
                { "first_name" : "Ted", "last_name" : "Karma", "eye_color" : "green",
                    'age' : 29, "birth_month" : 4, "birth_day" : 16 },
            ]
            # Mark the dataset id
            for i in range(len(self.DEFAULT_PERSON_DATASET)):
                self.DEFAULT_PERSON_DATASET[i]['datasetuid'] = self.datasetUid

            # Perform the inserts
            dbConn = ichorORM.getDatabaseConnection()

            pks = dbConn.doInsert(query="INSERT INTO person ( first_name, last_name, eye_color, age, birth_day, birth_month, datasetuid ) VALUES ( %(first_name)s, %(last_name)s, %(eye_color)s, %(age)s, %(birth_day)s, %(birth_month)s, %(datasetuid)s )", valueDicts=self.DEFAULT_PERSON_DATASET, returnPk=True)
            
            self.personIdToDataset = {}
            # Add the "id" to each dataset and build ref map

            for i in range(len(self.DEFAULT_PERSON_DATASET)):
                self.DEFAULT_PERSON_DATASET[i]['id'] = pks[i]

                self.personIdToDataset[ pks[i] ] = self.DEFAULT_PERSON_DATASET[i]



    def teardown_method(self, meth):
        '''
            teardown_method - Called after execution of each method to clean up

                @param meth <built-in method> - The method being tested (compare meth == self.someMethod)
        '''
        if meth in ( self.test_generalUpdate, self.test_updateTransaction, self.test_updateWithQueryStr ):
            self._deleteGlobalDatasets()


    def test_generalUpdate(self):

        janeOrigValues = [x for x in self.DEFAULT_PERSON_DATASET if x['first_name'] == 'Jane' and x['last_name'] == 'Doe']

        assert len(janeOrigValues) == 1 , 'Expected to find "Jane Doe" in same set but could not.'

        janeOrigValues = janeOrigValues[0]

        janeId = janeOrigValues['id']

        upQ = UpdateQuery(Person)

        upQ.setFieldValue('age', 21)
        upQ.setFieldValue('eye_color', 'red')

        upQWhere = upQ.addStage()
        upQWhere.addCondition('id', '=', janeId)

        gotException = False
        try:
            upQ.execute()
        except Exception as e:
            gotException = e

        assert gotException == False , 'Got exception on update: %s  %s' %( str(type(gotException)), str(gotException) )

        # Check that the update occured
        dbConn = ichorORM.getDatabaseConnection()

        results = dbConn.doSelect("SELECT id, first_name, last_name, eye_color, age, birth_day, birth_month, datasetuid FROM Person WHERE datasetuid = '%s'" %( self.datasetUid, ))

        assert results , 'Did not get results back from select.'

        assert len(results) == len(self.DEFAULT_PERSON_DATASET) , 'Did not get expected number of results back. Expected %d but got %d' %( len(self.DEFAULT_PERSON_DATASET), len(results))

        for resultRow in results:
            
            ( _id, first_name, last_name, eye_color, age, birth_day, birth_month, datasetuid ) = resultRow

            myFetchedValues = { 'id' : _id, 'first_name' : first_name, 'last_name' : last_name, 'eye_color' : eye_color,
                    'age' : age, 'birth_day' : birth_day, 'birth_month' : birth_month, 'datasetuid' : datasetuid,
            }

            if _id == janeId:
                # Update our sample with the new fields
                janeNewValues = copy.deepcopy(janeOrigValues)
                janeNewValues['age'] = 21
                janeNewValues['eye_color'] = 'red'

                testAgainstValues = janeNewValues
            else:
                testAgainstValues = self.personIdToDataset[_id]

            for fieldName, fieldValue in myFetchedValues.items():
                
                expectedValue = testAgainstValues[fieldName]

                assert fieldValue == expectedValue , 'Got unexpected value after Jane Doe update. On person %s %s  field "%s" does not have expected value %s. Fetched value was %s' %( first_name, last_name, fieldName, repr(expectedValue), repr(fieldValue) ) 


    def test_updateTransaction(self):
        '''
            test_updateTransaction - Test doing some updates within a transaction
        '''
        janeOrigValues = [x for x in self.DEFAULT_PERSON_DATASET if x['first_name'] == 'Jane' and x['last_name'] == 'Doe']

        assert len(janeOrigValues) == 1 , 'Expected to find "Jane Doe" in same set but could not.'

        janeOrigValues = janeOrigValues[0]
        janeId = janeOrigValues['id']

        johnSmithOrigValues = [x for x in self.DEFAULT_PERSON_DATASET if x['first_name'] == 'John' and x['last_name'] == 'Smith']

        assert len(johnSmithOrigValues) == 1 , 'Expected to find "John Smith" in same set but could not.'

        johnSmithOrigValues = johnSmithOrigValues[0]
        johnSmithId = johnSmithOrigValues['id']

        dbConnTrans = ichorORM.getDatabaseConnection(isTransactionMode=True)

        # Add one update query to transaction for jane
        upQ = UpdateQuery(Person)

        upQ.setFieldValue('age', 21)
        upQ.setFieldValue('eye_color', 'red')

        upQWhere = upQ.addStage()
        upQWhere.addCondition('id', '=', janeId)

        gotException = False
        try:
            upQ.execute(dbConn=dbConnTrans, doCommit=False)
        except Exception as e:
            gotException = e

        assert gotException == False , 'Got exception on update of Jane Doe: %s  %s' %( str(type(gotException)), str(gotException) )

        # Do another update query for John in same transaction
        upQ = UpdateQuery(Person)

        upQ.setFieldValue('age', 25)
        upQ.setFieldValue('eye_color', 'purple')

        upQWhere = upQ.addStage()
        upQWhere.addCondition('first_name', '=', 'John')
        upQWhere.addCondition('last_name', '=', 'Smith')
        upQWhere.addCondition('datasetuid', '=', self.datasetUid)

        gotException = False
        try:
            upQ.execute(dbConn=dbConnTrans, doCommit=False)
        except Exception as e:
            gotException = e

        assert gotException == False , 'Got exception on update of John Smith: %s  %s' %( str(type(gotException)), str(gotException) )

        # We have not yet committed, so make sure no changes took place
        dbConn = ichorORM.getDatabaseConnection()

        results = dbConn.doSelect("SELECT id, first_name, last_name, eye_color, age, birth_day, birth_month, datasetuid FROM Person WHERE datasetuid = '%s'" %( self.datasetUid, ))

        assert results , 'Did not get results back from select.'

        assert len(results) == len(self.DEFAULT_PERSON_DATASET) , 'Did not get expected number of results back. Expected %d but got %d' %( len(self.DEFAULT_PERSON_DATASET), len(results))

        for resultRow in results:
            
            ( _id, first_name, last_name, eye_color, age, birth_day, birth_month, datasetuid ) = resultRow

            myFetchedValues = { 'id' : _id, 'first_name' : first_name, 'last_name' : last_name, 'eye_color' : eye_color,
                    'age' : age, 'birth_day' : birth_day, 'birth_month' : birth_month, 'datasetuid' : datasetuid,
            }

            testAgainstValues = self.personIdToDataset[_id]

            for fieldName, fieldValue in myFetchedValues.items():
                
                expectedValue = testAgainstValues[fieldName]

                assert fieldValue == expectedValue , 'Got unexpected value before transaction commit. On person %s %s  field "%s" does not have expected value %s. Fetched value was %s' %( first_name, last_name, fieldName, repr(expectedValue), repr(fieldValue) ) 

        gotException = False
        try:
            dbConnTrans.commit()
        except Exception as e:
            gotException = e

        assert gotException == False , 'Got exception on transaction commit. %s  %s' %(str(type(e)), str(e))

        # Check that the update occured, now that we have committed the update transaction
        dbConn = ichorORM.getDatabaseConnection()

        results = dbConn.doSelect("SELECT id, first_name, last_name, eye_color, age, birth_day, birth_month, datasetuid FROM Person WHERE datasetuid = '%s'" %( self.datasetUid, ))

        assert results , 'Did not get results back from select.'

        assert len(results) == len(self.DEFAULT_PERSON_DATASET) , 'Did not get expected number of results back. Expected %d but got %d' %( len(self.DEFAULT_PERSON_DATASET), len(results))

        for resultRow in results:
            
            ( _id, first_name, last_name, eye_color, age, birth_day, birth_month, datasetuid ) = resultRow

            myFetchedValues = { 'id' : _id, 'first_name' : first_name, 'last_name' : last_name, 'eye_color' : eye_color,
                    'age' : age, 'birth_day' : birth_day, 'birth_month' : birth_month, 'datasetuid' : datasetuid,
            }

            if _id == janeId:
                # Update our sample with the new fields
                janeNewValues = copy.deepcopy(janeOrigValues)
                janeNewValues['age'] = 21
                janeNewValues['eye_color'] = 'red'

                testAgainstValues = janeNewValues
            elif _id == johnSmithId:
                johnSmithNewValues = copy.deepcopy(johnSmithOrigValues)
                johnSmithNewValues['age'] = 25
                johnSmithNewValues['eye_color'] = 'purple'

                testAgainstValues = johnSmithNewValues
            else:
                testAgainstValues = self.personIdToDataset[_id]

            for fieldName, fieldValue in myFetchedValues.items():
                
                expectedValue = testAgainstValues[fieldName]

                assert fieldValue == expectedValue , 'Got unexpected value after update transaction commit. On person %s %s  field "%s" does not have expected value %s. Fetched value was %s' %( first_name, last_name, fieldName, repr(expectedValue), repr(fieldValue) ) 


    def test_updateWithQueryStr(self):
        '''
            test_updateWithQueryStr - This will test an update which uses a QueryStr (inline SQL)
        '''

        # Increment everyone's age by 1
        upQ = UpdateQuery(Person)

        upQ.setFieldValue('age', QueryStr('age + 1'))

        # Apply to all items in this dataset
        upQWhere = upQ.addStage()
        upQWhere.addCondition('datasetuid', '=', self.datasetUid)

        gotException = False
        try:
            upQ.execute()
        except Exception as e:
            gotException = e

        assert gotException == False , 'Got exception on update of all ages + 1: %s  %s' %( str(type(gotException)), str(gotException) )

        # Check that the update occured, now that we have committed the update transaction
        dbConn = ichorORM.getDatabaseConnection()

        results = dbConn.doSelect("SELECT id, first_name, last_name, eye_color, age, birth_day, birth_month, datasetuid FROM Person WHERE datasetuid = '%s'" %( self.datasetUid, ))

        assert results , 'Did not get results back from select.'

        assert len(results) == len(self.DEFAULT_PERSON_DATASET) , 'Did not get expected number of results back. Expected %d but got %d' %( len(self.DEFAULT_PERSON_DATASET), len(results))

        for resultRow in results:
            
            ( _id, first_name, last_name, eye_color, age, birth_day, birth_month, datasetuid ) = resultRow

            myFetchedValues = { 'id' : _id, 'first_name' : first_name, 'last_name' : last_name, 'eye_color' : eye_color,
                    'age' : age, 'birth_day' : birth_day, 'birth_month' : birth_month, 'datasetuid' : datasetuid,
            }

            testAgainstValues = self.personIdToDataset[_id]

            for fieldName, fieldValue in myFetchedValues.items():
                
                expectedValue = testAgainstValues[fieldName]
                # We incremented age, so check against orig age + 1
                if fieldName == 'age':
                    expectedValue += 1

                assert fieldValue == expectedValue , 'Got unexpected value after update incrementing age. On person %s %s  field "%s" does not have expected value %s. Fetched value was %s' %( first_name, last_name, fieldName, repr(expectedValue), repr(fieldValue) ) 


if __name__ == '__main__':
    sys.exit(subprocess.Popen('GoodTests.py -n1 "%s" %s' %(sys.argv[0], ' '.join(['"%s"' %(arg.replace('"', '\\"'), ) for arg in sys.argv[1:]]) ), shell=True).wait())
