"""
Tests declaration for Nine CMS

All tests assume settings.LANGUAGE_CODE is defined
"""
__author__ = 'George Karakostas'
__copyright__ = 'Copyright 2015, George Karakostas'
__licence__ = 'BSD-3'
__email__ = 'gkarak@9-dev.com'

from django.test import TestCase
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User
from django.utils import translation
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import ProgrammingError
from ninecms.models import Node, image_path_file_name, file_path_file_name, video_path_file_name, TaxonomyTerm, PageType
from ninecms.utils.transliterate import transliterate
from ninecms.utils.serialize import ExtJsonSerializer
from django.utils.dateformat import DateFormat
from ninecms.forms import ContactForm, SearchForm
from ninecms.templatetags import ninecms_extras
from ninecms.tests.setup import create_front, create_basic, create_menu, create_block_static, create_block_menu, \
    create_block_signal_terms, create_block_simple, create_page, create_block_signal_video, create_image, create_file, \
    create_video, create_terms, assert_front, assert_basic, create_user, assert_image, data_contact, get_front_title, \
    data_login, url_with_lang, serialize


class ContentTests(TestCase):
    """ Tests with some default initial content, no login """
    @classmethod
    def setUpTestData(cls):
        """ Setup initial data:
        Create front page
        Create basic page
        Create menu
        Create blocks
        Create media
        :return: None
        """
        cls.node_rev_front = create_front('/')
        cls.node_rev_basic = create_basic('about')
        cls.menu = create_menu()
        # block system model tests
        cls.element_static = create_block_static(cls.node_rev_front.node.page_type, cls.node_rev_basic.node)
        cls.element_menu = create_block_menu(cls.node_rev_front.node.page_type, cls.menu)
        cls.element_terms = create_block_signal_terms(cls.node_rev_front.node.page_type)
        cls.element_generic = create_block_simple(cls.node_rev_front.node.page_type, 'generic')
        # block system view tests
        for i in range(1, 5):
            node_revision_basic = create_basic('block/' + str(i), settings.LANGUAGE_CODE, 'About ' + str(i))
            if i == 4:  # disable for last one
                node_revision_basic.node.status = 0
                node_revision_basic.node.save()
            create_block_static(cls.node_rev_front.node.page_type, node_revision_basic.node)
            create_page('video', "Video page", '', '', 'Video ' + str(i))
        create_block_signal_video(cls.node_rev_front.node.page_type)
        create_block_simple(cls.node_rev_front.node.page_type, 'language')
        # Media system
        cls.img = create_image()
        cls.img_big = create_image('test_big.jpg')
        cls.img_big_portrait = create_image('test_big_portrait.jpg')
        cls.file = create_file()
        cls.video = create_video()
        # Taxonomy, Contact
        cls.term = create_terms(())
        cls.element_contact = create_block_simple(cls.node_rev_front.node.page_type, 'contact')
        # Extra model tests
        cls.node_rev_basic_no_alias = create_basic('')
        # login / user menu
        cls.element_login = create_block_simple(cls.node_rev_front.node.page_type, 'login')
        cls.element_user_menu = create_block_simple(cls.node_rev_front.node.page_type, 'user-menu')
        # search
        cls.element_search = create_block_simple(cls.node_rev_front.node.page_type, 'search')
        cls.node_rev_basic_search = create_basic('search')
        cls.element_search_results = create_block_simple(cls.node_rev_basic_search.node.page_type, 'search-results')

    """ Node System """
    def test_node_model_methods(self):
        """ Test model methods such as __unicode__
        :return: None
        """
        self.assertEqual(str(self.node_rev_front.node.page_type), self.node_rev_front.node.page_type.description)
        self.assertEqual(str(self.node_rev_front.node), self.node_rev_front.node.title)
        self.assertEqual(str(self.node_rev_front), self.node_rev_front.title)
        self.assertEqual(self.node_rev_front.node.get_absolute_url(), '/')
        self.assertEqual(self.node_rev_basic.node.get_absolute_url(), '/about/')
        self.assertEqual('/cms/content/' in Node.objects.filter(title="Video 1")[0].get_absolute_url(), True)
        self.assertEqual(self.node_rev_basic.node.get_alias_template(), 'about')
        template = 'node_%d' % self.node_rev_basic_no_alias.node.id
        self.assertEqual(self.node_rev_basic_no_alias.node.get_alias_template(), template)

    def test_node_util_methods(self):
        """ Test utility methods
        :return: None
        """
        self.assertEqual(transliterate("Ξεσκεπάζω την ψυχοφθόρα βδελυγμία"), 'Xeskepazo-tin-psychofthora-bdelygmia')
        self.assertEqual(transliterate("Τάχιστη αλώπηξ βαφής ψημένη γη, δρασκελίζει υπέρ νωθρού κυνός"),
                         'Tachisti-alopix-bafis-psimeni-gi-draskelizei-yper-nothroy-kynos')
        self.assertEqual(transliterate('Ξεσκεπάζω την ψυχοφθόρα βδελυγμία%.doc', True, True),
                         'xeskepazo_tin_psychofthora_bdelygmia.doc')
        self.assertEqual(ninecms_extras.upper_no_intonation("Σχετικά"), "ΣΧΕΤΙΚΑ")

    def test_node_util_serialize(self):
        """ Test serializer with an object with simple field, related field, foreign key field, m2m field, prop
        :return: None
        """
        # assert ok
        nodes = Node.objects.select_related('page_type').all()
        data = serialize(nodes)
        self.assertIn('"model": "ninecms.node"', data)
        self.assertIn('"pk": 1', data)
        self.assertIn('"fields": {', data)
        self.assertIn('"page_type": "front"', data)
        self.assertIn('"user": 1', data)
        self.assertIn('"get_absolute_url": "/"', data)
        self.assertIn('"title": "Logismiko"', data)
        # assert not ok
        nodes = Node.objects.all()
        self.assertRaises(ProgrammingError, serialize, nodes)
        # assert many to many
        terms = TaxonomyTerm.objects.all()
        data = ExtJsonSerializer().serialize(terms, fields=['nodes'])
        self.assertIn('"nodes": []', data)

    def test_node_view_with_front(self):
        """ Test node view for front page
        Test simple
        Test front if /// is asked (TestCase cuts trailing slashes anyway though)
        Test front if /%2f/ is asked
        :return:
        """
        assert_front(self, reverse('ninecms:index'))
        assert_front(self, '///')
        assert_front(self, '/%2f/')

    def test_node_view_with_basic(self):
        """ Test basic node
        Test node view of basic page, slash is missing (expecting redirect)
        Test node view of basic page properly
        :return: None
        """
        response = assert_basic(self, 'about')
        self.assertRedirects(response, url_with_lang('/about/'), status_code=301)
        assert_basic(self, 'about/')

    def test_content_view_with_alias(self):
        """ Test view properly /cms/content/<node_id>
        :return: None
        """
        response = assert_basic(self, self.node_rev_basic.node_id, 'content_node')
        self.assertRedirects(response, url_with_lang('/about/'), status_code=301)

    def test_content_view_alias_redirect(self):
        """ Test view redirect from alias
        :return: None
        """
        Node.objects.get_or_create(page_type=self.node_rev_basic.node.page_type, language=settings.LANGUAGE_CODE,
                                   title='redirect', user=create_user(), alias='redirect', link='/', redirect=True)
        response = assert_front(self, '/redirect/')
        # first redirect is /redirect/ to /en/redirect/, last is to front
        # depending on settings.LANGUAGES, therefore not using assertRedirects
        self.assertEqual('/' in response.redirect_chain[-1][0], True)
        self.assertEqual(response.redirect_chain[-1][1], 301)

    def test_content_view_no_alias_unpublished(self):
        """ Test that an unpublished node without alias gets 403
        :return: None
        """
        self.node_rev_basic_no_alias.node.status = False
        self.node_rev_basic_no_alias.node.save()
        response = self.client.get(reverse('ninecms:content_node', args=(self.node_rev_basic_no_alias.node.id,)))
        self.assertEqual(response.status_code, 403)
        self.node_rev_basic_no_alias.node.status = True
        self.node_rev_basic_no_alias.node.save()

    def test_content_view_alias_unpublished(self):
        """ Test that an unpublished node with alias gets 403
        :return: None
        """
        self.node_rev_basic.node.status = False
        self.node_rev_basic.node.save()
        response = self.client.get(url_with_lang('/about/'))
        self.assertEqual(response.status_code, 403)
        self.node_rev_basic.node.status = True
        self.node_rev_basic.node.save()

    def test_node_aliases(self):
        """ Test node save overridden function that produces aliases
        :return: None
        """
        page_type = PageType.objects.create(name='test_aliases', description="Test aliases",
                                            url_pattern='test/[node:title]/[node:created:Y-m-d]/[node:id]')
        node = Node.objects.create(page_type=page_type, title="Test aliases node", user=self.node_rev_basic.node.user)
        alias = 'test/test-aliases-node/%s/%d' % (DateFormat(node.created).format('Y-m-d'), node.id)
        self.assertEqual(node.alias, alias)

    def test_node_aliases_duplicates(self):
        """ Test node save overridden function that produces aliases for duplicate nodes
        :return: None
        """
        page_type = PageType.objects.create(name='test_aliases_duplicates', description="Test aliases duplicates",
                                            url_pattern='test/[node:title]')
        node = Node.objects.create(page_type=page_type, title="Test aliases node", user=self.node_rev_basic.node.user)
        self.assertEqual(node.alias, 'test/test-aliases-node')
        node = Node.objects.create(page_type=page_type, title="Test aliases node", user=self.node_rev_basic.node.user)
        self.assertEqual(node.alias, 'test/test-aliases-node/%d' % node.id)

    """ Menu System """
    def test_menu_model_methods(self):
        """ Test menu model methods
        :return: None
        """
        self.assertEqual(str(self.menu), "Main Menu")

    """ Block System """
    def test_model_methods_block_static(self):
        """ Test model methods for static block
        :return: None
        """
        self.assertEqual(str(self.element_static), ' '.join((str(self.node_rev_front.node.page_type), 'header')))
        self.assertEqual(str(self.element_static.block), '-'.join(('static', str(self.node_rev_basic.node.title))))

    def test_model_methods_block_menu(self):
        """ Test model methods for menu block
        :return: None
        """
        self.assertEqual(str(self.element_menu.block), '-'.join(('menu', str(self.menu))))

    def test_model_methods_block_signal(self):
        """ Test model methods for signal block
        :return: None
        """
        self.assertEqual(str(self.element_terms.block), 'signal-terms')

    def test_model_methods_block_generic(self):
        """ Test model methods for generic block
        :return: None
        """
        self.assertEqual(str(self.element_generic.block), 'generic')

    def test_node_view_block_static(self):
        """ Test static block for front view
        :return: None
        """
        response = assert_front(self, reverse('ninecms:index'))
        for i in range(1, 4):
            self.assertContains(response, '<div class="body">About ' + str(i) + ' page.</div>')
        self.assertNotContains(response, '<div class="body">About 4 page.</div>')

    def test_node_view_block_menu(self):
        """ Test menu block for front view
        :return: None
        """
        response = assert_front(self, reverse('ninecms:index'))
        self.assertContains(response, '<a href="/">')
        self.assertContains(response, '<a href="/about/">')
        self.assertContains(response, '<a href="/about/#team">')
        self.assertContains(response, '<a href="http://google.com/">')
        self.assertContains(response, '<a href="#bookmark">')

    def test_node_view_block_menu_root_disabled(self):
        """ Test menu block for front view if root is disabled
        :return: None
        """
        self.menu.disabled = True
        self.menu.save()
        response = assert_front(self, reverse('ninecms:index'))
        self.assertNotContains(response, '<a href="/">Front</a>', html=True)
        self.assertNotContains(response, '<a href="/about/">About</a>', html=True)
        self.menu.disabled = False
        self.menu.save()

    def test_node_view_block_menu_item_disabled(self):
        """ Test menu block for front view if item is disabled
        :return: None
        """
        menu_item_disabled = self.menu.children.last()
        menu_item_disabled.disabled = True
        # saving parent does not save children
        menu_item_disabled.save()
        response = assert_front(self, reverse('ninecms:index'))
        self.assertContains(response, '<a href="/">')
        self.assertNotContains(response, '<a href="/about">')
        menu_item_disabled.disabled = False
        menu_item_disabled.save()

    def test_node_view_block_signal_video(self):
        """ Test signal block for random video
        :return: None
        """
        response = assert_front(self, reverse('ninecms:index'))
        self.assertContains(response, '<video')

    def test_node_view_block_language(self):
        """ Test block for language menu
        :return: None
        """
        response = assert_front(self, reverse('ninecms:index'))
        self.assertContains(response, '<ul class="nav navbar-nav navbar-right language menu')

    """ Media System """
    def test_image_model_methods(self):
        """ Test image model methods
        :return: None
        """
        self.assertEqual(str(self.img), str(self.img.node))
        self.assertEqual(image_path_file_name(self.img, 'test_small.png'), self.img.image)

    def test_file_model_methods(self):
        """ Test file model methods
        :return: None
        """
        self.assertEqual(str(self.file), str(self.file.node))
        self.assertEqual(file_path_file_name(self.file, 'readme.txt'), self.file.file)
        with self.assertRaises(ValidationError):
            create_file('ninecms/basic/file/readme.php')

    def test_video_model_methods(self):
        """ Test video model methods
        :return: None
        """
        self.assertEqual(str(self.video), str(self.video.node))
        self.assertEqual(video_path_file_name(self.video, 'video.mp4'), self.video.video)
        with self.assertRaises(ValidationError):
            create_video('ninecms/basic/video/video.php')

    def test_image_style_upscale(self):
        """ Test thumbnail-upscale
        :return: None
        """
        ninecms_extras.image_style(self.img.image.url, 'thumbnail_upscale')
        assert_image(self, None, self.img, '150x150', 'thumbnail_upscale')

    def test_image_style_thumbnail_with_large(self):
        """ Test thumbnail with large image
        :return: None
        """
        ninecms_extras.image_style(self.img_big.image.url, 'thumbnail')
        assert_image(self, None, self.img_big, '150x73', 'thumbnail')

    def test_image_style_crop_thumbnail(self):
        """ Test crop-thumbnail with large image
        :return: None
        """
        ninecms_extras.image_style(self.img_big.image.url, 'blog_style')
        assert_image(self, None, self.img_big, '350x226', 'blog_style')

    def test_image_style_crop_thumbnail_portrait(self):
        """ Test crop-thumbnail with large image portrait
        :return: None
        """
        ninecms_extras.image_style(self.img_big_portrait.image.url, 'blog_style')
        assert_image(self, None, self.img_big_portrait, '350x226', 'blog_style')

    """ Taxonomy System """
    def test_model_methods_terms(self):
        """ Test model methods
        :return: None
        """
        self.assertEqual(str(self.term), self.term.name)

    def test_node_view_terms_signals(self):
        """ Test that terms are rendered properly in block
        Test that signals work properly
        :return: None
        """
        response = assert_front(self, reverse('ninecms:index'))
        expected = '<li>Terms<ul><li>Tags<ul class="children"><li>General</li></ul></li></ul></li>'
        self.assertContains(response, expected, html=True)

    """ Contact System """
    def test_contact_form_invalid(self):
        """ Test that an empty form is invalid
        :return: None
        """
        form = ContactForm()
        self.assertEqual(form.is_valid(), False)

    def test_contact_form_valid(self):
        """ Test that a form is valid
        :return: None
        """
        form = ContactForm(data=data_contact())
        self.assertEqual(form.is_valid(), True)

    def test_contact_view(self):
        """ Test that view renders properly
        :return: None
        """
        response = assert_front(self, reverse('ninecms:index'))
        self.assertContains(response, '<form action="' + reverse('ninecms:contact') + '" method="post"')

    def test_contact_form_post_invalid(self):
        """ Test posting invalid data
        :return: None
        """
        data = data_contact()
        del data['sender_name']
        response = self.client.post(reverse('ninecms:contact'), data, follow=True)
        self.assertContains(response, get_front_title() + '</h1>')
        self.assertContains(response, "Contact form message has NOT been sent. Please fill in all contact form fields.")
        self.assertRedirects(response, reverse(data['redirect']))

    def test_contact_form_post_valid(self):
        """ Test posting valid data
        :return: None
        """
        data = data_contact()
        response = self.client.post(reverse('ninecms:contact'), data, follow=True)
        self.assertContains(response, get_front_title() + '</h1>')
        self.assertContains(response, "A message has been sent to the site using the contact form.")
        self.assertRedirects(response, reverse(data['redirect']))
        # https://docs.djangoproject.com/en/1.8/topics/testing/tools/#email-services
        # doesn't work; probably requires to get the mail object; test externally ok
        # self.assertEqual(len(mail.outbox), 1)

    """ Permissions """
    def test_login_view(self):
        """ Test that login form, user menu render properly
        :return: None
        """
        response = assert_front(self, reverse('ninecms:index'))
        self.assertContains(response, '<form action="' + reverse('ninecms:login') + '" method="post">')
        self.assertContains(response, '<li><a href="#">Login</a></li>')
        self.assertNotContains(response, '<form action="' + reverse('ninecms:logout') + '" method="post">')

    def test_login_form_post_invalid(self):
        """ Test posting invalid data
        :return: None
        """
        data = data_login()
        del data['password']
        response = self.client.post(reverse('ninecms:login'), data, follow=True)
        self.assertContains(response, get_front_title() + '</h1>')
        self.assertContains(response, "Please fill in all login form fields.")
        self.assertRedirects(response, reverse(data['redirect']))

    def test_login_form_post_invalid_wrong_password(self):
        """ Test posting invalid data: wrong password
        :return: None
        """
        data = data_login()
        data['password'] = "wrong"
        response = self.client.post(reverse('ninecms:login'), data, follow=True)
        self.assertContains(response, get_front_title() + '</h1>')
        self.assertContains(response, "Unfortunately the username or password are not correct.")
        self.assertRedirects(response, reverse(data['redirect']))

    def test_login_form_post_invalid_inactive(self):
        """ Test posting invalid data: inactive user
        :return: None
        """
        user = User.objects.get(username="admin")
        user.is_active = False
        user.save()
        data = data_login()
        response = self.client.post(reverse('ninecms:login'), data, follow=True)
        self.assertContains(response, get_front_title() + '</h1>')
        self.assertContains(response, "The account is disabled.")
        self.assertRedirects(response, reverse(data['redirect']))
        user.is_active = True
        user.save()

    def test_login_form_post_valid(self):
        """ Test posting valid data
        :return: None
        """
        data = data_login()
        response = self.client.post(reverse('ninecms:login'), data, follow=True)
        self.assertContains(response, get_front_title() + '</h1>')
        self.assertContains(response, "Login successful for")
        self.assertRedirects(response, reverse(data['redirect']))

    """ Search System """
    def test_search_form_valid(self):
        """ Test that a search form is valid
        :return: None
        """
        form = SearchForm(data={'q': "test"})
        self.assertEqual(form.is_valid(), True)

    def test_search_view(self):
        """ Test that search form renders properly
        :return: None
        """
        response = assert_front(self, reverse('ninecms:index'))
        self.assertContains(response, '<form id="searchform" action="/search/" method="get">')

    def test_search_results_view(self):
        """ Test search results view node
        :return: None
        """
        translation.activate(settings.LANGUAGE_CODE)
        response = self.client.get(reverse('ninecms:alias', args=('search/',)), {'q': 'About'})
        self.assertContains(response, '/block/1/">About 1</a>')
        response = self.client.get(reverse('ninecms:alias', args=('search/',)), {'q': 'test'})
        self.assertContains(response, '<p>No results found.</p>')
