# -*- 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 builtins import map
from builtins import range
from builtins import object
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 list(map(self.expected_row_single_level, student_rows)) + [""]

    def expected_rows_multiple_levels(self, student_rows):
        return list(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 = list(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(object):
    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(object):
    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 = [x.id for x in 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)
