# Copyright The IETF Trust 2013-2020, All Rights Reserved
# -*- coding: utf-8 -*-


import datetime

from urllib.parse import urlsplit

from django.urls import reverse as urlreverse

import debug                            # pyflakes:ignore

from ietf.name.models import TimerangeName
from ietf.group.models import Group
from ietf.meeting.models import Schedule, TimeSlot, Session, SchedTimeSessAssignment, Meeting, Constraint
from ietf.meeting.test_data import make_meeting_test_data
from ietf.person.models import Person
from ietf.utils.test_utils import TestCase
from ietf.utils.mail import outbox


class ApiTests(TestCase):
    def test_update_schedule(self):
        meeting = make_meeting_test_data()
        schedule = Schedule.objects.get(meeting__number=72,name="test-schedule")
        mars_session = Session.objects.filter(meeting=meeting, group__acronym="mars").first()
        ames_session = Session.objects.filter(meeting=meeting, group__acronym="ames").first()
    
        mars_scheduled = SchedTimeSessAssignment.objects.get(session=mars_session,schedule__name='test-schedule')
        mars_slot = mars_scheduled.timeslot 

        ames_scheduled = SchedTimeSessAssignment.objects.get(session=ames_session,schedule__name='test-schedule')
        ames_slot = ames_scheduled.timeslot 

        def do_unschedule(assignment):
            url = urlreverse("ietf.meeting.ajax.assignment_json", 
                             kwargs=dict(num=assignment.session.meeting.number, 
                                         owner=assignment.schedule.owner_email(),
                                         name=assignment.schedule.name,
                                         assignment_id=assignment.pk,))
            return self.client.delete(url)

        def do_schedule(schedule,session,timeslot):
            url = urlreverse("ietf.meeting.ajax.assignments_json",
                              kwargs=dict(num=session.meeting.number,
                                          owner=schedule.owner_email(),
                                          name=schedule.name,))
            post_data = '{ "session_id": "%s", "timeslot_id": "%s" }'%(session.pk,timeslot.pk)
            return self.client.post(url,post_data,content_type='application/x-www-form-urlencoded')

        def do_extend(schedule, assignment):
            session = assignment.session
            url = urlreverse("ietf.meeting.ajax.assignments_json",
                              kwargs=dict(num=session.meeting.number,
                                          owner=schedule.owner_email(),
                                          name=schedule.name,))
            post_data = '{ "session_id": "%s", "timeslot_id": "%s", "extendedfrom_id": "%s" }'%(session.pk,assignment.timeslot.slot_to_the_right.pk,assignment.pk)


            return self.client.post(url,post_data,content_type='application/x-www-form-urlencoded')

        # not logged in
        # faulty delete 
        r = do_unschedule(mars_scheduled)
        self.assertEqual(r.status_code, 403)
        self.assertEqual(SchedTimeSessAssignment.objects.get(pk=mars_scheduled.pk).session, mars_session)
        # faulty post
        r = do_schedule(schedule,ames_session,mars_slot)
        self.assertEqual(r.status_code, 403)

        # logged in as non-owner
        # faulty delete
        self.client.login(username="ad", password="ad+password")
        r = do_unschedule(mars_scheduled)
        self.assertEqual(r.status_code, 403)
        self.assertTrue("error" in r.json())
        # faulty post
        r = do_schedule(schedule,ames_session,mars_slot)
        self.assertEqual(r.status_code, 403)

        # Put ames in the same timeslot as mars
        self.client.login(username="plain", password='plain+password')
        r = do_unschedule(ames_scheduled)
        self.assertEqual(r.status_code, 200)
        self.assertNotIn("error", r.json())

        r = do_schedule(schedule,ames_session,mars_slot)
        self.assertEqual(r.status_code, 201)

        # Move the two timeslots close enough together for extension to work
        ames_slot_qs=TimeSlot.objects.filter(id=ames_slot.id)
        ames_slot_qs.update(time=mars_slot.time+mars_slot.duration+datetime.timedelta(minutes=10))
        
        # Extend the mars session
        r = do_extend(schedule,mars_scheduled)
        self.assertEqual(r.status_code, 201)
        self.assertTrue("error" not in r.json())
        self.assertEqual(mars_session.timeslotassignments.filter(schedule__name='test-schedule').count(),2)

        # Unschedule mars 
        r = do_unschedule(mars_scheduled)
        self.assertEqual(r.status_code, 200)
        self.assertNotIn("error", r.json())
        # Make sure it got both the original and extended session
        self.assertEqual(mars_session.timeslotassignments.filter(schedule__name='test-schedule').count(),0)

        self.assertEqual(SchedTimeSessAssignment.objects.get(session=ames_session,schedule__name='test-schedule').timeslot, mars_slot)


    def test_constraints_json(self):
        meeting = make_meeting_test_data()
        session = Session.objects.filter(meeting=meeting, group__acronym="mars").select_related("group").first()
        c_ames = Constraint.objects.create(meeting=meeting, source=session.group,
                                           target=Group.objects.get(acronym="ames"),
                                           name_id="conflict")

        c_person = Constraint.objects.create(meeting=meeting, source=session.group,
                                             person=Person.objects.get(user__username="ad"),
                                             name_id="bethere")

        c_adjacent = Constraint.objects.create(meeting=meeting, source=session.group,
                                              target=Group.objects.get(acronym="irg"),
                                              name_id="wg_adjacent")

        c_time_relation = Constraint.objects.create(meeting=meeting, source=session.group,
                                                    time_relation='subsequent-days',
                                                    name_id="time_relation")

        c_timerange = Constraint.objects.create(meeting=meeting, source=session.group,
                                                name_id="timerange")
        c_timerange.timeranges.set(TimerangeName.objects.filter(slug__startswith='monday'))

        r = self.client.get(urlreverse("ietf.meeting.ajax.session_constraints", kwargs=dict(num=meeting.number, sessionid=session.pk)))
        self.assertEqual(r.status_code, 200)
        constraints = r.json()
        expected_keys = set([c_ames.pk, c_person.pk, c_adjacent.pk, c_time_relation.pk, c_timerange.pk])
        self.assertEqual(expected_keys, set(c["constraint_id"] for c in constraints))

    def test_meeting_json(self):
        meeting = make_meeting_test_data()

        r = self.client.get(urlreverse("ietf.meeting.ajax.meeting_json", kwargs=dict(num=meeting.number)))
        self.assertEqual(r.status_code, 200)
        info = r.json()
        self.assertEqual(info["name"], meeting.number)

    def test_get_room_json(self):
        meeting = make_meeting_test_data()
        room = meeting.room_set.first()

        r = self.client.get(urlreverse("ietf.meeting.ajax.timeslot_roomurl", kwargs=dict(num=meeting.number, roomid=room.pk)))
        self.assertEqual(r.status_code, 200)
        info = r.json()
        self.assertEqual(info["name"], room.name)

    def test_create_new_room(self):
        meeting = make_meeting_test_data()
        timeslots_before = meeting.timeslot_set.filter(type='regular').count()
        url = urlreverse("ietf.meeting.ajax.timeslot_roomsurl", kwargs=dict(num=meeting.number))

        post_data = { "name": "new room", "capacity": "50" , "resources": [], "session_types":['regular']}

        # unauthorized post
        r = self.client.post(url, post_data)
        self.assertEqual(r.status_code, 302)
        self.assertTrue(not meeting.room_set.filter(name="new room"))

        # create room
        self.client.login(username="secretary", password="secretary+password")
        r = self.client.post(url, post_data)
        self.assertEqual(r.status_code, 302)
        self.assertTrue(meeting.room_set.filter(name="new room"))

        timeslots_after = meeting.timeslot_set.filter(type='regular').count()
        # It's not clear that what that ajax function is doing is the right thing to do,
        # but it currently makes a new timeslot for any existing timeslot.
        # The condition tested below relies on the timeslots before this test all having different start and end times
        self.assertEqual( timeslots_after, 2 * timeslots_before)

    def test_delete_room(self):
        meeting = make_meeting_test_data()
        room = meeting.room_set.first()
        timeslots_before = list(room.timeslot_set.values_list("pk", flat=True))

        url = urlreverse("ietf.meeting.ajax.timeslot_roomurl", kwargs=dict(num=meeting.number, roomid=room.pk))

        # unauthorized delete
        r = self.client.delete(url)
        self.assertEqual(r.status_code, 302)
        self.assertTrue(meeting.room_set.filter(pk=room.pk))

        # delete
        self.client.login(username="secretary", password="secretary+password")
        r = self.client.delete(url)
        self.assertTrue(not meeting.room_set.filter(pk=room.pk))
        self.assertTrue(not TimeSlot.objects.filter(pk__in=timeslots_before))

    # This really belongs in group tests
    def test_group_json(self):
        make_meeting_test_data()
        group = Group.objects.get(acronym="mars")

        url = urlreverse("ietf.group.views.group_json", kwargs=dict(acronym=group.acronym))
        r = self.client.get(url)
        self.assertEqual(r.status_code, 200)
        info = r.json()
        self.assertEqual(info["name"], group.name)

    # This really belongs in person tests
    def test_person_json(self):
        make_meeting_test_data()
        person = Person.objects.get(user__username="ad")

        url = urlreverse("ietf.person.ajax.person_json", kwargs=dict(personid=person.pk))
        r = self.client.get(url)
        self.assertEqual(r.status_code, 200)
        info = r.json()
        self.assertEqual(info["name"], person.name)

    def test_sessions_json(self):
        meeting = make_meeting_test_data()
 
        url = urlreverse("ietf.meeting.ajax.sessions_json",kwargs=dict(num=meeting.number))
        r = self.client.get(url)
        self.assertEqual(r.status_code, 200)
        info = r.json()
        self.assertEqual(set([x['short_name'] for x in info]),set([s.session.short_name for s in meeting.schedule.assignments.filter(session__type_id='regular')]))

        schedule = meeting.schedule
        url = urlreverse("ietf.meeting.ajax.assignments_json",
                         kwargs=dict(num=meeting.number,owner=schedule.owner_email(),name=schedule.name))
        r = self.client.get(url)
        self.assertEqual(r.status_code, 200)
        info = r.json()
        self.assertEqual(len(info),schedule.assignments.count())


    def test_slot_json(self):
        meeting = make_meeting_test_data()
        slot = meeting.timeslot_set.all()[0]

        url = urlreverse("ietf.meeting.ajax.timeslot_sloturl",
                         kwargs=dict(num=meeting.number, slotid=slot.pk))
        r = self.client.get(url)
        self.assertEqual(r.status_code, 200)
        info = r.json()
        self.assertEqual(info["timeslot_id"], slot.pk)

    def test_create_new_slot(self):
        meeting = make_meeting_test_data()

        slot_time = datetime.date.today()

        url = urlreverse("ietf.meeting.ajax.timeslot_slotsurl",
                         kwargs=dict(num=meeting.number))
        post_data = {
            'type' : 'plenary',
            'time' : slot_time.strftime("%Y-%m-%d"),
            'duration': '08:00:00',
        }

        # unauthorized post
        prior_slotcount = meeting.timeslot_set.count()
        self.client.login(username="ad", password="ad+password")
        r = self.client.post(url, post_data)
        self.assertEqual(r.status_code, 403)
        self.assertEqual(meeting.timeslot_set.count(),prior_slotcount)

        # create slot
        self.client.login(username="secretary", password="secretary+password")
        r = self.client.post(url, post_data)
        self.assertEqual(r.status_code, 201)
        self.assertTrue(meeting.timeslot_set.filter(time=slot_time))
        self.assertEqual(meeting.timeslot_set.count(),prior_slotcount+1)

    def test_delete_slot(self):
        meeting = make_meeting_test_data()
        slot = meeting.timeslot_set.all()[0]

        url = urlreverse("ietf.meeting.ajax.timeslot_sloturl",
                         kwargs=dict(num=meeting.number, slotid=slot.pk))

        # unauthorized delete
        self.client.login(username="ad", password="ad+password")
        r = self.client.delete(url)
        self.assertEqual(r.status_code, 403)

        # delete
        self.client.login(username="secretary", password="secretary+password")
        self.client.delete(url)
        self.assertTrue(not meeting.timeslot_set.filter(pk=slot.pk))

    def test_schedule_json(self):
        meeting = make_meeting_test_data()

        url = urlreverse("ietf.meeting.ajax.schedule_infourl",
                         kwargs=dict(num=meeting.number,
                                     owner=meeting.schedule.owner_email(),
                                     name=meeting.schedule.name))

        r = self.client.get(url)
        info = r.json()
        self.assertEqual(info["schedule_id"], meeting.schedule.pk)

    def test_create_new_schedule(self):
        meeting = make_meeting_test_data()

        url = urlreverse("ietf.meeting.ajax.schedule_infosurl",
                         kwargs=dict(num=meeting.number))
        post_data = {
            'name': 'new-schedule',
        }

        # unauthorized post
        self.client.login(username="plain", password="plain+password")
        r = self.client.post(url, post_data)
        self.assertEqual(r.status_code, 403)
        self.assertTrue(not meeting.schedule_set.filter(name='new-schedule'))

        # create new schedule
        self.client.login(username="secretary", password="secretary+password")
        r = self.client.post(url, post_data)
        self.assertEqual(r.status_code, 302)
        self.assertTrue(meeting.schedule_set.filter(name='new-schedule'))

    def test_update_meeting_schedule(self):
        meeting = make_meeting_test_data()

        self.assertTrue(meeting.schedule.visible)

        url = urlreverse("ietf.meeting.ajax.schedule_infourl",
                         kwargs=dict(num=meeting.number,
                                     owner=meeting.schedule.owner_email(),
                                     name=meeting.schedule.name))

        post_data = {
            'visible': 'false',
            'name': 'new-test-name',
        }

        # unauthorized posts
        self.client.logout()
        r = self.client.post(url, post_data)
        self.assertEqual(r.status_code, 403)
        self.client.login(username="ad", password="ad+password")
        r = self.client.post(url, post_data)
        self.assertEqual(r.status_code, 403)

        # change schedule
        self.client.login(username="secretary", password="secretary+password")
        r = self.client.post(url, post_data)
        self.assertEqual(r.status_code, 302)
        changed_schedule = Schedule.objects.get(pk=meeting.schedule.pk)
        self.assertTrue(not changed_schedule.visible)
        self.assertEqual(changed_schedule.name, "new-test-name")

    def test_delete_schedule(self):
        meeting = make_meeting_test_data()

        url = urlreverse("ietf.meeting.ajax.schedule_infourl",
                         kwargs=dict(num=meeting.number,
                                     owner=meeting.schedule.owner_email(),
                                     name=meeting.schedule.name))
        # unauthorized delete
        self.client.login(username="plain", password="plain+password")
        r = self.client.delete(url)
        self.assertEqual(r.status_code, 403)

        # delete
        self.client.login(username="secretary", password="secretary+password")
        r = self.client.delete(url)
        self.assertEqual(r.status_code, 200)
        self.assertTrue(not Schedule.objects.filter(pk=meeting.schedule.pk))

    def test_set_meeting_schedule(self):
        meeting = make_meeting_test_data()
        schedule = meeting.schedule

        url = urlreverse("ietf.meeting.ajax.meeting_json",
                         kwargs=dict(num=meeting.number))
        post_data = {
            "schedule": "",
            }
        # unauthorized post
        self.client.login(username="ad", password="ad+password")
        r = self.client.post(url, post_data)
        self.assertEqual(r.status_code, 403)

        # clear
        self.client.login(username="secretary", password="secretary+password")
        r = self.client.post(url, post_data)
        self.assertEqual(r.status_code, 200)
        self.assertTrue(not Meeting.objects.get(pk=meeting.pk).schedule)

        # set schedule - first fail with non-public
        post_data = {
            "schedule": schedule.name,
            }
        schedule.public = False
        schedule.save()

        r = self.client.post(url, post_data)
        self.assertTrue(r.status_code != 200)
        self.assertTrue(not Meeting.objects.get(pk=meeting.pk).schedule)

        # then go through with public
        schedule.public = True
        schedule.save()

        # Setting a meeting as official no longer sends mail immediately
        prior_length= len(outbox)
        r = self.client.post(url, post_data)
        self.assertEqual(r.status_code, 200)
        self.assertEqual(Meeting.objects.get(pk=meeting.pk).schedule, schedule)
        self.assertEqual(len(outbox),prior_length)

    def test_read_only(self):
        meeting = make_meeting_test_data()

        # Secretariat
        self.client.login(username="secretary", password="secretary+password")
        url = '/meeting/%s/agenda/%s/%s/permissions' % (meeting.number, meeting.schedule.owner.email_address(), meeting.schedule.name);
        r = self.client.get(url)
        self.assertEqual(r.status_code, 200)

        info = r.json()
        self.assertEqual(info['secretariat'], True)
        self.assertEqual(urlsplit(info['owner_href'])[2], "/person/%s.json" % meeting.schedule.owner_id)
        self.assertEqual(info['read_only'], True)
        self.assertEqual(info['save_perm'], True)

        # owner
        self.client.login(username=meeting.schedule.owner.user.username,
                          password=meeting.schedule.owner.user.username+"+password")
        url = '/meeting/%s/agenda/%s/%s/permissions' % (meeting.number, meeting.schedule.owner.email_address(), meeting.schedule.name);
        r = self.client.get(url)
        self.assertEqual(r.status_code, 200)

        info = r.json()
        self.assertEqual(info['secretariat'], False)
        self.assertEqual(info['read_only'], False)
        self.assertEqual(info['save_perm'], False)

    def test_update_timeslot_pinned(self):
        meeting = make_meeting_test_data()
        scheduled = SchedTimeSessAssignment.objects.filter(
            session__meeting=meeting, session__group__acronym="mars").first()

        url = '/meeting/%s/agenda/%s/%s/session/%u.json' % (meeting.number, meeting.schedule.owner_email(), meeting.schedule.name, scheduled.pk)

        post_data = {
            "pinned": True
            }

        # unauthorized post gets failure (no redirect)
        r = self.client.put(url, post_data)
        self.assertEqual(r.status_code, 403,
                         "post to %s should have failed, no permission, got: %u/%s" %
                         (url, r.status_code, r.content))
        self.assertTrue(not SchedTimeSessAssignment.objects.get(pk=scheduled.pk).pinned)

        # set pinned
        meeting.schedule.owner = Person.objects.get(user__username="secretary")
        meeting.schedule.save()

        # need to rebuild URL, since the schedule owner has changed.
        url = '/meeting/%s/agenda/%s/%s/session/%u.json' % (meeting.number, meeting.schedule.owner_email(), meeting.schedule.name, scheduled.pk)

        self.client.login(username="secretary", password="secretary+password")
        r = self.client.put(url, post_data)
        self.assertEqual(r.status_code, 200,
                         "post to %s should have worked, but got: %u/%s" %
                         (url, r.status_code, r.content))
        self.assertTrue(SchedTimeSessAssignment.objects.get(pk=scheduled.pk).pinned)

class TimeSlotEditingApiTests(TestCase):

    def test_manipulate_timeslot(self):
        meeting = make_meeting_test_data()
        slot = meeting.timeslot_set.all()[0]
        self.assertEqual(TimeSlot.objects.get(pk=slot.pk).type_id,'regular')

        url = urlreverse("ietf.meeting.ajax.timeslot_sloturl",
                         kwargs=dict(num=meeting.number, slotid=slot.pk))

        modify_post_data = {
            "purpose"       : "plenary"
        }

        # Fail as non-secretariat
        self.client.login(username="plain", password="plain+password")
        r = self.client.post(url, modify_post_data)
        self.assertEqual(r.status_code, 403)
        slot.refresh_from_db()
        self.assertEqual(slot.type_id, 'regular')

        # Successful change of purpose
        self.client.login(username="secretary", password="secretary+password")
        r = self.client.post(url, modify_post_data)
        self.assertEqual(r.status_code, 200)
        slot.refresh_from_db()
        self.assertEqual(slot.type_id, 'plenary')