#!/usr/bin/env GoodTests.py
'''
    test_DeleteQuery - Test the query builder DeleteQuery
'''

import subprocess
import sys
import traceback
import uuid

import LocalConfig


import ichorORM

from ichorORM.model import DatabaseModel
from ichorORM.query import InsertQuery, SelectQuery, DeleteQuery, QueryStr
from ichorORM import getDatabaseConnection

from ichor_test_models.all import Person, Meal

class MyPersonModel(DatabaseModel):
    '''
        MyPersonModel - A model that could represent a person
    '''

    FIELDS = ['id', 'first_name', 'last_name', 'age', 'birth_day', 'birth_month']

    REQUIRED_FIELDS = ['first_name', 'last_name']

    TABLE_NAME = 'ichortest_my_person_model_delq'

    def getFullName(self):
        return "%s %s" %(self.first_name, self.last_name)


    @classmethod
    def dropModel(cls):
        '''
            dropModel - Will drop this model's table
        '''
        dbConn = getDatabaseConnection()

        didDrop = False
        # Drop table if it exists
        try:
            dbConn.executeSql('DROP TABLE %s' %(cls.TABLE_NAME, ))
            didDrop = True
        except:
            pass

        return didDrop

    @classmethod
    def createModel(cls):
        '''
            createModel - Will create this model
        '''
        cls.dropModel()

        dbConn = getDatabaseConnection()

        createQuery = '''CREATE TABLE ''' + cls.TABLE_NAME + ''' ( id serial primary key, first_name varchar(255) NOT NULL, last_name varchar(255) NOT NULL, age smallint NULL, birth_day smallint NULL, birth_month smallint NULL )'''

        dbConn.executeSql(createQuery)


class TestDeleteQuery(object):
    '''
        Test class for a DeleteQuery
    '''

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

        MyPersonModel.createModel()


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

        MyPersonModel.dropModel()


    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_generalDelete, self.test_deleteWithQueryStr, self.test_deleteWithSelectQuery, self.test_deleteAll ):

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

            dbConn = ichorORM.getDatabaseConnection(isTransactionMode=True)
            pks = dbConn.doInsert("INSERT INTO " + MyPersonModel.TABLE_NAME + " (first_name, last_name, age, birth_day, birth_month) VALUES ( %(first_name)s, %(last_name)s, %(age)s, %(birth_day)s, %(birth_month)s )", valueDicts=self.dataSet, doCommit=False, returnPk=True)

            dbConn.commit()

            for i in range(len(self.dataSet)):
                self.dataSet[i]['id'] = pks[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_generalDelete, self.test_deleteWithQueryStr, self.test_deleteWithSelectQuery, self.test_deleteAll ):
            dbConn = ichorORM.getDatabaseConnection()
            dbConn.executeSql('DELETE FROM %s' %( MyPersonModel.TABLE_NAME, ))


    def _getNumberPeople(self):
        '''
            _getNumberPeople - Get the number of rows in the Person table matching our dataset uid
        '''
        dbConn = getDatabaseConnection()

        query = "SELECT COUNT(*) FROM %s" %( MyPersonModel.TABLE_NAME, )

        results = dbConn.doSelect(query)

        assert results , 'Did not get any results for COUNT(*) query'
        
        assert len(results) == 1 , 'Expected 1 row of results for COUNT(*) query, but got %d.  %s' %(len(results), repr(results))

        return int(results[0][0])
        

    def test_generalDelete(self):
        '''
            test_generalDelete - A general DeleteQuery test
        '''

        dbConn = getDatabaseConnection()

        numberPeople = self._getNumberPeople()

        assert numberPeople == 5 , 'Expected 5 rows in Person model, but got %d' %(numberPeople, )
        
        delQ = DeleteQuery(MyPersonModel)

        delQWhere = delQ.addStage()

        delQWhere.addCondition('first_name', '=', 'John')

        gotException = False
        try:
            delQ.executeDelete()
        except Exception as e:
            gotException = e

        assert gotException is False , "Got unexpected exception trying to delete where first_name = 'John'. %s  %s" %(str(type(gotException)), str(gotException))

        numberPeople = self._getNumberPeople()

        assert numberPeople == 3 , 'Expected 3 rows in Person model after delete, but got %d' %(numberPeople, )

        selQ = SelectQuery(MyPersonModel)

        allPeople = selQ.executeGetObjs()


        assert len(allPeople) == 3 , 'Expected 3 results for all remaining people, but got %d.  %s' %(len(allPeople), repr(allPeople))

        for person in allPeople:
            if person.first_name == 'John':
                raise AssertionError('Found a person with first_name "John" after deleting all Johns. %s' %(repr(person), ))

    def test_deleteWithQueryStr(self):
        '''
            test_deleteWithQueryStr - Test using a QueryStr within DeleteQuery
        '''

        dbConn = getDatabaseConnection()

        numberPeople = self._getNumberPeople()

        assert numberPeople == 5 , 'Expected 5 rows in Person model, but got %d' %(numberPeople, )
        
        delQ = DeleteQuery(MyPersonModel)

        delQWhere = delQ.addStage()

        delQWhere.addCondition('id', 'in', QueryStr("( SELECT id FROM %s WHERE first_name = 'John' )" %(MyPersonModel.TABLE_NAME, ) ) )

        gotException = False
        try:
            delQ.executeDelete()
        except Exception as e:
            gotException = e

        assert gotException is False , "Got unexpected exception trying to delete where first_name = 'John'. %s  %s" %(str(type(gotException)), str(gotException))

        numberPeople = self._getNumberPeople()

        assert numberPeople == 3 , 'Expected 3 rows in Person model after delete, but got %d' %(numberPeople, )

        selQ = SelectQuery(MyPersonModel)

        allPeople = selQ.executeGetObjs()


        assert len(allPeople) == 3 , 'Expected 3 results for all remaining people, but got %d.  %s' %(len(allPeople), repr(allPeople))

        for person in allPeople:
            if person.first_name == 'John':
                raise AssertionError('Found a person with first_name "John" after deleting all Johns. %s' %(repr(person), ))


    def test_deleteWithSelectQuery(self):
        '''
            test_deleteWithSelectQuery - Test using a SelectQuery within DeleteQuery
        '''

        dbConn = getDatabaseConnection()

        numberPeople = self._getNumberPeople()

        assert numberPeople == 5 , 'Expected 5 rows in Person model, but got %d' %(numberPeople, )
        
        delQ = DeleteQuery(MyPersonModel)
        delQWhere = delQ.addStage()

        subSelQ = SelectQuery(MyPersonModel, selectFields=['id'])
        subSelQWhere = subSelQ.addStage()
        subSelQWhere.addCondition('first_name', '=', 'John')

        delQWhere.addCondition('id', 'in', subSelQ)

        gotException = False
        try:
            delQ.executeDelete()
        except Exception as e:
            gotException = e

        assert gotException is False , "Got unexpected exception trying to delete where first_name = 'John'. %s  %s" %(str(type(gotException)), str(gotException))

        numberPeople = self._getNumberPeople()

        assert numberPeople == 3 , 'Expected 3 rows in Person model after delete, but got %d' %(numberPeople, )

        selQ = SelectQuery(MyPersonModel)

        allPeople = selQ.executeGetObjs()


        assert len(allPeople) == 3 , 'Expected 3 results for all remaining people, but got %d.  %s' %(len(allPeople), repr(allPeople))

        for person in allPeople:
            if person.first_name == 'John':
                raise AssertionError('Found a person with first_name "John" after deleting all Johns. %s' %(repr(person), ))


    def test_deleteAll(self):
        '''
            test_deleteAll - Test deleting all records on a table
        '''

        delQ = DeleteQuery(MyPersonModel)

        gotException = False
        try:
            delQ.executeDelete()
        except Exception as e:
            gotException = e

        assert gotException is not False , 'Expected to get an Exception for safety on attempting to execute a delete query without any WHERE stages (i.e. delete all records). Did not.'

        # Try again with the override flag
        gotException = False
        try:
            delQ.executeDelete(allowDeleteAll=True)
        except Exception as e:
            gotException = e

        assert gotException is False , 'Expected to be able to execute a delete query without WHERE stages (i.e. delete all records) with allowDeleteAll=True override flag set. Got exception: %s  %s' %(str(type(gotException)), str(gotException))



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


