#!/usr/bin/env GoodTests.py
'''
    test_Insert - General "Insert" test
'''

import subprocess
import sys
import traceback
import uuid

import LocalConfig


import ichorORM

from ichorORM.model import DatabaseModel
from ichorORM.query import InsertQuery, SelectQuery

from ichor_test_models.all import Person, Meal

class TestInsertQuery(object):
    '''
        Test class for a InsertQuery
    '''

    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_transactionInsert, ):

            # 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


    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_transactionInsert, ):
            self._deleteGlobalDatasets()


    def test_generalInsert(self):
        '''
            test_generalInsert - A general InsertQuery test
        '''

        insQ = InsertQuery(Person)

        # Set one field with setFieldValue
        insQ.setFieldValue('birth_month', 2)

        # Set several fields via setFieldValues
        insQ.setFieldValues( {"first_name" : "John", "last_name" : "Smith", "age" : 35, "datasetuid" : self.datasetUid } )


        gotException = False
        try:
            insertedId = insQ.executeInsert(returnPk=True)
        except Exception as e:
            gotException = e
            excInfo = sys.exc_info()
            traceback.print_exception(*excInfo)

        assert gotException is False , 'Expected to be able to insert object, but got Exception  %s:  %s' %(str(type(gotException)), str(gotException) )

        assert insertedId , 'Expected to have id returned, but did not'

        # Do a raw select here to minimize overlap of tests

        dbConn = ichorORM.getDatabaseConnection()

        resultRows = dbConn.doSelect("SELECT first_name, last_name, age, birth_day, birth_month FROM Person WHERE id = %d" %(insertedId, ))

        assert resultRows , 'Did not get a result from our select query.'

        assert len(resultRows) == 1 , 'Expected to get one result, but got %d' %(len(resultRows), )

        resultRow = resultRows[0]
        
        assert resultRow[0] == 'John' , 'Expected first_name to be set to "John" but got: %s' %(repr(resultRow[0]), )
        assert resultRow[1] == 'Smith' , 'Expected last_name to be set to "Smith" but got: %s' %(repr(resultRow[1]), )
        assert str(resultRow[2]) == '35' , 'Expected age to be set to 35 but got: %s' %(repr(resultRow[2]), )
        assert resultRow[3] is None , 'Expected "birth_day" field to be NULL because was not set. Got: %s' %(repr(resultRow[3]), )

        assert str(resultRow[4]) == '2' , 'Expected "birth_month" to be set to 2 but got: %s' %(repr(resultRow[4]), )


    def test_transactionInsert(self):
        '''
            test_transactionInsert - Here we do several inserts within a transaction, and make sure it works properly.
        '''

        dbConn = ichorORM.getDatabaseConnection(isTransactionMode=True)

        # personIds - A list of all primary keys we insert
        personIds = []

        # personIdToDataset - The resulting primary key to the dataset used for insert
        personIdToDataset = {}

        for dataEntry in self.DEFAULT_PERSON_DATASET:
            
            insQ = InsertQuery(Person, initialFieldValues=dataEntry)

            gotException = False
            # Add an INSERT into this transaction but don't commit yet.
            #  Since read-commit, the SELECT from serial sequence to obtain primary key
            #  should return right away.
            try:
                personId = insQ.executeInsert(dbConn=dbConn, doCommit=False, returnPk=True)
            except Exception as e:
                gotException = e
                excInfo = sys.exc_info()
                traceback.print_exception(*excInfo)

            assert gotException == False , 'Got an exception trying to add an INSERT onto transaction. Model is %s\nException is %s:  %s' %( repr(dataEntry), str(type(gotException)), str(gotException) )

            assert personId , 'executeInsert(...returnPk=True) in transaction did not return primary key, but should have from read-level isolation mode (isTransactionMode=True)'

            assert personId not in personIds , 'Got a duplicate primary key returned: %s' %(repr(personId), )

            dataEntry['id'] = personId

            personIds.append(personId)

            personIdToDataset[ personId ] = dataEntry

        # Ok, queued up all the inserts into this transaction.
        #
        #  Now, lets run a couple SELECTs to ensure these objects are not available prior to insert
        #  Again, using raw queries here instead of ichorORM to isolate testing

        selDbConn = ichorORM.getDatabaseConnection(isTransactionMode=False)

        idArray = '(' + ', '.join([str(personId) for personId in personIds]) + ')'

        results = selDbConn.doSelect('SELECT id, first_name, last_name, eye_color, age, birth_day, birth_month FROM Person WHERE id in ' + idArray)

        assert not results , 'Expected to get no results fetching by primary keys of yet-to-be-commited objects. Instead, got: ' + repr(results)

        # Ok, now we commit!
        gotException = False
        try:
            dbConn.commit()
        except Exception as e:
            gotException = e
            excInfo = sys.exc_info()
            traceback.print_exception(*excInfo)

        assert gotException == False , 'Got an exception calling commit: %s  %s' %( str(type(gotException)), str(gotException) )

        # We have committed, the records should now be there.
        results = selDbConn.doSelect('SELECT id, first_name, last_name, eye_color, age, birth_day, birth_month FROM Person WHERE id in ' + idArray)

        assert results , 'Expected to get results fetching by primary keys after transaction committed. Did not.'

        assert len(results) == len(personIds) , 'Expected to get 1 record for each record inserted. Got %d back but did %d inserts.' % \
                ( len(results), len(personIds) )


        # Iterate through results and make sure fields are all correct
        for resultRow in results:
            try:
                (_id, first_name, last_name, eye_color, age, birth_day, birth_month ) = resultRow
            except:
                raise AssertionError("Resulting row did not return the number of fields selected. Got: " + repr(resultRow))

            thisResultDict = { "id" : _id, "first_name" : first_name, "last_name" : last_name,
                        "eye_color" : eye_color, "age" : age, "birth_day" : birth_day, "birth_month" : birth_month,
            }

            sourceData = personIdToDataset[_id]

            for fieldName, fieldValue in thisResultDict.items():
                
                sourceValue = sourceData[fieldName]
                assert fieldValue == sourceValue , 'Got unexpected value for field "%s". We set with "%s" but got back "%s".\nFull source fields/values: %s\nFull returned fields/values: %s\n' %( fieldName, sourceValue, fieldValue, repr(sourceData), repr(thisResultDict))



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())
