# Copyright (C) 2018, 2020, 2021 The Meme Factory, Inc.
# http://www.karlpinc.com/

# This file is part of PGWUI_Common.
#
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU Affero General Public License
# as published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License along with this program.  If not, see
# <http://www.gnu.org/licenses/>.
#

# Karl O. Pinc <kop@karlpinc.com>

import pytest
import logging
import pyramid.request
from pyramid.threadlocal import get_current_request

import pgwui_common.urls as urls
import pgwui_common.exceptions as common_ex

from pgwui_develop import testing

# Activiate the PGWUI pytest plugin
pytest_plugins = ("pgwui",)

# Mark all tests with "unittest"
pytestmark = pytest.mark.unittest


# Helper functions and constants

FOO_URL = 'foo://bar/'

mock_find_pgwui_components = testing.make_mock_fixture(
    urls, 'find_pgwui_components')

mock_method_route_path = testing.instance_method_mock_fixture('route_path')
mock_route_url = testing.instance_method_mock_fixture('route_url')
mock_include = testing.instance_method_mock_fixture('include')
mock_add_static_view = testing.instance_method_mock_fixture('add_static_view')
mock_add_route = testing.instance_method_mock_fixture('add_route')
mock_add_view = testing.instance_method_mock_fixture('add_view')
mock_static_path = testing.instance_method_mock_fixture('static_path')

mock_request_blank = testing.make_magicmock_fixture(
    pyramid.request, 'Request')


def mock_view(request):
    if (hasattr(request, 'registry')
            and 'pgwui' in request.registry.settings):
        return request.registry.settings
    return {'pgwui': {'foo': FOO_URL}}


def check_base_view_results(request, pgwui):
    assert pgwui['foo'] == FOO_URL


# Unit tests

# route_path()

def test_route_path_with_path(pyramid_request_config, mock_method_route_path):
    '''static_path() result is returned
    '''
    expected = 'route'

    request = get_current_request()
    mocked_route_path = mock_method_route_path(request)
    mocked_route_path.return_value = expected

    result = urls.route_path(request, None, None)

    assert result == expected


def test_route_path_no_path(pyramid_request_config, mock_method_route_path):
    '''BadRouteError() raised when there's no path
    '''
    request = get_current_request()
    mocked_route_path = mock_method_route_path(request)
    mocked_route_path.side_effect = KeyError

    with pytest.raises(common_ex.BadRouteError):
        urls.route_path(request, None, None)

    assert True


mock_route_path = testing.make_mock_fixture(
    urls, 'route_path')


# asset_path()

def test_asset_path_with_path(pyramid_request_config, mock_static_path):
    '''static_path() result is returned
    '''
    expected = 'static'

    request = get_current_request()
    mocked_static_path = mock_static_path(request)
    mocked_static_path.return_value = expected

    result = urls.asset_path(request, None, None)

    assert result == expected


def test_asset_path_no_path(pyramid_request_config, mock_static_path):
    '''BadAssetError() raised when there's no path
    '''
    request = get_current_request()
    mocked_static_path = mock_static_path(request)
    mocked_static_path.side_effect = ValueError

    with pytest.raises(common_ex.BadAssetError):
        urls.asset_path(request, None, None)

    assert True


mock_asset_path = testing.make_mock_fixture(
    urls, 'asset_path')


# url_of_page()

@pytest.mark.parametrize(
    ('pgwui', 'page_name', 'expected'), [
        ({'test_page': {'type': 'URL',
                        'source': 'somesource'}},
         'test_page',
         'somesource'),
        ({'test_page': {'type': 'file',
                        'source': 'somesource'}},
         'test_page',
         'pgwui_common.test_page'),
        ({'test_page': {'type': 'route',
                        'source': 'somesource'}},
         'test_page',
         'routepath'),
        ({'test_page': {'type': 'asset',
                        'source': 'somesource'}},
         'test_page',
         'static'),
        ({'test_page': {'type': 'impossible',
                        'source': 'somesource'}},
         'test_page',
         None)])
def test_url_of_page(
        pyramid_request_config, mock_method_route_path,
        mock_route_path, mock_asset_path, pgwui, page_name, expected):
    '''The right results and calls are made
    '''
    mock_asset_path.return_value = 'static'
    mock_route_path.return_value = 'routepath'

    request = get_current_request()
    mocked_route_path = mock_method_route_path(request)
    mocked_route_path.side_effect = lambda x: x

    request.registry.settings['pgwui'] = pgwui
    result = urls.url_of_page(request, page_name)

    assert result == expected


mock_url_of_page = testing.make_mock_fixture(
    urls, 'url_of_page')


# set_menu_url()

@pytest.mark.parametrize(
    ('test_urls', 'expected_urls'),
    [
        # menu and home have identical urls, no url is added for menu
        ({'pgwui_menu': '/', 'pgwui_home': '/'},
         {}),
        # No menu url, no url is added for menu
        ({'pgwui_home': '/'},
         {}),
        # menu and home have different urls, url is added for menu
        ({'pgwui_menu': '/menu', 'pgwui_home': '/'},
         {'pgwui_menu': '/menu'})])
def test_set_menu_url_good_path(
        pyramid_request_config, mock_method_route_path, mock_url_of_page,
        test_urls, expected_urls):
    '''The expected urls are returned without errors when the path
    settings are good, when the page's assets and routes have paths
    '''
    def path_func(name):
        return test_urls[name]

    mock_url_of_page.side_effect = lambda *args: test_urls['pgwui_menu']
    request = get_current_request()
    mocked_route_path = mock_method_route_path(request)
    mocked_route_path.side_effect = path_func

    urls_dict = {'pgwui_home': test_urls['pgwui_home']}
    expected_urls.update(urls_dict)
    result = urls.set_menu_url(request, urls_dict)

    assert urls_dict == expected_urls
    assert result == []


def test_set_menu_url_bad_page(
        pyramid_request_config, mock_method_route_path, mock_url_of_page):
    '''The expected urls are returned with errors  when the page settings
    are bad, when the page's asset or route has no path
    '''
    expected_urls = {'home_page': '/', 'pgwui_logout': '/logout'}

    mock_url_of_page.side_effect = common_ex.BadPathError('example error')
    request = get_current_request
    new_urls = expected_urls.copy()
    result = urls.set_menu_url(request, new_urls)

    assert new_urls == expected_urls
    assert len(result) == 1
    assert isinstance(result[0], common_ex.BadPathError)


mock_set_menu_url = testing.make_mock_fixture(
    urls, 'set_menu_url')


# set_component_urls()

@pytest.mark.parametrize(
    'test_urls', [
        # With a pgwui_menu
        {'pgwui_menu': '/menu',
         'pgwui_logout': '/logout',
         'pgwui_foo': '/foo',
         'pgwui_home': '/'},
        # Without a pgwui_menu
        {'pgwui_logout': '/logout',
         'pgwui_foo': '/foo',
         'pgwui_home': '/'}])
def test_set_component_urls(
        pyramid_request_config, mock_method_route_path, mock_set_menu_url,
        mock_find_pgwui_components, test_urls):
    '''Urls are set for every component which has a route, except for
    pgwui_menu, expected errors are returned
    '''
    test_errors = ['some error']
    test_components = list(test_urls) + ['pgwui_noroute']

    def url_func(url):
        return test_urls[url]

    mock_set_menu_url.return_value = test_errors
    request = get_current_request()
    mocked_route_path = mock_method_route_path(request)
    mocked_route_path.side_effect = url_func
    mock_find_pgwui_components.return_value = test_components

    urls_dict = dict()
    result = urls.set_component_urls(request, urls_dict)

    expected_urls = test_urls.copy()
    if 'pgwui_menu' in expected_urls:
        del expected_urls['pgwui_menu']

    mock_set_menu_url.assert_called_once()
    assert urls_dict == expected_urls
    assert result == test_errors


mock_set_component_urls = testing.make_mock_fixture(
    urls, 'set_component_urls')


# set_urls()

def test_set_urls_good_path(
        pyramid_request_config, mock_url_of_page, mock_set_component_urls):
    '''When the 'home_page' route path is good the 'home' url is added,
    set_component_urls() called, and the expected errors are returned
    '''
    component_errors = ['some error']
    test_home_route = '/'
    request = get_current_request()

    mock_set_component_urls.return_value = component_errors
    mock_url_of_page.return_value = test_home_route

    urls_dict = dict()
    result = urls.set_urls(request, urls_dict)

    assert urls_dict['pgwui_home'] == test_home_route
    mock_set_component_urls.assert_called_once()
    assert result == component_errors


def test_set_urls_bad_path(
        pyramid_request_config, mock_url_of_page, mock_set_component_urls):
    '''When the 'home_page' route is bad a bad path error is one of the errors
    returned
    '''
    component_errors = ['some error']
    home_error = common_ex.BadPathError('example bad path error')
    request = get_current_request()

    mock_url_of_page.side_effect = home_error
    mock_set_component_urls.return_value = component_errors

    urls_dict = dict()
    result = urls.set_urls(request, urls_dict)

    assert 'pgwui_home' in urls_dict
    mock_set_component_urls.assert_called_once()
    assert home_error in result


mock_set_urls = testing.make_mock_fixture(
    urls, 'set_urls')


# add_urls_setting()

def test_add_urls_setting(
        caplog, pyramid_request_config, mock_request_blank, mock_set_urls):
    '''Request.blank() and set_urls() are called, the urls set
    are put in the pgwui dict in the settings
    '''
    caplog.set_level(logging.DEBUG)
    expected_errors = ['some error']
    expected_urls = {'key1': 'val1', 'key2': 'val2'}

    def set_urls(request, urls):
        urls.update(expected_urls)
        return expected_errors

    mock_set_urls.side_effect = set_urls

    request_settings = {'pgwui': {'urls': expected_urls}}
    request = get_current_request()
    request.registry.settings = request_settings

    settings = {'pgwui': {}}
    result = urls.add_urls_setting(pyramid_request_config, settings)

    mock_request_blank.blank.assert_called_once()
    mock_set_urls.assert_called_once()
    assert settings['pgwui']['urls'] == expected_urls

    assert result == expected_errors

    logs = caplog.record_tuples
    assert len(logs) == 1
    assert logs[0][1] == logging.DEBUG
