import json
from unittest import TestCase
from .utils import CourseTestCase
from django.core.urlresolvers import reverse
from contentstore.utils import get_modulestore
from xmodule.modulestore.inheritance import own_metadata

from contentstore.views.course import (
    validate_textbooks_json, validate_textbook_json, TextbookValidationError)


class TextbookIndexTestCase(CourseTestCase):
    "Test cases for the textbook index page"
    def setUp(self):
        "Set the URL for tests"
        super(TextbookIndexTestCase, self).setUp()
        self.url = self.course_locator.url_reverse('textbooks')

    def test_view_index(self):
        "Basic check that the textbook index page responds correctly"
        resp = self.client.get(self.url)
        self.assertEqual(resp.status_code, 200)
        # we don't have resp.context right now,
        # due to bugs in our testing harness :(
        if resp.context:
            self.assertEqual(resp.context['course'], self.course)

    def test_view_index_xhr(self):
        "Check that we get a JSON response when requested via AJAX"
        resp = self.client.get(
            self.url,
            HTTP_ACCEPT="application/json",
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )
        self.assertEqual(resp.status_code, 200)
        obj = json.loads(resp.content)
        self.assertEqual(self.course.pdf_textbooks, obj)

    def test_view_index_xhr_content(self):
        "Check that the response maps to the content of the modulestore"
        content = [
            {
                "tab_title": "my textbook",
                "url": "/abc.pdf",
                "id": "992"
            }, {
                "tab_title": "pineapple",
                "id": "0pineapple",
                "chapters": [
                    {
                        "title": "The Fruit",
                        "url": "/a/b/fruit.pdf",
                    }, {
                        "title": "The Legend",
                        "url": "/b/c/legend.pdf",
                    }
                ]
            }
        ]
        self.course.pdf_textbooks = content
        # Save the data that we've just changed to the underlying
        # MongoKeyValueStore before we update the mongo datastore.
        self.course.save()
        store = get_modulestore(self.course.location)
        store.update_metadata(self.course.location, own_metadata(self.course))

        resp = self.client.get(
            self.url,
            HTTP_ACCEPT="application/json",
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )
        self.assertEqual(resp.status_code, 200)
        obj = json.loads(resp.content)
        self.assertEqual(content, obj)

    def test_view_index_xhr_put(self):
        "Check that you can save information to the server"
        textbooks = [
            {"tab_title": "Hi, mom!"},
            {"tab_title": "Textbook 2"},
        ]
        resp = self.client.put(
            self.url,
            data=json.dumps(textbooks),
            content_type="application/json",
            HTTP_ACCEPT="application/json",
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )
        self.assertEqual(resp.status_code, 200)

        # reload course
        store = get_modulestore(self.course.location)
        course = store.get_item(self.course.location)
        # should be the same, except for added ID
        no_ids = []
        for textbook in course.pdf_textbooks:
            del textbook["id"]
            no_ids.append(textbook)
        self.assertEqual(no_ids, textbooks)

    def test_view_index_xhr_put_invalid(self):
        "Check that you can't save invalid JSON"
        resp = self.client.put(
            self.url,
            data="invalid",
            content_type="application/json",
            HTTP_ACCEPT="application/json",
            HTTP_X_REQUESTED_WITH='XMLHttpRequest'
        )
        self.assertEqual(resp.status_code, 400)
        obj = json.loads(resp.content)
        self.assertIn("error", obj)


class TextbookCreateTestCase(CourseTestCase):
    "Test cases for creating a new PDF textbook"

    def setUp(self):
        "Set up a url and some textbook content for tests"
        super(TextbookCreateTestCase, self).setUp()
        self.url = self.course_locator.url_reverse('textbooks')
        self.textbook = {
            "tab_title": "Economics",
            "chapters": {
                "title": "Chapter 1",
                "url": "/a/b/c/ch1.pdf",
            }
        }

    def test_happy_path(self):
        "Test that you can create a textbook"
        resp = self.client.post(
            self.url,
            data=json.dumps(self.textbook),
            content_type="application/json",
            HTTP_ACCEPT="application/json",
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(resp.status_code, 201)
        self.assertIn("Location", resp)
        textbook = json.loads(resp.content)
        self.assertIn("id", textbook)
        del textbook["id"]
        self.assertEqual(self.textbook, textbook)

    def test_valid_id(self):
        "Textbook IDs must begin with a number; try a valid one"
        self.textbook["id"] = "7x5"
        resp = self.client.post(
            self.url,
            data=json.dumps(self.textbook),
            content_type="application/json",
            HTTP_ACCEPT="application/json",
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(resp.status_code, 201)
        textbook = json.loads(resp.content)
        self.assertEqual(self.textbook, textbook)

    def test_invalid_id(self):
        "Textbook IDs must begin with a number; try an invalid one"
        self.textbook["id"] = "xxx"
        resp = self.client.post(
            self.url,
            data=json.dumps(self.textbook),
            content_type="application/json",
            HTTP_ACCEPT="application/json",
            HTTP_X_REQUESTED_WITH="XMLHttpRequest",
        )
        self.assertEqual(resp.status_code, 400)
        self.assertNotIn("Location", resp)


class TextbookDetailTestCase(CourseTestCase):
    "Test cases for the `textbook_detail_handler` view"

    def setUp(self):
        "Set some useful content and URLs for tests"
        super(TextbookDetailTestCase, self).setUp()
        self.textbook1 = {
            "tab_title": "Economics",
            "id": 1,
            "chapters": {
                "title": "Chapter 1",
                "url": "/a/b/c/ch1.pdf",
            }
        }
        self.url1 = self.course_locator.url_reverse("textbooks", "1")
        self.textbook2 = {
            "tab_title": "Algebra",
            "id": 2,
            "chapters": {
                "title": "Chapter 11",
                "url": "/a/b/ch11.pdf",
            }
        }
        self.url2 = self.course_locator.url_reverse("textbooks", "2")
        self.course.pdf_textbooks = [self.textbook1, self.textbook2]
        # Save the data that we've just changed to the underlying
        # MongoKeyValueStore before we update the mongo datastore.
        self.course.save()
        self.store = get_modulestore(self.course.location)
        self.store.update_metadata(self.course.location, own_metadata(self.course))
        self.url_nonexist = self.course_locator.url_reverse("textbooks", "20")

    def test_get_1(self):
        "Get the first textbook"
        resp = self.client.get(self.url1)
        self.assertEqual(resp.status_code, 200)
        compare = json.loads(resp.content)
        self.assertEqual(compare, self.textbook1)

    def test_get_2(self):
        "Get the second textbook"
        resp = self.client.get(self.url2)
        self.assertEqual(resp.status_code, 200)
        compare = json.loads(resp.content)
        self.assertEqual(compare, self.textbook2)

    def test_get_nonexistant(self):
        "Get a nonexistent textbook"
        resp = self.client.get(self.url_nonexist)
        self.assertEqual(resp.status_code, 404)

    def test_delete(self):
        "Delete a textbook by ID"
        resp = self.client.delete(self.url1)
        self.assertEqual(resp.status_code, 204)
        course = self.store.get_item(self.course.location)
        self.assertEqual(course.pdf_textbooks, [self.textbook2])

    def test_delete_nonexistant(self):
        "Delete a textbook by ID, when the ID doesn't match an existing textbook"
        resp = self.client.delete(self.url_nonexist)
        self.assertEqual(resp.status_code, 404)
        course = self.store.get_item(self.course.location)
        self.assertEqual(course.pdf_textbooks, [self.textbook1, self.textbook2])

    def test_create_new_by_id(self):
        "Create a textbook by ID"
        textbook = {
            "tab_title": "a new textbook",
            "url": "supercool.pdf",
            "id": "1supercool",
        }
        url = self.course_locator.url_reverse("textbooks", "1supercool")
        resp = self.client.post(
            url,
            data=json.dumps(textbook),
            content_type="application/json",
        )
        self.assertEqual(resp.status_code, 201)
        resp2 = self.client.get(url)
        self.assertEqual(resp2.status_code, 200)
        compare = json.loads(resp2.content)
        self.assertEqual(compare, textbook)
        course = self.store.get_item(self.course.location)
        self.assertEqual(
            course.pdf_textbooks,
            [self.textbook1, self.textbook2, textbook]
        )

    def test_replace_by_id(self):
        "Create a textbook by ID, overwriting an existing textbook ID"
        replacement = {
            "tab_title": "You've been replaced!",
            "url": "supercool.pdf",
            "id": "2",
        }
        resp = self.client.post(
            self.url2,
            data=json.dumps(replacement),
            content_type="application/json",
        )
        self.assertEqual(resp.status_code, 201)
        resp2 = self.client.get(self.url2)
        self.assertEqual(resp2.status_code, 200)
        compare = json.loads(resp2.content)
        self.assertEqual(compare, replacement)
        course = self.store.get_item(self.course.location)
        self.assertEqual(
            course.pdf_textbooks,
            [self.textbook1, replacement]
        )


class TextbookValidationTestCase(TestCase):
    "Tests for the code to validate the structure of a PDF textbook"

    def setUp(self):
        "Set some useful content for tests"
        self.tb1 = {
            "tab_title": "Hi, mom!",
            "url": "/mom.pdf"
        }
        self.tb2 = {
            "tab_title": "Hi, dad!",
            "chapters": [
                {
                    "title": "Baseball",
                    "url": "baseball.pdf",
                }, {
                    "title": "Basketball",
                    "url": "crazypants.pdf",
                }
            ]
        }
        self.textbooks = [self.tb1, self.tb2]

    def test_happy_path_plural(self):
        "Test that the plural validator works properly"
        result = validate_textbooks_json(json.dumps(self.textbooks))
        self.assertEqual(self.textbooks, result)

    def test_happy_path_singular_1(self):
        "Test that the singular validator works properly"
        result = validate_textbook_json(json.dumps(self.tb1))
        self.assertEqual(self.tb1, result)

    def test_happy_path_singular_2(self):
        "Test that the singular validator works properly, with different data"
        result = validate_textbook_json(json.dumps(self.tb2))
        self.assertEqual(self.tb2, result)

    def test_valid_id(self):
        "Test that a valid ID doesn't trip the validator, and comes out unchanged"
        self.tb1["id"] = 1
        result = validate_textbook_json(json.dumps(self.tb1))
        self.assertEqual(self.tb1, result)

    def test_invalid_id(self):
        "Test that an invalid ID trips the validator"
        self.tb1["id"] = "abc"
        with self.assertRaises(TextbookValidationError):
            validate_textbook_json(json.dumps(self.tb1))

    def test_invalid_json_plural(self):
        "Test that invalid JSON trips the plural validator"
        with self.assertRaises(TextbookValidationError):
            validate_textbooks_json("[{'abc'}]")

    def test_invalid_json_singular(self):
        "Test that invalid JSON trips the singluar validator"
        with self.assertRaises(TextbookValidationError):
            validate_textbook_json("[{1]}")

    def test_wrong_json_plural(self):
        "Test that a JSON object trips the plural validators (requires a list)"
        with self.assertRaises(TextbookValidationError):
            validate_textbooks_json('{"tab_title": "Hi, mom!"}')

    def test_wrong_json_singular(self):
        "Test that a JSON list trips the plural validators (requires an object)"
        with self.assertRaises(TextbookValidationError):
            validate_textbook_json('[{"tab_title": "Hi, mom!"}, {"tab_title": "Hi, dad!"}]')

    def test_no_tab_title_plural(self):
        "Test that `tab_title` is required for the plural validator"
        with self.assertRaises(TextbookValidationError):
            validate_textbooks_json('[{"url": "/textbook.pdf"}]')

    def test_no_tab_title_singular(self):
        "Test that `tab_title` is required for the singular validator"
        with self.assertRaises(TextbookValidationError):
            validate_textbook_json('{"url": "/textbook.pdf"}')

    def test_duplicate_ids(self):
        "Test that duplicate IDs in the plural validator trips the validator"
        textbooks = [{
            "tab_title": "name one",
            "url": "one.pdf",
            "id": 1,
        }, {
            "tab_title": "name two",
            "url": "two.pdf",
            "id": 1,
        }]
        with self.assertRaises(TextbookValidationError):
            validate_textbooks_json(json.dumps(textbooks))
