#!/usr/bin/env GoodTests.py
'''
    test_Relations - General "Relations" 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 TestRelations(object):
    '''
        Test class for Relations
    '''

    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_oneToOneRelation, self.test_oneToManyRelation ):

            # 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]

            #     FIELDS = ['id', 'food_group', 'item_name', 'price', 'id_person', 'datasetUid']

            mealPks = []

            self.DEFAULT_MEAL_DATASET = [ ]

            self.personHasPizza = []

            for i in range(len(self.DEFAULT_PERSON_DATASET)):
                
                personDataset = self.DEFAULT_PERSON_DATASET[i]

                mealPk = dbConn.doInsert(query="INSERT INTO meal ( food_group, item_name, price, id_person, datasetuid ) VALUES ('desert', 'ice cream', 2.38, %d, '%s')" %( personDataset['id'], self.datasetUid ), returnPk=True )

                mealPks.append(mealPk)

                if i in (0, 2):
                    mealPk = dbConn.doInsert(query="INSERT INTO meal ( food_group, item_name, price, id_person, datasetuid ) VALUES ('awesome', 'pizza', 5.99, %d, '%s')" %( personDataset['id'], self.datasetUid ), returnPk=True )

                    mealPks.append(mealPk)

                    self.personHasPizza.append(personDataset['id'])
                    


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


    def test_oneToOneRelation(self):
        '''
            test_oneToOneRelation - Test one to one relations
        '''
        allPersons = Person.filter(datasetuid=self.datasetUid, orderByField='id', orderByDir='ASC')

        for i in range(len(allPersons)):
            
            person = allPersons[i]
            relatedMeals = person.meals

            if person.id in self.personHasPizza:
                assert len(relatedMeals) == 2 , 'Expected to get 2 meals for first and third person. Got: ' + repr(relatedMeals)

                foundPizza = False
                foundIceCream = False

                for meal in relatedMeals:
                    if meal.item_name == 'pizza':
                        foundPizza = True
                    elif meal.item_name == 'ice cream':
                        foundIceCream = True

                    assert meal.id_person == person.id , 'Bad relation! meal.id_person %d != person.id %d' %(meal.id_person, person.id)

                assert foundPizza is True , 'Did not find pizza related model for person %d  %s' %( i, repr(person) )
            else:
                
                assert len(relatedMeals) == 1 , 'Expected to get 1 meal for non-first-or-third person. Got: ' + repr(relatedMeals)

                meal = relatedMeals[0]

                assert meal.item_name == 'ice cream' , 'Expected to find ice cream, but got meal: ' + repr(meal)
                assert meal.id_person == person.id , 'Bad relation! meal.id_person %d != person.id %d' %(meal.id_person, person.id)
        


    def test_oneToManyRelation(self):
        '''
            test_oneToManyRelation - Test one to many relations
        '''
        allMeals = Meal.filter(datasetuid=self.datasetUid)

        for meal in allMeals:
            
            relatedPerson = meal.person

            assert relatedPerson , 'Expected to get a person from this meal, but did not. Meal is: ' + repr(meal)

            assert meal.id_person == relatedPerson.id , 'Bad relation! meal.id_person %d != person.id %d' %(meal.id_person, relatedPerson.id)



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