# By: Asif Al Shahariar
# this file contains unit tests for the policy level modules

from tests.fixtures.policy_fixtures import policy_level_from_dict
from tests.fixtures.policy_fixtures import PolicyLevelDictFactory,PolicyLevelFactory
from tests.fixtures.routine_fixtures import RoutineFactory, RoutineDictFactory
from unittest import TestCase
from unittest.mock import MagicMock, patch
import pandas as pd


# Patch pandas.read_csv before anything imports Routine
fake_df = pd.DataFrame({'value': ['World', 'Bar']}, index=['Hello', 'Foo'])

with patch('pandas.read_csv', return_value=fake_df):
    from objects.policy_level import PolicyLevel
    from objects.routine import Routine
    from utils import var_names

class TestPolicyLevel(TestCase):
    def setUp(self):
        self.policy_level = PolicyLevelFactory()
        self.routine = self.policy_level.routines[0]


    def test_create_level(self):
        # use dynamically generated data for PolicyLevel
        details = {
            var_names.assignee_level: self.policy_level.level,
            var_names.level_minutes: self.policy_level.minutes,
            var_names.routines: self.policy_level.routines
        }
        created_level = PolicyLevel.create_level(details)
        self.assertIsInstance(created_level, PolicyLevel)
        self.assertEqual(created_level.level, self.policy_level.level)
        self.assertEqual(created_level.minutes, self.policy_level.minutes)
        self.assertEqual(len(created_level.routines), len(self.policy_level.routines))

    def test_get_on_call(self):
        # Mock the routine's get_on_call method to return a known value
        mock_routine = MagicMock()
        mock_routine.get_on_call.return_value = [('user1', 'User One', 1)]
        self.policy_level.routines = [mock_routine]  # overriding the routine dict from factory
        result = self.policy_level.get_on_call()
        self.assertEqual(result, [('user1', 'User One', 1)])

    def test_policy_level_routine_with_no_on_call_users(self):
        # Test get_on_call when routine has no on-call users
        mock_routine = MagicMock()
        mock_routine.get_on_call.return_value = []
        self.policy_level.routines = [mock_routine]  # overriding the routine dict from factory
        on_call_users = self.policy_level.get_on_call()
        self.assertEqual(on_call_users, [])

    def test_get_all_assignees(self):
        # Mock the routine's get_all_assignees method to return a known value
        mock_routine = MagicMock()
        mock_routine.get_all_assignees.return_value = [('user1', 'User One')]
        self.policy_level.routines = [mock_routine]  # overriding the routine dict from factory
        result = self.policy_level.get_all_assignees()
        self.assertEqual(result, [('user1', 'User One')])

    def test_to_dict_basic_info(self):
        # Convert dicts to real Routine objects
        self.policy_level.routines = [Routine.create_routine(r) for r in self.policy_level.routines]

        # Match the actual structure returned by to_dict (list of [name, id])
        routine_dicts = [[routine.routine_name, routine.routine_id] for routine in self.policy_level.routines]

        expected_dict = {
            var_names.assignee_level: self.policy_level.level,
            var_names.level_minutes: self.policy_level.minutes,
            var_names.routines: routine_dicts
        }

        result = self.policy_level.to_dict(basic_info=True)
        self.assertEqual(result, expected_dict)


    def test_to_dict_full_info(self):
        # Convert dicts to real Routine objects
        self.policy_level.routines = [Routine.create_routine(r) for r in self.policy_level.routines]

        # Get full routine dicts
        routine_dicts = [routine.to_dict(basic_info=False) for routine in self.policy_level.routines]

        expected_dict = {
            var_names.assignee_level: self.policy_level.level,
            var_names.level_minutes: self.policy_level.minutes,
            var_names.routines: routine_dicts
        }

        result = self.policy_level.to_dict(basic_info=False)
        self.assertEqual(result, expected_dict)


    def test_create_level_invalid_details(self):
        # Test create_level with missing keys in details
        details = {
            var_names.assignee_level: self.policy_level.level,
            # Missing level_minutes
            var_names.routines: self.policy_level.routines  # a list of Routine objects
        }
        with self.assertRaises(KeyError):
            PolicyLevel.create_level(details)

    def test_get_on_call_empty(self):
        # Test get_on_call when no routines have on-call users
        mock_routine = MagicMock()
        mock_routine.get_on_call.return_value = []
        self.policy_level.routines = [mock_routine]  # overriding the routine dict from factory
        result = self.policy_level.get_on_call()
        self.assertEqual(result, [])


    def test_get_all_assignees_empty(self):
        mock_routine = MagicMock()
        mock_routine.get_all_assignees.return_value = []
        self.policy_level.routines = [mock_routine]  # overriding the routine dict from factory
        result = self.policy_level.get_all_assignees()
        self.assertEqual(result, [])

    def test_create_level_with_invalid_routine(self):
        # Test create_level with a routine that is not a valid Routine object(highly unlikely but for robustness)
        details = {
            var_names.assignee_level: self.policy_level.level,
            var_names.level_minutes: self.policy_level.minutes,
            var_names.routines: [{'invalid_key': 'value'}]  # Invalid routine data
        }
        with self.assertRaises(KeyError):
            PolicyLevel.create_level(details)

    def test_get_on_call_with_none_datetime(self):
        # Test get_on_call with check_datetime as None
        mock_routine = MagicMock()
        mock_routine.get_on_call.return_value = [('user1', 'User One', 1)]
        self.policy_level.routines = [mock_routine]
        on_call_users = self.policy_level.get_on_call(check_datetime=None)
        self.assertEqual(on_call_users, [('user1', 'User One', 1)])

    def test_policy_level_initialization(self):
        # Test initialization with valid parameters
        level = 1
        minutes = 30
        routines = [self.routine]
        policy_level = PolicyLevel(level, minutes, routines)
        self.assertEqual(policy_level.level, level)
        self.assertEqual(policy_level.minutes, minutes)
        self.assertEqual(policy_level.routines, routines)


##########  -------- This is a valid use case for PolicyLevel -------------- ##########
    
    # def test_policy_level_duplicate_routines(self):
    #     # Test adding the same routine instance multiple times
    #     with self.assertRaises(ValueError):
    #         PolicyLevel(1, 30, [self.routine, self.routine])

##### ------ This will happen when there a group for a policy followed by a person from the same routine --------- ########
    #########                         checked through database queries in the codebase                      #########
    
#######     No check for invalid initialization values like negative minutes or level, or None routines   #######
######      This are handled by other parts of the codebase, not in PolicyLevel class itself   ##########


    def test_policy_level_routine_with_empty_assignees(self):
        # Test get_all_assignees when routine has no assignees
        mock_routine = MagicMock()
        mock_routine.get_all_assignees.return_value = []
        self.policy_level.routines = [mock_routine]  # overriding the routine dict from factory
        assignees = self.policy_level.get_all_assignees()
        self.assertEqual(assignees, [])

    def test_policy_level_routine_with_none(self):
        # Test create_level with None as routine
        details = {
            var_names.assignee_level: self.policy_level.level,
            var_names.level_minutes: self.policy_level.minutes,
            var_names.routines: [None]  # None as routine
        }
        with self.assertRaises(TypeError):
            PolicyLevel.create_level(details)

    def test_policy_level_routine_with_malformed_routine(self):
        # Test create_level with a malformed routine object
        details = {
            var_names.assignee_level: self.policy_level.level,
            var_names.level_minutes: self.policy_level.minutes,
            var_names.routines: [{'routine_name': 'Invalid Routine'}]  # Missing required fields
        }
        with self.assertRaises(KeyError):
            PolicyLevel.create_level(details)
