# -*- encoding: utf-8 -*-
#   Copyright 2008 Agile42 GmbH, Berlin (Germany)
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#   Authors: 
#        - Sebastian Schulze <sebastian.schulze__at__agile42.com>
#        - Andrea Tomasini <andrea.tomasini__at__agile42.com>

import unittest
from datetime import timedelta, datetime
from trac.util.datefmt import parse_date, to_datetime

from agilo.scrum.backlog import RemainingTime
from agilo.scrum.metrics import TeamMetrics
from agilo.scrum.team import TeamMemberModelManager, \
    TeamModelManager, TeamMemberCalendar, TeamController
from agilo.utils import Key, Type, Status
from agilo.utils.db import get_user_attribute_from_session, \
    set_user_attribute_in_session
from agilo.test import TestEnvHelper


class TestTeam(unittest.TestCase):
    """Tests for the team model class Team"""
    
    def setUp(self):
        self.teh = TestEnvHelper()
        self.env = self.teh.get_env()
        self.tm = TeamModelManager(self.env)
        self.tmm = TeamMemberModelManager(self.env)
        self.controller = TeamController(self.env)
    
    def testCapacityHours(self):
        """Test the get_capacity_hours() method"""
        test_team = self.tm.create(name="Team#1")
        self.assertTrue(test_team.exists)
        test_members = (self.tmm.create(name="Member#1", team=test_team),
                        self.tmm.create(name="Member#2", team=test_team, 
                                              default_capacity=[4,4,4,0,0,0,0]),
                        self.tmm.create(name="Member#3", team=test_team, 
                                              default_capacity=[0,0,0,2,2,0,0]))
        for tm in test_members:
            self.assertNotEqual(None, tm.team)
            self.assertEqual(test_team, tm.team)
        
        # test the default constructor
        start_date = parse_date("2008-09-08T08:00:00")
        test_sprint =  self.teh.create_sprint(name="TestSprint", start=start_date, 
                                              duration=10)
        
        # test save and restore
        for member in test_members:
            self.assertTrue(self.tmm.save(member))
        
        test_sprint.team = test_team
        test_sprint.save()
        
        weekly_hours = (5 * 6) + (4 + 4 + 4) + (2 + 2)
        self.assertEqual(weekly_hours, test_team.get_capacity_hours_for_week())
        
        sprint_hours = 2 * weekly_hours
        self.assertEqual(sprint_hours, test_team.get_capacity_hours_for_interval(test_sprint.start,
                                                                                 test_sprint.end))
    
    def testVelocityForSprint(self):
        """Tests the set velocity for a sprint as a team metric"""
        test_team = self.tm.create(name="Team#1")
        self.assertTrue(test_team.save())
        self.tmm.create(name="Member#1", team=test_team).save()
        self.tmm.create(name="Member#2", team=test_team).save()
        self.tmm.create(name="Member#3", team=test_team).save()
        sprint = self.teh.create_sprint("TestSprint", team=test_team)
        us1 = self.teh.create_ticket(Type.USER_STORY, props={Key.STORY_POINTS: '5',
                                                             Key.SPRINT: sprint.name})
        us2 = self.teh.create_ticket(Type.USER_STORY, props={Key.STORY_POINTS: '8',
                                                             Key.SPRINT: sprint.name})
        us3 = self.teh.create_ticket(Type.USER_STORY, props={Key.STORY_POINTS: '13',
                                                             Key.SPRINT: sprint.name})
        # we have to use the TeamController here
        cmd_store = TeamController.StoreTeamVelocityCommand(self.env,
                                                            sprint=sprint,
                                                            team=test_team,
                                                            estimated=True)
        self.assertEqual(self.controller.process_command(cmd_store), 26)
        # Now close a story and check the actual velocity
        cmd_get = TeamController.GetStoredTeamVelocityCommand(self.env,
                                                              sprint=sprint.name,
                                                              team=test_team,
                                                              estimated=True)
        us3[Key.STATUS] = Status.CLOSED
        us3.save_changes('tester', 'closed US3')
        self.assertEqual(self.controller.process_command(cmd_get), 26)
        cmd_store.estimated = False
        self.assertEqual(self.controller.process_command(cmd_store), 13)
        cmd_get.estimated = False
        self.assertEqual(self.controller.process_command(cmd_get), 13)
        
    def test_team_commitment(self):
        """Tests store and retrieval of the team commitment of a sprint"""
        test_team = self.tm.create(name="Team#1")
        sprint = self.teh.create_sprint("TestSprint", team=test_team)
        self.assertTrue(test_team.exists)
        # Set the initial USP/RT ratio to 2
        tm = TeamMetrics(self.env, sprint, test_team)
        tm[Key.RT_USP_RATIO] = 2
        tm.save()
        
        tm1 = self.tmm.create(name="Member#1", team=test_team)
        self.tmm.create(name="Member#2", team=test_team)
        self.tmm.create(name="Member#3", team=test_team)
        us1 = self.teh.create_ticket(Type.USER_STORY, props={Key.STORY_POINTS: '5',
                                                             Key.SPRINT: sprint.name})
        us2 = self.teh.create_ticket(Type.USER_STORY, props={Key.STORY_POINTS: '8',
                                                             Key.SPRINT: sprint.name})
        t1 = self.teh.create_ticket(Type.TASK, props={Key.REMAINING_TIME: '12',
                                                      Key.OWNER: tm1.name,
                                                      Key.SPRINT: sprint.name})
        # This task is not explicitly planned for the sprint, but because it is
        # linked should be calculated
        t2 = self.teh.create_ticket(Type.TASK, props={Key.REMAINING_TIME: '8',
                                                      Key.OWNER: tm1.name})
        # Make sure there is a remaining time entry on the first day of the sprint
        RemainingTime(self.env, t1).set_remaining_time(t1[Key.REMAINING_TIME], 
                                                       sprint.start)
        RemainingTime(self.env, t2).set_remaining_time(t2[Key.REMAINING_TIME], 
                                                       sprint.start)
        us1.link_to(t1)
        us1.link_to(t2)
        us2 = self.teh.load_ticket(ticket=us2)
        self.assertEqual(Type.USER_STORY, us2.get_type())
        self.assertNotEqual(None, us2._calculated)
        # check the estimated remaining time for us2
        self.assertEqual(8 * 2, us2[Key.ESTIMATED_REMAINING_TIME])
        
        cmd_class = TeamController.CalculateAndStoreTeamCommitmentCommand
        cmd_store_commitment = cmd_class(self.env, sprint=sprint, team=test_team)
        commitment = TeamController(self.env).process_command(cmd_store_commitment)
        self.assertEqual(12 + 8 + 8 * 2, commitment)
        cmd_get_commitment = TeamController.GetTeamCommitmentCommand(self.env,
                                                                     sprint=sprint,
                                                                     team=test_team)
        self.assertEqual(commitment, 
                         self.controller.process_command(cmd_get_commitment))
    
    def test_get_team_metrics_command_returns_none_if_no_team_given(self):
        sprint_without_team = self.teh.create_sprint('FooSprint')
        cmd = TeamController.GetTeamCommitmentCommand(self.env, sprint=sprint_without_team)
        commitment = TeamController(self.env).process_command(cmd)
        self.assertEqual(None, commitment)


class TestTeamMember(unittest.TestCase):
    
    def setUp(self):
        self.teh = TestEnvHelper()
        self.tmm = TeamMemberModelManager(self.teh.get_env())
        
    def tearDown(self):
        self.teh.cleanup()

    def testTimeSheet(self):
        """Tests the accessor and mutator methods for attribute "time_sheet"."""
        test_ts_1 = [1.5, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]
        test_ts_2 = [7.3, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0]
        # test the default constructor, capacity should return decimals
        team_member = self.tmm.create(name="Team Member #1", default_capacity=test_ts_1)
        self.assertEqual(team_member.capacity, test_ts_1)
        self.tmm.get_cache().invalidate()
        team_member = self.tmm.get(name="Team Member #1")
        self.assertEqual(team_member.capacity, test_ts_1)
        
        team_member.capacity = test_ts_2
        self.assertEqual(team_member.capacity, test_ts_2)
        self.assertTrue(self.tmm.save(team_member))
        team_member = self.tmm.get(name="Team Member #1")
        self.assertEqual(team_member.capacity, test_ts_2)
        
    def test_set_get_session_attribute(self):
        """Tests the set and get of session attribute"""
        team_member = self.tmm.create(name="test_member")
        env = self.teh.get_env()
        set_user_attribute_in_session(env,
                                      'test',
                                      'Test',
                                      team_member.name)
        self.assertEqual('Test',
                         get_user_attribute_from_session(env, 
                                                         'test',
                                                         team_member.name))
    
    def test_set_get_full_name(self):
        """Tests the set and get of the fullname of a Team Member"""
        team_member = self.tmm.create(name="test_member")
        team_member.full_name = "Test Member"
        self.assertEqual(team_member.full_name, "Test Member")
        
    def test_set_get_email(self):
        """Tests the set and get of the email of a TeamMember"""
        team_member = self.tmm.create(name="test_member")
        team_member.email = "test.member@test.com"
        self.assertEqual(team_member.email, "test.member@test.com")
        
    def test_team_member_in_team(self):
        """Tests the TeamMember is a member of the Team"""
        team = self.teh.create_team(name="Test Team")
        tm1 = self.tmm.create(name="tm1", team=team)
        self.assertTrue(tm1.exists)
        tm2 = self.tmm.create(name="tm2", team=team)
        self.assertTrue(tm2.exists)
        tm3 = self.tmm.create(name="tm3")
        self.assertTrue(tm3.exists)
        # Check members of the team using the __cmp__ of persistent object
        self.assertEqual(len(team.members), 2)
        self.assertTrue(tm1 in team.members)
        self.assertTrue(tm2 in team.members)
        self.assertFalse(tm3 in team.members)
        
    def test_team_member_data_in_session_are_safe(self):
        """Tests that the team member data stored in the session are safe"""
        # Create a team member in the session simulating registration
        db = self.teh.get_env().get_db_cnx()
        cursor = db.cursor()
        full_name = "Team Member Test"
        email = "team@member.test"
        cursor.execute("INSERT INTO session_attribute (sid, authenticated, name, value)" \
                       " VALUES (%s, %s, %s, %s)", ['tm', 1, 'name', full_name])
        cursor.execute("INSERT INTO session_attribute (sid, authenticated, name, value)" \
                       " VALUES (%s, %s, %s, %s)", ['tm', 1, 'email', email])
        db.commit()
        # Now load the TeamMember object
        tm = self.tmm.create(name='tm')
        self.assertEqual(full_name, tm.full_name)
        self.assertEqual(email, tm.email)
        
        
class TestTeamMemberCalendar(unittest.TestCase):
    """Tests the team member calendar object"""
    def setUp(self):
        self.teh = TestEnvHelper()
        self.tm = self.teh.create_member(name='TM1')
        self.tm.capacity = [6,6,6,6,6,0,0]
        self.assertTrue(self.tm.save())
        self.tmc = TeamMemberCalendar(self.teh.get_env(), self.tm)
        
    def tearDown(self):
        self.teh.cleanup()
        
    def testCalendarForTeamMember(self):
        """Tests the calendar for a TeamMember"""
        today = to_datetime(datetime(2008, 9, 1)).date()
        one_day = timedelta(days=1)
        self.tmc.set_hours_for_day(4, today)
        self.assertEqual(self.tmc.get_hours_for_day(today), 4)
        self.assertEqual(self.tmc.get_hours_for_day(today + one_day), 6)
        # Now save and check if is still there
        self.assertTrue(self.tmc.save())
        # reload
        tmc2 = TeamMemberCalendar(self.teh.get_env(), self.tm)
        self.assertEqual(tmc2.get_hours_for_day(today), 4)
        self.assertEqual(tmc2.get_hours_for_day(today + one_day), 6)
        
    def testCalendarForTeamMemberInterval(self):
        """Tests the calendar returned for a given interval of time"""
        today = to_datetime(None).date()
        one_day = timedelta(days=1)
        self.tmc.set_hours_for_day(5, today)
        cal = self.tmc.get_hours_for_interval(today - (3 * one_day),
                                              today + (3 * one_day))
        self.assertEqual(cal[today], 5)
        self.assertEqual(len(cal), 7)


if __name__ == '__main__':
    from agilo.test.testfinder import run_unit_tests
    run_unit_tests(__file__)

