import json

import pymongo
from datadiff.tools import assert_equal
from nose.tools import assert_is_none

from data_access import ReviewerDao, ReviewDao, MagazineDao
from mongo_utils import MongoUtils
from my_exceptions import InvalidData
from my_mongo_client import MyMongoClientReader


class DataAccessTestCase(object):
    def __init__(self):
        self.mongo_client = MyMongoClientReader('test')
        self.mongo_utils = MongoUtils(self.mongo_client)

    def setup(self):
        self.mongo_utils.clear_collection('review')
        self.mongo_utils.clear_collection('reviewer')
        self.mongo_utils.clear_collection('magazine')

    def _assert_equals_without_review_id(self, expected, actual):
        reviews_without_id = []
        for review in actual['review']:
            review = {k: v for k, v in review.items() if k != '_id'}
            reviews_without_id.append(review)
        actual['review'] = reviews_without_id
        assert_equal(expected, actual)

    def _assert_equals(self, expected, actual):
        reviews_without_id = []
        for review in actual:
            review = {k: v for k, v in review.items() if k != '_id'}
            reviews_without_id.append(review)
        assert_equal(expected, reviews_without_id)

    def _assert_tuple_equal_without_id(self, expected, actual):
        tuple_list = []
        for first, second in actual:
            second_content_without_id = []
            for elem in second:
                second_content_without_id.append({k: v for k, v in elem.items() if k != '_id'})
            tuple_list.append((first, second_content_without_id))
        assert_equal(expected, tuple_list)


class TestMagazineDao(DataAccessTestCase):
    def __init__(self):
        super(TestMagazineDao, self).__init__()
        self.magazine_dao = MagazineDao(self.mongo_client)

    def test_get_all_magazine_years(self):
        self.mongo_utils.insertJsonFile('magazine', 'magazines.json', 'issueNumber')
        magazine_years = self.magazine_dao.get_all_magazine_years()
        assert_equal([2003, 2007], magazine_years)

    def test_get_last_magazine(self):
        self.mongo_utils.insertJsonFile('magazine', 'magazines.json', 'issueNumber')
        magazine = self.magazine_dao.get_last_magazine()
        assert_equal(273, magazine['issueNumber'])

    def test_get_all_magazines_by_year(self):
        self.mongo_utils.insertJsonFile('magazine', 'magazines.json', 'issueNumber')
        magazines = self.magazine_dao.get_all_magazines_by_year(2007)
        assert_equal(1, len(magazines))
        assert_equal(273, magazines[0]['issueNumber'])

    def test_get_magazine_by_issue_number(self):
        self.mongo_utils.insertJsonFile('magazine', 'magazines.json', 'title')
        self.mongo_utils.insertJsonFile('review', 'reviews.json', 'title')
        actual_magazine = self.magazine_dao.get_magazine_by_issue_number(273)

        with open('data/expected_magazine_273.json', 'rb') as expected_file:
            data = expected_file.read()
            expected_magazine = json.loads(data)
            self._assert_equals_without_review_id(expected_magazine, actual_magazine)

    def test_get_empty_magazines(self):
        self.mongo_utils.insertJsonFile('magazine', 'magazines.json', 'issueNumber')
        empty_magazines = self.magazine_dao.get_empty_magazines()
        assert_equal([273, 200], empty_magazines)

    def test_save_magazine(self):
        magazine_data = {
            '_id': '',
            'issueNumber': '280',
            'issueDate': '15/07/2013',
            'coverUrl': 'http://www.canardpc.com/img/couv/couv_Canard_PC_280.jpg',
            'coverTnUrl': 'http://www.canardpc.com/img/couv/couv_Canard_PC_280_169.jpg',
            'title': 'Minecraft',
            'subTitle': 'Le dossier qui vous laissera sur le cube',
        }

        self.magazine_dao.save_magazine(magazine_data)

        added_magazine = self.magazine_dao.get_magazine_by_issue_number(280)
        assert_equal('http://www.canardpc.com/img/couv/couv_Canard_PC_280.jpg', added_magazine['coverUrl'])
        assert_equal('http://www.canardpc.com/img/couv/couv_Canard_PC_280_169.jpg', added_magazine['coverTnUrl'])

    def test_get_all_magazines(self):
        self.mongo_utils.insertJsonFile('magazine', 'magazines.json', 'issueNumber')
        self.mongo_utils.insertJsonFile('magazine', 'others_magazines.json', 'issueNumber', False)
        actual_magazines = self.magazine_dao.get_all_magazines()

        assert_equal([(2003, [{"issueDate": "03/12/2003",
                               "coverUrl": "http://www.canardpc.com/img/couv/couv_Canard_PC_200.jpg",
                               "coverTnUrl": "http://www.canardpc.com/img/couv/couv_Canard_PC_200.jpg",
                               "issueNumber": 200},
                              {"issueDate": "03/05/2003",
                               "coverUrl": "http://www.canardpc.com/img/couv/couv_Canard_PC_200.jpg",
                               "coverTnUrl": "http://www.canardpc.com/img/couv/couv_Canard_PC_200.jpg",
                               "issueNumber": 180}]),
                      (2007, [{"issueDate": "26/11/2007",
                               "coverUrl": "http://www.canardpc.com/img/couv/couv_Canard_PC_273.jpg",
                               "coverTnUrl": "http://www.canardpc.com/img/couv/couv_Canard_PC_273.jpg",
                               "issueNumber": 273},
                              {"issueDate": "26/01/2007",
                               "coverUrl": "http://www.canardpc.com/img/couv/couv_Canard_PC_273.jpg",
                               "coverTnUrl": "http://www.canardpc.com/img/couv/couv_Canard_PC_273.jpg",
                               "issueNumber": 260}])], actual_magazines)


class TestReviewDao(DataAccessTestCase):
    def __init__(self):
        super(TestReviewDao, self).__init__()
        self.review_dao = ReviewDao(self.mongo_client)

    def test_get_all_review_by_title(self):
        self.mongo_utils.insertJsonFile('review', 'reviews.json', 'title')

        reviews = self.review_dao.get_all_review_by_title('bioshock')
        assert_equal(2, len(reviews))
        assert_equal('Bioshock Infinite', reviews[0]['title'])
        assert_equal('http://www.canardpc.com/img/couv/couv_Canard_PC_273.jpg', reviews[0]['issue']['coverTnUrl'])
        assert_equal('Bioshock', reviews[1]['title'])
        assert_equal('http://www.canardpc.com/img/couv/couv_Canard_PC_200.jpg', reviews[1]['issue']['coverTnUrl'])

        reviews = self.review_dao.get_all_review_by_title('Sim')
        assert_equal('Guy Moquette', reviews[0]['reviewer']['name'])

    def test_get_all_game_titles(self):
        self.mongo_utils.insertJsonFile('review', 'reviews.json', 'title')

        expected_list = [u'Bioshock', u'Bioshock Infinite', u'Doom & Destiny', u'Sim City',
                         u'Starcraft 2: Heart Of The Swarm']
        actual_list = self.review_dao.get_all_game_titles()
        assert_equal(expected_list, actual_list)

    def test_get_studio_by_name(self):
        self.mongo_utils.insertJsonFile('review', 'reviews.json', 'title')
        actual_studio = self.review_dao.get_studio_by_name('Irrational Games')

        with open('data/expected_studio_irrational.json', 'rb') as expected_file:
            data = expected_file.read()
            expected_studio = json.loads(data)
            self._assert_equals_without_review_id(expected_studio, actual_studio)

    def test_reviews_by_genre(self):
        self.mongo_utils.insertJsonFile('review', 'reviews.json', 'title')
        actual_genre = self.review_dao.get_reviews_by_genre('FPS')
        with open('data/expected_genre_FPS.json', 'rb') as expected_file:
            data = expected_file.read()
            expected_genre = json.loads(data)
            self._assert_equals_without_review_id(expected_genre, actual_genre)

    def test_get_all_reviews(self):
        self.mongo_utils.insertJsonFile('review', 'reviews.json', 'title')

        reviews = self.review_dao.get_all_reviews()
        assert_equal(5, len(reviews))

    def test_save_review(self):
        self.mongo_utils.insertJsonFile('reviewer', 'reviewer.json', 'name')
        self.mongo_utils.insertJsonFile('magazine', 'magazines.json', 'issueNumber')
        review_data_from_ui = {
            '_id': '',
            "issueNumber": 273,
            "title": "Bioshock",
            "subTitle": "Nouveau sous-titre",
            "year": 2009,
            "displayedGenre": "FPS",
            "primaryGenre": "FPS",
            "reviewer": "Threanor",
            "score": 10,
            "studioName": "Irrational Games",
            "studio": "Irrational Games",
            "publisher": "2K",
            "coverTnUrl": "http://cpc.cx/7ol"
        }

        self.review_dao.save_review(review_data_from_ui)

        updated_review = self.review_dao.get_review_by_title('Bioshock')
        assert_equal('Nouveau sous-titre', updated_review['subTitle'])
        assert_equal({'name': 'Threanor',
                      'reviewerTnUrl': 'http://www.canardpc.com/img/redac/threanor.png',
                      'function': u'Journaliste',
                      'isPresent': False}, updated_review['reviewer'])

        assert_equal({'issueNumber': 273, 'coverTnUrl': 'http://www.canardpc.com/img/couv/couv_Canard_PC_273.jpg',
                      'issueDate': '26/11/2007'},
                     updated_review['issue'])

    def test_save_review_with_unknown_reviewer_raise_an_exception(self):
        self.mongo_utils.insertJsonFile('magazine', 'magazines.json', 'issueNumber')
        review_data_from_ui = {
            '_id': '',
            "issueNumber": 273,
            "title": "Bioshock",
            "subTitle": "Nouveau sous-titre",
            "year": 2009,
            "displayedGenre": "FPS",
            "primaryGenre": "FPS",
            "reviewer": "coincoin",
            "score": 10,
            "studioName": "Irrational Games",
            "studio": "Irrational Games",
            "publisher": "2K",
            "coverTnUrl": "http://cpc.cx/7ol"
        }

        try:
            self.review_dao.save_review(review_data_from_ui)
            raise ValueError('Should raise an exception')
        except InvalidData as ex:
            assert_equal(["Testeur 'coincoin' inconnu"], ex.errors)
            assert_is_none(self.review_dao.get_review_by_title('Bioshock'))

    def test_save_review_with_unknown_magazine_raise_an_exception(self):
        self.mongo_utils.insertJsonFile('reviewer', 'reviewer.json', 'name')

        review_data_from_ui = {
            '_id': '',
            "issueNumber": 1,
            "title": "Bioshock",
            "subTitle": "Nouveau sous-titre",
            "year": 2009,
            "displayedGenre": "FPS",
            "primaryGenre": "FPS",
            "reviewer": "Threanor",
            "score": 10,
            "studioName": "Irrational Games",
            "studio": "Irrational Games",
            "publisher": "2K",
            "coverTnUrl": "http://cpc.cx/7ol"
        }

        try:
            self.review_dao.save_review(review_data_from_ui)
            raise ValueError('Should raise an exception')
        except InvalidData as ex:
            assert_equal(["Magazine '1' inconnu"], ex.errors)
            assert_is_none(self.review_dao.get_review_by_title('Bioshock'))

    def test_save_review_with_score_as_number_string(self):
        self.mongo_utils.insertJsonFile('reviewer', 'reviewer.json', 'name')
        self.mongo_utils.insertJsonFile('magazine', 'magazines.json', 'issueNumber')
        review_data_from_ui = {
            '_id': '',
            "issueNumber": 273,
            "title": "Bioshock",
            "subTitle": "Nouveau sous-titre",
            "year": 2009,
            "displayedGenre": "FPS",
            "primaryGenre": "FPS",
            "reviewer": "Threanor",
            "score": '10',
            "studioName": "Irrational Games",
            "studio": "Irrational Games",
            "publisher": "2K",
            "coverTnUrl": "http://cpc.cx/7ol"
        }

        self.review_dao.save_review(review_data_from_ui)

        updated_review = self.review_dao.get_review_by_title('Bioshock')
        assert_equal('Nouveau sous-titre', updated_review['subTitle'])
        assert_equal({'name': 'Threanor',
                      'reviewerTnUrl': 'http://www.canardpc.com/img/redac/threanor.png',
                      'function': u'Journaliste',
                      'isPresent': False}, updated_review['reviewer'])

        assert_equal({'issueNumber': 273, 'coverTnUrl': 'http://www.canardpc.com/img/couv/couv_Canard_PC_273.jpg',
                      'issueDate': '26/11/2007'},
                     updated_review['issue'])

        assert_equal(10, updated_review['score'])

    def test_save_review_with_score_as_not_number_string(self):
        self.mongo_utils.insertJsonFile('reviewer', 'reviewer.json', 'name')
        self.mongo_utils.insertJsonFile('magazine', 'magazines.json', 'issueNumber')
        review_data_from_ui = {
            '_id': '',
            "issueNumber": 273,
            "title": "Bioshock",
            "subTitle": "Nouveau sous-titre",
            "year": 2009,
            "displayedGenre": "FPS",
            "primaryGenre": "FPS",
            "reviewer": "Threanor",
            "score": 'ahhhh',
            "studioName": "Irrational Games",
            "studio": "Irrational Games",
            "publisher": "2K",
            "coverTnUrl": "http://cpc.cx/7ol"
        }

        self.review_dao.save_review(review_data_from_ui)

        updated_review = self.review_dao.get_review_by_title('Bioshock')
        assert_equal('Nouveau sous-titre', updated_review['subTitle'])
        assert_equal({'name': 'Threanor',
                      'reviewerTnUrl': 'http://www.canardpc.com/img/redac/threanor.png',
                      'function': u'Journaliste',
                      'isPresent': False}, updated_review['reviewer'])

        assert_equal({'issueNumber': 273, 'coverTnUrl': 'http://www.canardpc.com/img/couv/couv_Canard_PC_273.jpg',
                      'issueDate': '26/11/2007'},
                     updated_review['issue'])

        assert_equal(-1, updated_review['score'])
        assert_equal('ahhhh', updated_review['otherScore'])

    def test_get_all_genres(self):
        self.mongo_utils.insertJsonFile('review', 'reviews.json', 'title')

        actual_genres = self.review_dao.get_all_genres()
        self._assert_tuple_equal_without_id([('City Builder', [{'title': 'Sim City', 'primaryGenre': 'City Builder'}]),
                                             ('FPS', [{'title': 'Bioshock', 'primaryGenre': 'FPS'},
                                                      {'title': 'Bioshock Infinite', 'primaryGenre': 'FPS'}]),
                                             ('RPG', [{'title': 'Doom & Destiny', 'primaryGenre': 'RPG'}]),
                                             ('STR',
                                              [{'title': 'Starcraft 2: Heart Of The Swarm', 'primaryGenre': 'STR'}])],
                                            actual_genres)

    def test_get_all_scores(self):
        self.mongo_utils.insertJsonFile('review', 'reviews.json', 'title')
        self.mongo_utils.insertData('review', [
            {
                "title": "Surgeon Simulator",
                "year": 2013,
                "score": 4,
                "otherScore": "Scie"
            }
        ], 'title', False)

        self._assert_tuple_equal_without_id([(4, [{'score': 4, 'title': 'Sim City'},
                                                  {'score': 4, 'title': 'Surgeon Simulator'}]),
                                             (6, [{'score': 6, 'title': 'Starcraft 2: Heart Of The Swarm'}]),
                                             (8, [{'score': 8, 'title': 'Doom & Destiny'}]),
                                             (9, [{'score': 9, 'title': 'Bioshock Infinite'}]),
                                             (10, [{'score': 10, 'title': 'Bioshock'}])],
                                            self.review_dao.get_all_scores())

    def test_get_all_review_titles(self):
        self.mongo_utils.insertJsonFile('review', 'reviews.json', 'title')

        self._assert_tuple_equal_without_id([('B', [{'title': 'Bioshock'}, {'title': 'Bioshock Infinite'}]),
                                             ('D', [{'title': 'Doom & Destiny'}]),