from django.test import TestCase
from os import path
from rest_framework import status
from rest_framework.test import APIClient
import random

from scheduler.models import Profile
from scheduler.factories import (
    CourseFactory,
    SpacetimeFactory,
    UserFactory,
    ProfileFactory,
    SectionFactory,
    AttendanceFactory,
    OverrideFactory,
    create_attendances_for,
)

random.seed(0)

COURSE_NAMES = ("CS88", "CS61A", "CS61B", "CS70", "CS61C", "EE16A")
ROLE_MAP = Profile.ROLE_MAP
BASE_PATH = "/scheduler"

# ----- REQUEST UTILITIES -----
def fail_msg(ep, resp):
    return "Endpoint: {}\nResponse Content: {}".format(ep, resp.content)


class APITestCase(TestCase):
    def get_client_for(self, user):
        """Returns an APIClient object that is logged in as the provided user."""
        client = APIClient()
        client.force_authenticate(user)
        return client

    def request(self, method, endpoint, exp_code=None, data=None):
        """
        Performs a request to the specified endpoint and returns the response object.
        Also checks if the status code of the response is exp_code, if provided.
        The method parameter should be a get/post/etc from an APIClient object.
        """
        resp = method(path.join(BASE_PATH, endpoint.strip("/")), follow=True, data=data)
        if exp_code is not None:
            self.assertEqual(resp.status_code, exp_code, msg=fail_msg(endpoint, resp))
        return resp

    def req_fails_perms(self, method, endpoint, data=None):
        """
        Performs a request to the specified endpoint, and checks that it fails
        due to the user lacking proper permissions.
        The method parameter should be a get/post/etc from an APIClient object.
        Returns the response object afterwards.
        """
        return self.request(
            method, endpoint, exp_code=status.HTTP_403_FORBIDDEN, data=data
        )

    def req_fails_method(self, method, endpoint, data=None):
        """
        Performs a request to the specified endpoint, and checks that it fails
        due to the endpoint not supporting the provided method.
        Returns the response object.
        """
        return self.request(
            method, endpoint, exp_code=status.HTTP_405_METHOD_NOT_ALLOWED, data=data
        )

    def req_succeeds(self, method, endpoint, data=None):
        """
        Performs a request to the specified endpoint, and checks that it succeeds.
        The method parameter should be a get/post/etc from an APIClient object.
        Returns the response object.
        """
        return self.request(method, endpoint, exp_code=status.HTTP_200_OK, data=data)


# ----- MODEL GENERATION -----


def random_objs(clazz, n=1):
    """
    Generates N instances of the provided class, retrieved from the database.
    """
    src = clazz.objects.all()
    for _ in range(n):
        yield random.choice(src)


def make_test_courses():
    """Creates course objects and persists them to database."""
    return [CourseFactory.create(name=name) for name in COURSE_NAMES]


def make_test_users(n):
    """Creates N test users and persists them to database."""
    return UserFactory.create_batch(n)


def give_role(user, role, course):
    """
	Creates a profile for USER in a given ROLE for the provided COURSE, and
	saves the profile to database.
	"""
    return ProfileFactory.create(
        user=user, course=course, leader=None, section=None, role=role
    )


def create_empty_section_for(mentor):
    """
	Creates a section for MENTOR without populated students.
	"""
    return SectionFactory.create(course=mentor.course, mentor=mentor)


def enroll_user_as_student(user, section):
    """
	Creates a student profile for USER, and assigns them to the given SECTION.
	Also creates blank attendances as necessary.
	Returns the created profile.
	"""
    student = give_role(user, Profile.STUDENT, section.course)
    student.section = section
    student.leader = section.leader
    create_attendances_for(student)
    return student


def gen_test_data(cls, NUM_USERS=300):
    """
	Adds NUM_USERS users to the database and initializes profiles for them as follows:
	- 2 coords per course
	- 4 SMs per coord, each with a section of 3-6 students
	- 3 JMs per SM, each with a section of 3-6 students
	"""
    users = iter(make_test_users(NUM_USERS))
    courses = make_test_courses()
    # for sanity tests, everyone only has one role for now
    num_courses = len(courses)
    coords, seniors, juniors, students = [], [], [], []
    COORD_COUNT = 2
    SM_COUNT = 4
    JM_COUNT = 3

    def assign(role, leader, c, lst):
        # returns the profile created
        profile = give_role(next(users), role, c)
        profile.leader = leader
        lst.append(profile)
        return profile

    try:
        for c in courses:
            # coords
            for i in range(COORD_COUNT):
                coord = assign(Profile.COORDINATOR, None, c, coords)
                # SMs
                for j in range(SM_COUNT):
                    sm = assign(Profile.SENIOR_MENTOR, coord, c, seniors)
                    section = create_empty_section_for(sm)
                    for k in range(random.randint(3, 6)):
                        students.append(enroll_user_as_student(next(users), section))
                    # JMs
                    for k in range(JM_COUNT):
                        jm = assign(Profile.JUNIOR_MENTOR, sm, c, juniors)
                        for _ in range(random.randint(3, 6)):
                            students.append(
                                enroll_user_as_student(next(users), section)
                            )
    except StopIteration:
        pass
    cls.users = users
    cls.courses = courses
    cls.coords = coords
    cls.seniors = seniors
    cls.juniors = juniors
    cls.students = students

[(19, 'random.seed', 'random.seed', False, 'import random\n'), (97, 'scheduler.factories.UserFactory.create_batch', 'UserFactory.create_batch', False, 'from scheduler.factories import CourseFactory, SpacetimeFactory, UserFactory, ProfileFactory, SectionFactory, AttendanceFactory, OverrideFactory, create_attendances_for\n'), (105, 'scheduler.factories.ProfileFactory.create', 'ProfileFactory.create', False, 'from scheduler.factories import CourseFactory, SpacetimeFactory, UserFactory, ProfileFactory, SectionFactory, AttendanceFactory, OverrideFactory, create_attendances_for\n'), (114, 'scheduler.factories.SectionFactory.create', 'SectionFactory.create', False, 'from scheduler.factories import CourseFactory, SpacetimeFactory, UserFactory, ProfileFactory, SectionFactory, AttendanceFactory, OverrideFactory, create_attendances_for\n'), (126, 'scheduler.factories.create_attendances_for', 'create_attendances_for', False, 'from scheduler.factories import CourseFactory, SpacetimeFactory, UserFactory, ProfileFactory, SectionFactory, AttendanceFactory, OverrideFactory, create_attendances_for\n'), (33, 'rest_framework.test.APIClient', 'APIClient', False, 'from rest_framework.test import APIClient\n'), (92, 'scheduler.factories.CourseFactory.create', 'CourseFactory.create', False, 'from scheduler.factories import CourseFactory, SpacetimeFactory, UserFactory, ProfileFactory, SectionFactory, AttendanceFactory, OverrideFactory, create_attendances_for\n'), (87, 'random.choice', 'random.choice', False, 'import random\n'), (162, 'random.randint', 'random.randint', False, 'import random\n'), (167, 'random.randint', 'random.randint', False, 'import random\n')]