import os

from django.test import TestCase
from django.test.utils import override_settings
from django.urls import reverse
from document_catalogue import models, permissions
from . import base


class BaseTestWithUsers(TestCase):
    """
        Test behaviours for Document CRUD views
    """
    def setUp(self):
        super().setUp()
        self.categories = base.create_document_categories()
        self.privilegedUser = base.create_user(username='privileged',
                                               permissions=('Can add document', 'Can change document', 'Can delete document'))
        self.restrictedUser = base.create_user(username='restricted')

    def login(self, user):
        response = self.client.login(username=user.username, password='password')
        self.assertTrue(response)


class PermissionsTests(BaseTestWithUsers):
    """
    Test default permissions
    """
    def test_user_can_view_document_catalogue(self):
        self.assertFalse(permissions.user_can_view_document_catalogue(base.anonymous_user()))
        self.assertTrue(permissions.user_can_view_document_catalogue(self.restrictedUser))

    def test_user_can_download_document(self):
        self.assertFalse(permissions.user_can_download_document(base.anonymous_user()))
        self.assertTrue(permissions.user_can_download_document(self.restrictedUser))

    def test_user_can_edit_document(self):
        self.assertFalse(permissions.user_can_edit_document(self.restrictedUser))
        self.assertTrue(permissions.user_can_edit_document(self.privilegedUser))

    def test_user_can_post_document(self):
        self.assertFalse(permissions.user_can_post_document(self.restrictedUser))
        self.assertTrue(permissions.user_can_post_document(self.privilegedUser))

    def test_user_can_delete_document(self):
        self.assertFalse(permissions.user_can_delete_document(self.restrictedUser))
        self.assertTrue(permissions.user_can_delete_document(self.privilegedUser))


class SuccessDocumentViewTests(BaseTestWithUsers) :
    """
        SUCCESS -- test privileged user makes perfectly reasonable requests
    """
    def test_catalogue_list_view(self):
        self.login(self.restrictedUser)   # Any logged-in user can view the catalogue, by default
        url = reverse('document_catalogue:catalogue_list')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200, "Catalogue view returned non-success status code.")

    def test_category_list_view(self):
        self.login(self.restrictedUser)   # Any logged-in user can view the catalogue, by default
        category_slug = self.categories[0].slug
        url = reverse('document_catalogue:category_list', kwargs={'slug': category_slug})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200, "Category view returned non-success status code.")

    def test_document_detail_view(self):
        self.login(self.restrictedUser)   # Any logged-in user can view docuemnts in the catalogue, by default
        document = base.create_document()
        url = reverse('document_catalogue:document_detail', kwargs={'pk': document.pk})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200, "Document detail view returned non-success status code.")

    def test_document_download_view(self):
        self.login(self.restrictedUser)   # Any logged-in user can view docuemnts in the catalogue, by default
        document = base.create_document()
        url = reverse('document_catalogue:document_download', kwargs={'pk': document.pk})
        if base.appConfig.settings.USE_PRIVATE_FILES:
            response = self.client.get(url, follow=True)
            self.assertEqual(response.status_code, 200,
                             "Document download view should re-direct and respond with file.")
            self.assertEqual(302, response.redirect_chain[0][1])
            self.assertIn(document.file.url, response.redirect_chain[0][0])
        else:
            # test client doesn't serve media, so just check for the redirect and that the path exists
            response = self.client.get(url, follow=False)
            self.assertEqual(response.status_code, 302,
                             "Document download view should re-direct and respond with file.")
            self.assertTrue(os.path.exists(document.file.path))

    def test_document_edit_view_get(self):
        self.login(self.privilegedUser)   # Only privileged users can edit a document
        document = base.create_document()
        url = reverse('document_catalogue:document_edit', kwargs={'pk': document.pk})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200, "Get document edit view returned non-success status code.")

    def test_document_edit_view_post_errors(self) :
        self.login(self.privilegedUser)   # Only privileged users can edit a document
        document = base.create_document()

        myfile = base.generate_file('Test.txt', file_type='txt')
        with open(myfile.name, 'r') as file:
            new_title = 'A Brave New World!'
            post_data = {'file': file, 'title': new_title}  # missing required category
            url = reverse('document_catalogue:document_edit', kwargs={'pk': document.pk})
            response = self.client.post(url, data=post_data)
            self.assertEqual(response.status_code, 200, "Post to document edit view returned non-success status code.")
            self.assertIn(b'form-group has-error', response.content, "Invalid post data response does not contain form-errors")
        # Cleanup
        os.remove(myfile.name)

    def test_document_edit_view_post_success(self):
        self.login(self.privilegedUser)   # Only privileged users can edit a document
        document = base.create_document()
        new_title = 'A Brave New World!'

        myfile = base.generate_file('Test.txt', file_type='txt')
        with open(myfile.name, 'r') as file:
            post_data = {'file': file, 'title': new_title,
                         'description': 'new description', 'category': document.category.pk,
                         'is_published': ''}
            url = reverse('document_catalogue:document_edit', kwargs={'pk': document.pk})
            response = self.client.post(url, data=post_data)
            self.assertEqual(response.status_code, 302, "Post Document edit view did not redirect after editing doc.")
            self.assertNotIn(b'form-errors', response.content, "Valid post data response contains form-errors")

        doc = models.Document.objects.get(pk=document.pk)
        self.assertEqual(doc.title, new_title, "Valid post to document edit view did not alter the title")
        # Cleanup
        os.remove(myfile.name)

    def test_document_delete_view_get(self):
        self.login(self.privilegedUser)   # Only privileged users can delete a document
        document = base.create_document()
        url = reverse('document_catalogue:document_delete', kwargs={'pk': document.pk})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200, "Get Document delete view returned non-success status code.")

    def test_document_delete_view_post(self):
        self.login(self.privilegedUser)   # Only privileged users can delete a document
        document = base.create_document()
        url = reverse('document_catalogue:document_delete', kwargs={'pk': document.pk})
        response = self.client.post(url)
        self.assertEqual(response.status_code, 302, "Post Document delete view did not redirect after deleting doc.")
        with self.assertRaises(models.Document.DoesNotExist, msg="Document still exists after requesting delete URL."):
            models.Document.objects.get(pk=document.pk)


class DeniedDocumentViewTests(BaseTestWithUsers) :
    """
        DENIED -- test under-privileged user or otherwise makes unreasonable requests
    """
    def test_catalogue_list_view(self):
        # Only authenticated users can view the catalogue
        url = reverse('document_catalogue:catalogue_list')
        response = self.client.get(url)
        self.assertEqual(response.status_code, 403, "Catalogue view non-denied status code for anonymous user.")

    def test_category_list_view(self):
        # Only authenticated users can view the catalogue
        category_slug = self.categories[0].slug
        url = reverse('document_catalogue:category_list', kwargs={'slug': category_slug})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 403, "Category view non-denied status code for anonymous user.")

    def test_document_detail_view(self):
        # Only authenticated users can view the catalogue
        document = base.create_document()
        url = reverse('document_catalogue:document_detail', kwargs={'pk': document.pk})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 403, "Document detail view non-denied status code for anonymous user.")

    def test_document_download_view(self):
        # Only authenticated users can download documents
        document = base.create_document()
        url = reverse('document_catalogue:document_download', kwargs={'pk': document.pk})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 403, "Document download view non-denied status code for anonymous user.")

    def test_private_file_download(self):
        # With private-storage, only authenticated users can download a document file
        document = base.create_document()
        if base.appConfig.settings.USE_PRIVATE_FILES:
            response = self.client.get(document.file.url)
            self.assertEqual(response.status_code, 403, "Private file download view not denied for anonymous user.")
        else:
            # test client doesn't serve media, so just check the path exists
            self.assertTrue(os.path.exists(document.file.path))

    def test_document_edit_view(self):
        self.login(self.restrictedUser)   # Only privileged users can edit a document
        document = base.create_document()
        url = reverse('document_catalogue:document_edit', kwargs={'pk': document.pk})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 403, "Document edit view non-denied status code for restricted user.")

    def test_document_delete_view(self):
        self.login(self.restrictedUser)   # Only privileged users can delete a document
        document = base.create_document()
        url = reverse('document_catalogue:document_delete', kwargs={'pk': document.pk})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 403, "Document delete view non-denied status code for restricted user.")


class SuccessAjaxApiTests(BaseTestWithUsers) :
    """
        SUCCESS -- test privileged user makes perfectly reasonable requests
    """
    def test_api_post(self):
        self.login(self.privilegedUser)
        category_slug = self.categories[0].slug
        url = reverse('document_catalogue:api_post', kwargs={'slug': category_slug})
        myfile = base.generate_file('Test.txt', file_type='txt')
        with open(myfile.name, 'r') as file:
            post_data = {'file': file}
            response = self.client.post(url, post_data)
            self.assertEqual(response.status_code, 200, "Request to upload document file returned non-success status code.")
            self.assertEqual(models.Document.objects.count(), 1, "Document count incorrect after file upload.")

        document = models.Document.objects.all().first()
        _, filename = os.path.split(myfile.name)
        self.assertEqual(document.title, filename, "Document has different default name from uploaded file.")
        self.assertIn(category_slug, document.file.path, "Document path does not include category slug.")
        # The filename may get altered when saved, but should contain the name and extension of the original
        name, extension = os.path.splitext(filename)
        self.assertIn(name, document.file.path, "Document path does not contain original uploaded file name.")
        self.assertIn(extension, document.file.path, "Document path does not contain original uploaded file name.")
        # Cleanup
        os.remove(myfile.name)

    def test_api_delete(self):
        self.login(self.privilegedUser)   # Only privileged users can delete a document
        document = base.create_document()
        url = reverse('document_catalogue:api_delete', kwargs={'pk': document.pk})
        response = self.client.delete(url)
        self.assertEqual(response.status_code, 200, "Request to delete document returned non-success status code.")
        with self.assertRaises(models.Document.DoesNotExist, msg="Document still exists after requesting delete URL."):
            models.Document.objects.get(pk=document.pk)


class DeniedAjaxApiTests(BaseTestWithUsers) :
    """
        DENIED -- test unprivileged user or otherwise makes unreasonable requests
    """
    def test_api_post_invalid_file(self):
        self.login(self.privilegedUser)
        category_slug = self.categories[0].slug
        url = reverse('document_catalogue:api_post', kwargs={'slug': category_slug})

        myfile = base.generate_file('Test.html', file_type='html')
        with open(myfile.name, 'r') as file:
            post_data = {'file': file}
            response = self.client.post(url, post_data)
            self.assertEqual(response.status_code, 403, "Invalid filetype did not result in PermissionDenied.")
        # Cleanup
        os.remove(myfile.name)

    def test_api_post(self):
        self.login(self.restrictedUser)   # Only privileged users can delete a document
        category_slug = self.categories[0].slug
        url = reverse('document_catalogue:api_post', kwargs={'slug': category_slug})

        myfile = base.generate_file('Test.txt', file_type='txt')
        with open(myfile.name, 'r') as file:
            post_data = {'file': file}
            response = self.client.post(url, post_data)
            self.assertEqual(response.status_code, 403, "Request to upload file non-denied status code for restricted user.")
        # Cleanup
        os.remove(myfile.name)

    def test_api_delete(self):
        self.login(self.restrictedUser)   # Only privileged users can delete a document
        document = base.create_document()
        url = reverse('document_catalogue:api_delete', kwargs={'pk': document.pk})
        response = self.client.delete(url)
        self.assertEqual(response.status_code, 403, "Request to delete document non-denied status code for restricted user.")
