# -*- coding: utf-8 -*-
# Code for Life
#
# Copyright (C) 2019, Ocado Innovation Limited
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#
# ADDITIONAL TERMS – Section 7 GNU General Public Licence
#
# This licence does not grant any right, title or interest in any “Ocado” logos,
# trade names or the trademark “Ocado” or any other trademarks or domain names
# owned by Ocado Innovation Limited or the Ocado group of companies or any other
# distinctive brand features of “Ocado” as may be secured from time to time. You
# must not distribute any modification of this program using the trademark
# “Ocado” or claim any affiliation or association with Ocado or its employees.
#
# You are not authorised to use the name Ocado (or any of its trade names) or
# the names of any author or contributor in advertising or for publicity purposes
# pertaining to the distribution of this program, without the prior written
# authorisation of Ocado.
#
# Any propagation, distribution or conveyance of this program must include this
# copyright notice and these terms. You must not misrepresent the origins of this
# program; modified versions of the program must be marked as such and not
# identified as the original program.
from datetime import timedelta, datetime
from django.utils.timezone import utc

from django.core.urlresolvers import reverse
from django.test import TestCase, Client
from hamcrest import *

from game.models import Level, Attempt
from game.views.scoreboard import StudentRow, scoreboard_data
from game.views.scoreboard_csv import (
    scoreboard_csv_multiple_levels,
    scoreboard_csv_single_level,
)
from portal.models import Class
from portal.tests.utils.classes import create_class_directly
from portal.tests.utils.organisation import create_organisation_directly
from portal.tests.utils.student import create_school_student_directly
from portal.tests.utils.teacher import signup_teacher_directly

Headers = [
    "Class",
    "Name",
    "Total Score",
    "Total Time",
    "Started Levels %",
    "Attempted levels %",
    "Finished levels %",
]


class ScoreboardTestCase(TestCase):
    def test_teacher_multiple_students_multiple_levels(self):
        level_ids = ids_of_levels_named(["1", "2"])
        level1 = Level.objects.get(name="1")
        level2 = Level.objects.get(name="2")

        clas, student, student2 = set_up_data()

        create_attempt(student, level1, 10.5)
        create_attempt(student2, level1, 2.3)
        create_attempt(student2, level2, 16.7)

        student_data, headers = scoreboard_data(Teacher(), level_ids, [clas.id])

        assert_that(
            headers,
            equal_to(
                [
                    "Class",
                    "Name",
                    "Total Score",
                    "Total Time",
                    "Progress",
                    u"Level 1",
                    u"Level 2",
                ]
            ),
        )
        assert_that(student_data, has_length(2))

        assert_student_row(
            student_row=student_data[0],
            class_name=clas.name,
            student_name=student.user.user.first_name,
            total_score=10.5,
            total_time=timedelta(0),
            progress=(0.0, 0.0, 50.0),
            scores=[10.5, ""],
        )

        assert_student_row(
            student_row=student_data[1],
            class_name=clas.name,
            student_name=student2.user.user.first_name,
            total_score=19.0,
            total_time=timedelta(0),
            progress=(0.0, 50.0, 50.0),
            scores=[2.3, 16.7],
        )

    def test_teacher_multiple_students_single_level(self):
        level_ids = ids_of_levels_named(["1"])
        level1 = Level.objects.get(name="1")

        clas, student, student2 = set_up_data()

        create_attempt(student, level1, 10.5)
        create_attempt(student2, level1, 2.3)

        student_data, headers = scoreboard_data(Teacher(), level_ids, [clas.id])

        assert_that(
            headers,
            equal_to(
                ["Class", "Name", "Score", "Total Time", "Start Time", "Finish Time"]
            ),
        )
        assert_that(student_data, has_length(2))

        assert_student_row_single_level(
            student_row=student_data[0],
            class_name=clas.name,
            student_name=student.user.user.first_name,
            total_score=10.5,
            total_time=timedelta(0),
        )

        assert_student_row_single_level(
            student_row=student_data[1],
            class_name=clas.name,
            student_name=student2.user.user.first_name,
            total_score=2.3,
            total_time=timedelta(0),
        )

    def test_student_multiple_students_multiple_levels(self):
        level_ids = ids_of_levels_named(["1", "2"])
        level1 = Level.objects.get(name="1")
        level2 = Level.objects.get(name="2")

        clas, student, student2 = set_up_data(True)

        create_attempt(student, level1, 10.5)
        create_attempt(student2, level1, 2.3)
        create_attempt(student2, level2, 16.7)

        student_data, headers = scoreboard_data(Student(student), level_ids, [clas.id])

        assert_that(
            headers,
            equal_to(
                [
                    "Class",
                    "Name",
                    "Total Score",
                    "Total Time",
                    "Progress",
                    u"Level 1",
                    u"Level 2",
                ]
            ),
        )
        assert_that(student_data, has_length(2))

        assert_student_row(
            student_row=student_data[0],
            class_name=clas.name,
            student_name=student.user.user.first_name,
            total_score=10.5,
            total_time=timedelta(0),
            progress=(0.0, 0.0, 50.0),
            scores=[10.5, ""],
        )

        assert_student_row(
            student_row=student_data[1],
            class_name=clas.name,
            student_name=student2.user.user.first_name,
            total_score=19.0,
            total_time=timedelta(0),
            progress=(0.0, 50.0, 50.0),
            scores=[2.3, 16.7],
        )

    def test_student_multiple_students_single_level(self):
        level_ids = ids_of_levels_named(["2"])
        level2 = Level.objects.get(name="2")

        clas, student, student2 = set_up_data(True)

        create_attempt(student, level2, 10.5)
        create_attempt(student2, level2, 16.7)

        student_data, headers = scoreboard_data(Student(student), level_ids, [clas.id])

        assert_that(
            headers,
            equal_to(
                ["Class", "Name", "Score", "Total Time", "Start Time", "Finish Time"]
            ),
        )
        assert_that(student_data, has_length(2))

        assert_student_row_single_level(
            student_row=student_data[0],
            class_name=clas.name,
            student_name=student.user.user.first_name,
            total_score=10.5,
            total_time=timedelta(0),
        )

        assert_student_row_single_level(
            student_row=student_data[1],
            class_name=clas.name,
            student_name=student2.user.user.first_name,
            total_score=16.7,
            total_time=timedelta(0),
        )

    def test_student_multiple_students_multiple_levels_cannot_see_classmates(self):
        level_ids = ids_of_levels_named(["1", "2"])
        level1 = Level.objects.get(name="1")
        level2 = Level.objects.get(name="2")

        clas, student, student2 = set_up_data()
        create_attempt(student, level1, 10.5)
        create_attempt(student2, level1, 2.3)
        create_attempt(student2, level2, 16.7)

        student_data, headers = scoreboard_data(Student(student), level_ids, [clas.id])

        assert_that(
            headers,
            equal_to(
                [
                    "Class",
                    "Name",
                    "Total Score",
                    "Total Time",
                    "Progress",
                    u"Level 1",
                    u"Level 2",
                ]
            ),
        )
        assert_that(student_data, has_length(1))

        assert_student_row(
            student_row=student_data[0],
            class_name=clas.name,
            student_name=student.user.user.first_name,
            total_score=10.5,
            total_time=timedelta(0),
            progress=(0.0, 0.0, 50.0),
            scores=[10.5, ""],
        )

    def test_scoreboard_loads(self):
        email, password = signup_teacher_directly()
        create_organisation_directly(email)
        klass, name, access_code = create_class_directly(email)
        create_school_student_directly(access_code)
        url = reverse("scoreboard")
        c = Client()
        c.login(username=email, password=password)
        response = c.get(url)
        self.assertEqual(200, response.status_code)


class ScoreboardCsvTestCase(TestCase):
    def test_multiple_levels(self):
        levels = Level.objects.sorted_levels()
        student_rows = [(self.student_row("secrète")), (self.student_row())]

        response = scoreboard_csv_multiple_levels(student_rows, levels)

        actual_header, actual_rows = self.actual_data(response.content)

        expected_header = self.expected_header(levels)
        expected_rows = self.expected_rows_multiple_levels(student_rows)

        assert_that(actual_header, equal_to(expected_header))

        assert_that(actual_rows, equal_to(expected_rows))

    def test_single_level(self):
        student_rows = [(self.student_row("secrète")), (self.student_row())]
        response = scoreboard_csv_single_level(student_rows)

        actual_header, actual_rows = self.actual_data(response.content)

        expected_header = "Class,Name,Score,Total Time,Start Time,Finish Time"
        expected_rows = self.expected_rows_single_level(student_rows)

        assert_that(actual_header, equal_to(expected_header))

        assert_that(actual_rows, equal_to(expected_rows))

    def expected_rows_single_level(self, student_rows):
        return map(self.expected_row_single_level, student_rows) + [""]

    def expected_rows_multiple_levels(self, student_rows):
        return map(self.expected_row_multiple_levels, student_rows) + [""]

    def student_row(self, class_name=None):
        email, password = signup_teacher_directly()
        _, class_name, access_code = create_class_directly(email, class_name)
        _, _, student = create_school_student_directly(access_code)

        total_time = timedelta(0, 30)
        scores = [x for x in range(20)]
        total_score = sum(scores)
        progress = (0, 0, 0)

        all_scores = scores + [""] * 89

        row = StudentRow(
            student=student,
            total_time=total_time,
            total_score=total_score,
            start_time=datetime.fromtimestamp(1435305072, tz=utc),
            finish_time=datetime.fromtimestamp(1438305072, tz=utc),
            progress=progress,
            scores=all_scores,
            class_field=Class(name="MyClass"),
        )
        return row

    def expected_row_multiple_levels(self, student_row):
        beginning = (
            "%s,%s,190,0:00:30,0,0,0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,"
            % (student_row.class_field.name.encode("utf-8"), student_row.name)
        )
        padding = ",".join([""] * 89)
        return beginning + padding

    def expected_row_single_level(self, student_row):
        return (
            "%s,%s,190,0:00:30,2015-06-26 07:51:12+00:00,2015-07-31 01:11:12+00:00"
            % (student_row.class_field.name.encode("utf-8"), student_row.name)
        )

    def expected_header(self, levels):
        level_strings = map(str, levels)
        all_header_strings = Headers + level_strings
        joined = ",".join(all_header_strings)
        return joined

    def actual_data(self, content):
        split = content.split("\r\n")
        header = split[0]
        rows = split[1:]
        return header, rows


class Student:
    def __init__(self, student):
        self.student = student

    def is_student(self):
        return True

    def is_teacher(self):
        return False

    def is_independent_student(self):
        return False


class Teacher:
    def is_student(self):
        return False

    def is_teacher(self):
        return True

    def is_independent_student(self):
        return False


def assert_student_row(
    student_row, class_name, student_name, total_score, total_time, progress, scores
):
    assert_that(student_row.class_field.name, equal_to(class_name))
    assert_that(student_row.name, equal_to(student_name))
    assert_that(student_row.total_score, equal_to(total_score))
    assert_that(student_row.total_time, equal_to(total_time))
    assert_that(student_row.progress, equal_to(progress))
    assert_that(student_row.scores, equal_to(scores))


def assert_student_row_single_level(
    student_row, class_name, student_name, total_score, total_time
):
    assert_that(student_row.class_field.name, equal_to(class_name))
    assert_that(student_row.name, equal_to(student_name))
    assert_that(student_row.total_score, equal_to(total_score))
    assert_that(student_row.total_time, equal_to(total_time))


def create_attempt(student, level, score):
    attempt = Attempt.objects.create(
        finish_time=datetime.fromtimestamp(1435305072),
        level=level,
        student=student,
        score=score,
        is_best_attempt=True,
    )
    attempt.start_time = datetime.fromtimestamp(1435305072)
    attempt.save()


def ids_of_levels_named(names):
    levels = Level.objects.filter(name__in=names)
    assert_that(len(levels), equal_to(len(names)))
    level_ids = map(lambda x: x.id, levels)
    return level_ids


def set_up_data(classmates_data_viewable=False):
    email, password = signup_teacher_directly()
    clas, class_name, access_code = create_class_directly(email)
    if classmates_data_viewable:
        clas.classmates_data_viewable = True
        clas.save()
    _, _, student = create_school_student_directly(access_code)
    _, _, student2 = create_school_student_directly(access_code)
    create_random_school_data()
    return clas, student, student2


def create_random_school_data():
    email, password = signup_teacher_directly()
    clas, class_name, access_code = create_class_directly(email)
    create_school_student_directly(access_code)
    create_school_student_directly(access_code)
