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

# This file is part of PGWUI_Develop.
#
# 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 click.testing
import pathlib

from unittest import mock

from pgwui_develop import testing
import pgwui_develop.pgwui as pgwui

# Mock fixtures for module level objects
MockTemplate = testing.make_magicmock_fixture(
    pgwui.mako.template, 'Template')
mock_text_error_template = testing.make_magicmock_fixture(
    pgwui.mako.exceptions, 'text_error_template')
MockTemporaryDirectory = testing.make_magicmock_fixture(
    pgwui.tempfile, 'TemporaryDirectory')
mock_set_extraction_path = testing.instance_method_mock_fixture(
    'set_extraction_path')
mock_resource_filename = testing.instance_method_mock_fixture(
    'resource_filename')
mock_cleanup_resources = testing.instance_method_mock_fixture(
    'cleanup_resources')
mock_scandir = testing.make_magicmock_fixture(
    pgwui.os, 'scandir')


@pytest.fixture()
def MockPath():
    def run():
        return mock.MagicMock(autospec=pathlib.Path)
    return run


# Unit tests

# validate_target()

@pytest.mark.unittest
def test_validate_target_not_exists(capsys, MockPath):
    '''When the target does not exist, nothing happens
    '''
    entry = MockPath()
    entry.exists.return_value = False

    pgwui.validate_target(entry)

    (out, err) = capsys.readouterr()
    assert out == ''
    assert err == ''


@pytest.mark.unittest
def test_validate_target_exists(capsys, MockPath):
    '''When the target exists, SystemExit is raised with a
    value of 1, text is sent to stdout
    '''
    entry = MockPath()
    entry.exists.return_value = True

    with pytest.raises(SystemExit) as execinfo:
        pgwui.validate_target(entry)

    assert execinfo.value.code == 1

    (out, err) = capsys.readouterr()
    assert out == ''
    assert err != ''


mock_validate_target = testing.make_mock_fixture(
    pgwui, 'validate_target')


# transform_name()

@pytest.mark.parametrize(
    ('name', 'expected'), [
        ('anything', 'anything'),
        ('anytemplate', 'anytemplate'),
        ('template', 'template'),
        ('templates', 'templates'),
        ('anyTEMPLATE', 'anySUB'),
        ('anyTEMPLATEany', 'anySUBany')])
@pytest.mark.unittest
def test_transform_name(name, expected):
    '''The expected result is returned
    '''
    result = pgwui.transform_name({'short_name': 'SUB'}, name)

    assert result == expected


mock_transform_name = testing.make_mock_fixture(
    pgwui, 'transform_name')


# renderable()

@pytest.mark.parametrize(
    ('suffix', 'expected'), [
        ('', False),
        ('.ish', False),
        ('.foo', False),
        ('.mak', True),
        ('.makish', True)])
@pytest.mark.unittest
def test_renderable(MockPath, suffix, expected):
    '''The expected result is returned
    '''
    entry = MockPath()
    entry.suffix = suffix

    result = pgwui.renderable(entry)

    assert result == expected


mock_renderable = testing.make_mock_fixture(
    pgwui, 'renderable')


# transform_file_name()

@pytest.mark.parametrize(
    ('suffix', 'renderable_result', 'stem', 'name', 'expected_name'), [
        ('', True, 'stem', 'name', 'stem'),
        ('.ish', False, 'stem', 'name', 'stem'),
        ('', False, 'stem', 'name', 'name')])
@pytest.mark.unittest
def test_transform_file_name(
        MockPath, mock_renderable, mock_transform_name,
        suffix, renderable_result, stem, name, expected_name):
    '''The expected name is passed to transform_name()
    '''
    entry = MockPath()
    entry.suffix = suffix
    entry.name = name
    entry.stem = stem

    mock_renderable.return_value = renderable_result

    pgwui.transform_file_name(None, entry)

    assert mock_transform_name.call_args[0][1] == expected_name


mock_transform_file_name = testing.make_mock_fixture(
    pgwui, 'transform_file_name')


# render()

@pytest.mark.unittest
def test_render_success(capsys, MockTemplate, mock_text_error_template):
    '''When the template is successfully rendered, the result is returned,
    nothing is sent to stderr
    '''
    mock_template = MockTemplate()
    mock_template.render.return_value = 'expected'
    MockTemplate.return_value = mock_template

    result = pgwui.render({}, None, None)

    assert result == 'expected'
    (out, err) = capsys.readouterr()
    assert out == ''
    assert err == ''


@pytest.mark.unittest
def test_render_fail(capsys, MockTemplate, mock_text_error_template):
    '''When the template does not render, the the error template render is
    sent to stderr, and sys exit raised with the right return code
    '''
    MockTemplate.side_effect = Exception

    with pytest.raises(SystemExit) as execinfo:
        pgwui.render({}, None, None)

        assert execinfo.value.code == 1

    (out, err) = capsys.readouterr()
    assert out == ''
    assert err != ''


mock_render = testing.make_mock_fixture(
    pgwui, 'render')


# make_renderable()


@pytest.mark.parametrize(
    ('input', 'expected'), [
        ('% ${foo}\nbar', '% ${foo}\nbar'),
        (' %% ${foo} %%\n bar %% \n', '%${foo} %%\n bar%\n'),
        ('$ {foo}\n $ {bar}\n${baz}\n', '${foo}\n ${bar}\n${baz}\n')])
@pytest.mark.unittest
def test_make_renderable(input, expected):
    '''Produces the expected result
    '''
    result = pgwui.make_renderable(input)

    assert result == expected


mock_make_renderable = testing.make_mock_fixture(
    pgwui, 'make_renderable')


# produce_content()

@pytest.mark.parametrize(
    ('content', 'suffix',
     'renderable', 'render_result', 'make_renderable_result',
     'expected'), [
         ('content', '', False, None, None, 'content'),
         (None, '', True, 'rendered_content', None, 'rendered_content'),
         (None, '.makish', True, None,
          'renderable_content', 'renderable_content')])
@pytest.mark.unittest
def test_produce_content(
        MockPath, mock_renderable, mock_render, mock_make_renderable,
        content, suffix,
        renderable, render_result, make_renderable_result, expected):
    '''The right suffix makes the right calls
    '''
    entry = MockPath()
    entry.suffix = suffix
    entry.read_text.return_value = content

    mock_renderable.return_value = renderable
    mock_render.return_value = render_result
    mock_make_renderable.return_value = make_renderable_result

    result = pgwui.produce_content(None, entry)

    assert result == expected


mock_produce_content = testing.make_mock_fixture(
    pgwui, 'produce_content')


# deliver_file()

@pytest.mark.unittest
def test_deliver_file(
        tmp_path, mock_transform_file_name, mock_produce_content):
    '''The expected file content is written to the expected file
    '''
    test_name = 'test_name'
    test_content = 'some content\n'
    mock_transform_file_name.return_value = test_name
    mock_produce_content.return_value = test_content

    pgwui.deliver_file(tmp_path, None, None)

    assert pathlib.Path(tmp_path / test_name).read_text() == test_content


mock_deliver_file = testing.make_mock_fixture(
    pgwui, 'deliver_file')


# deliver_dir()

@pytest.mark.unittest
def test_deliver_dir(
        tmp_path, MockPath,
        mock_transform_name, mock_scandir, mock_deliver_entry):
    '''Makes the entry directory, calls deliver_entry() for every entry
    returned by os.scandir(), calls transform_name()
    '''
    test_name = 'test_name'
    test_entries = ['a', 'b', 'c']

    mock_entry = MockPath()
    mock_entry.name = test_name

    mock_transform_name.return_value = test_name

    mocked_scandir = mock_scandir(pgwui.os)
    mocked_scandir.__enter__.return_value = test_entries

    pgwui.deliver_dir(tmp_path, {}, mock_entry)

    assert mock_transform_name.call_count == 1
    assert mock_deliver_entry.call_count == len(test_entries)
    assert pathlib.Path(tmp_path / test_name).exists()


mock_deliver_dir = testing.make_mock_fixture(
    pgwui, 'deliver_dir')


# delivery_entry()

@pytest.mark.parametrize(
    ('is_dir', 'dd_calls', 'df_calls'), [
        (True, 1, 0),
        (False, 0, 1)])
@pytest.mark.unittest
def test_deliver_entry(
        tmp_path, mock_deliver_dir, mock_deliver_file,
        is_dir, dd_calls, df_calls):
    '''Calls the right functions
    '''
    if is_dir:
        test_entry = tmp_path
    else:
        test_entry = tmp_path / 'test_file'
        test_entry.touch()

    pgwui.deliver_entry(None, None, test_entry)

    assert mock_deliver_dir.call_count == dd_calls
    assert mock_deliver_file.call_count == df_calls


mock_deliver_entry = testing.make_mock_fixture(
    pgwui, 'deliver_entry')


# traverse_templates()

@pytest.mark.unittest
def test_traverse_templates(MockPath, mock_scandir, mock_deliver_entry):
    '''Makes the target directory, calls deliver_entry for every entry
    returned by os.scandir()
    '''
    test_entries = ['a', 'b', 'c']
    mock_path = MockPath()

    mocked_scandir = mock_scandir(pgwui.os)
    mocked_scandir.__enter__.return_value = test_entries

    pgwui.traverse_templates(mock_path, {}, None)

    assert mock_path.mkdir.call_count == 1
    assert mock_deliver_entry.call_count == len(test_entries)


mock_traverse_templates = testing.make_mock_fixture(
    pgwui, 'traverse_templates')


# deliver_target()

@pytest.mark.unittest
def test_deliver_target(
        MockTemporaryDirectory, mock_set_extraction_path,
        mock_resource_filename, mock_traverse_templates,
        mock_cleanup_resources):
    '''All the mocks are called
    '''
    mocked_set_extraction_path = mock_set_extraction_path(pgwui.pkg_resources)
    mocked_resource_filename = mock_resource_filename(pgwui.pkg_resources)
    mocked_cleanup_resources = mock_cleanup_resources(pgwui.pkg_resources)

    pgwui.deliver_target(None, None)

    assert MockTemporaryDirectory.call_count
    assert mocked_set_extraction_path.call_count
    assert mocked_resource_filename.call_count
    assert mock_traverse_templates.call_count
    assert mocked_cleanup_resources.call_count


mock_deliver_target = testing.make_mock_fixture(
    pgwui, 'deliver_target')


# path_or_default()

@pytest.mark.parametrize(
    ('path', 'expected'), [
        ('/anything', '/anything'),
        (None, '.')])
@pytest.mark.unittest
def test_path_or_default(path, expected):
    '''The expected values are returned
    '''
    result = pgwui.path_or_default(path)

    assert result == expected


mock_path_or_default = testing.make_mock_fixture(
    pgwui, 'path_or_default')


# cookiecutter()

@pytest.mark.unittest
def test_cookiecutter(
        mock_path_or_default, mock_validate_target, mock_deliver_target):
    '''Takes the right arguments, calls the right functions
    '''
    # Care required here, because click actually tests for existance.
    mock_path_or_default.return_value = '.'

    runner = click.testing.CliRunner()
    with runner.isolated_filesystem():
        result = runner.invoke(pgwui.pgwui, [
            'cookiecutter',
            '--component', 'component',
            '--short-name', 'short_name',
            '--summary', 'summary',
            '--author-name', 'author_name',
            '--author-email', 'author_email',
            '.'])  # ignored, mock return value used

    assert mock_path_or_default.call_count == 1
    assert mock_validate_target.call_count == 1
    assert mock_deliver_target.call_count == 1
    assert result.exit_code == 0


mock_cookiecutter = testing.make_mock_fixture(
    pgwui, 'cookiecutter')


# pgwui()

@pytest.mark.unittest
def test_pgwui(mock_cookiecutter):
    '''The cookiecutter is not called when not given on the command line
    '''
    runner = click.testing.CliRunner()
    with runner.isolated_filesystem():
        result = runner.invoke(pgwui.pgwui, [])

    assert result.exit_code == 0
    assert mock_cookiecutter.call_count == 0


# Integration tests

@pytest.mark.integrationtest
def test_cookiecutter_integration(
        tmp_path, mock_deliver_file, mock_deliver_dir):
    '''Execute the code, except that which writes
    '''
    runner = click.testing.CliRunner()
    with runner.isolated_filesystem():
        result = runner.invoke(pgwui.pgwui, [
            'cookiecutter',
            '--component', 'component',
            '--short-name', 'short_name',
            '--summary', 'summary',
            '--author-name', 'author_name',
            '--author-email', 'author_email',
            # Care required here, because click actually tests for existance.
            '.'])

    assert result.exit_code == 0
