# SPDX-License-Identifier: AGPL-3.0-or-later
"""
Tests for calibre views.
"""

from unittest.mock import call, patch

import pytest
from django import urls
from django.contrib.messages.storage.fallback import FallbackStorage
from django.http.response import Http404

from plinth import actions, module_loader
from plinth.modules.calibre import views

# For all tests, use plinth.urls instead of urls configured for testing
pytestmark = pytest.mark.urls('plinth.urls')


@pytest.fixture(autouse=True, scope='module')
def fixture_calibre_urls():
    """Make sure calibre app's URLs are part of plinth.urls."""
    with patch('plinth.module_loader._modules_to_load', new=[]) as modules, \
            patch('plinth.urls.urlpatterns', new=[]):
        modules.append('plinth.modules.calibre')
        module_loader.include_urls()
        yield


@pytest.fixture(autouse=True)
def calibre_patch():
    """Patch calibre methods."""
    with patch('plinth.modules.calibre.list_libraries') as list_libraries:
        list_libraries.return_value = ['TestExistingLibrary']

        yield


def make_request(request, view, **kwargs):
    """Make request with a message storage."""
    setattr(request, 'session', 'session')
    messages = FallbackStorage(request)
    setattr(request, '_messages', messages)
    response = view(request, **kwargs)

    return response, messages


@patch('plinth.modules.calibre.create_library')
def test_create_library(create_library, rf):
    """Test that create library view works."""
    form_data = {'calibre-name': 'TestLibrary'}
    request = rf.post(urls.reverse('calibre:create-library'), data=form_data)
    view = views.CreateLibraryView.as_view()
    response, messages = make_request(request, view)

    assert response.status_code == 302
    assert response.url == urls.reverse('calibre:index')
    assert list(messages)[0].message == 'Library created.'
    create_library.assert_has_calls([call('TestLibrary')])


@patch('plinth.modules.calibre.create_library')
def test_create_library_failed(create_library, rf):
    """Test that create library fails as expected."""
    create_library.side_effect = actions.ActionError('calibre', 'TestOutput',
                                                     'TestError')
    form_data = {'calibre-name': 'TestLibrary'}
    request = rf.post(urls.reverse('calibre:create-library'), data=form_data)
    view = views.CreateLibraryView.as_view()
    response, messages = make_request(request, view)

    assert response.status_code == 302
    assert response.url == urls.reverse('calibre:index')
    assert list(messages)[0].message == \
        'An error occurred while creating the library. TestError'


def test_create_library_existing_library(rf):
    """Test that create library errors out for an existing library name."""
    form_data = {'calibre-name': 'TestExistingLibrary'}
    request = rf.post(urls.reverse('calibre:create-library'), data=form_data)
    view = views.CreateLibraryView.as_view()
    response, _ = make_request(request, view)

    assert response.context_data['form'].errors['name'][
        0] == 'A library with this name already exists.'
    assert response.status_code == 200


def test_create_library_invalid_name(rf):
    """Test that create library errors out for invalid name."""
    form_data = {'calibre-name': 'Invalid Library'}
    request = rf.post(urls.reverse('calibre:create-library'), data=form_data)
    view = views.CreateLibraryView.as_view()
    response, _ = make_request(request, view)

    assert response.context_data['form'].errors['name'][
        0] == 'Enter a valid value.'
    assert response.status_code == 200


@patch('plinth.modules.calibre.app')
def test_delete_library_confirmation_view(_app, rf):
    """Test that deleting library confirmation shows correct name."""
    response, _ = make_request(rf.get(''), views.delete_library,
                               name='TestExistingLibrary')
    assert response.status_code == 200
    assert response.context_data['name'] == 'TestExistingLibrary'


@patch('plinth.modules.calibre.delete_library')
@patch('plinth.modules.calibre.app')
def test_delete_library(_app, delete_library, rf):
    """Test that deleting a library works."""
    response, messages = make_request(rf.post(''), views.delete_library,
                                      name='TestExistingLibrary')
    assert response.status_code == 302
    assert response.url == urls.reverse('calibre:index')
    assert list(messages)[0].message == 'TestExistingLibrary deleted.'
    delete_library.assert_has_calls([call('TestExistingLibrary')])


@patch('plinth.modules.calibre.delete_library')
def test_delete_library_error(delete_library, rf):
    """Test that deleting a library shows error when operation fails."""
    delete_library.side_effect = actions.ActionError('calibre', 'TestInput',
                                                     'TestError')
    response, messages = make_request(rf.post(''), views.delete_library,
                                      name='TestExistingLibrary')
    assert response.status_code == 302
    assert response.url == urls.reverse('calibre:index')
    assert list(messages)[0].message == \
        'Could not delete TestExistingLibrary: '\
        "('calibre', 'TestInput', 'TestError')"


def test_delete_library_non_existing(rf):
    """Test that deleting a library shows error when operation fails."""
    with pytest.raises(Http404):
        make_request(rf.post(''), views.delete_library,
                     name='TestNonExistingLibrary')

    with pytest.raises(Http404):
        make_request(rf.get(''), views.delete_library,
                     name='TestNonExistingLibrary')
