# type: ignore
# As done in parser_obj.py, this module is not used for now, so we just skip
# type checking for the sake of simplicity.
""" Tests for functions and classes in parser_obj.py """

import sys
import unittest
from unittest import mock

import pytest

from certbot_nginx._internal.parser_obj import COMMENT_BLOCK
from certbot_nginx._internal.parser_obj import parse_raw


class CommentHelpersTest(unittest.TestCase):
    def test_is_comment(self):
        from certbot_nginx._internal.parser_obj import _is_comment
        assert _is_comment(parse_raw(['#']))
        assert _is_comment(parse_raw(['#', ' literally anything else']))
        assert not _is_comment(parse_raw(['not', 'even', 'a', 'comment']))

    def test_is_certbot_comment(self):
        from certbot_nginx._internal.parser_obj import _is_certbot_comment
        assert _is_certbot_comment(
            parse_raw(COMMENT_BLOCK))
        assert not _is_certbot_comment(
            parse_raw(['#', ' not a certbot comment']))
        assert not _is_certbot_comment(
            parse_raw(['#', ' managed by Certbot', ' also not a certbot comment']))
        assert not _is_certbot_comment(
            parse_raw(['not', 'even', 'a', 'comment']))

    def test_certbot_comment(self):
        from certbot_nginx._internal.parser_obj import _certbot_comment
        from certbot_nginx._internal.parser_obj import _is_certbot_comment
        comment = _certbot_comment(None)
        assert _is_certbot_comment(comment)
        assert comment.dump() == COMMENT_BLOCK
        assert comment.dump(True) == ['    '] + COMMENT_BLOCK
        assert _certbot_comment(None, 2).dump(True) == ['  '] + COMMENT_BLOCK


class ParsingHooksTest(unittest.TestCase):
    def test_is_sentence(self):
        from certbot_nginx._internal.parser_obj import Sentence
        assert not Sentence.should_parse([])
        assert Sentence.should_parse([''])
        assert Sentence.should_parse(['word'])
        assert Sentence.should_parse(['two', 'words'])
        assert not Sentence.should_parse([[]])
        assert not Sentence.should_parse(['word', []])

    def test_is_block(self):
        from certbot_nginx._internal.parser_obj import Block
        assert not Block.should_parse([])
        assert not Block.should_parse([''])
        assert not Block.should_parse(['two', 'words'])
        assert not Block.should_parse([[[]], []])
        assert not Block.should_parse([['block_name'], ['hi', []], []])
        assert not Block.should_parse([['block_name'], 'lol'])
        assert Block.should_parse([['block_name'], ['hi', []]])
        assert Block.should_parse([['hello'], []])
        assert Block.should_parse([['block_name'], [['many'], ['statements'], 'here']])
        assert Block.should_parse([['if', ' ', '(whatever)'], ['hi']])

    @mock.patch("certbot_nginx._internal.parser_obj.Parsable.parsing_hooks")
    def test_parse_raw(self, parsing_hooks):
        fake_parser1 = mock.Mock()
        fake_parser1.should_parse = lambda x: True
        fake_parser2 = mock.Mock()
        fake_parser2.should_parse = lambda x: True
        parsing_hooks.return_value = (fake_parser1, fake_parser2,)
        # First encountered "match" should parse.
        parse_raw([])
        fake_parser1().parse.assert_called_once()
        fake_parser2().parse.assert_not_called()
        fake_parser1.reset_mock()
        # "match" that returns False shouldn't parse.
        fake_parser1.should_parse = lambda x: False
        parse_raw([])
        fake_parser1().parse.assert_not_called()
        fake_parser2().parse.assert_called_once()

    @mock.patch("certbot_nginx._internal.parser_obj.Parsable.parsing_hooks")
    def test_parse_raw_no_match(self, parsing_hooks):
        from certbot import errors
        fake_parser1 = mock.Mock()
        fake_parser1.should_parse = lambda x: False
        parsing_hooks.return_value = (fake_parser1,)
        with pytest.raises(errors.MisconfigurationError):
            parse_raw([])
        parsing_hooks.return_value = ()
        with pytest.raises(errors.MisconfigurationError):
            parse_raw([])

    @mock.patch("certbot_nginx._internal.parser_obj.Parsable.parsing_hooks")
    def test_parse_raw_passes_add_spaces(self, parsing_hooks):
        fake_parser1 = mock.Mock()
        fake_parser1.should_parse = lambda x: True
        parsing_hooks.return_value = (fake_parser1,)
        parse_raw([])
        fake_parser1().parse.assert_called_with([], False)
        parse_raw([], add_spaces=True)
        fake_parser1().parse.assert_called_with([], True)


class SentenceTest(unittest.TestCase):
    def setUp(self):
        from certbot_nginx._internal.parser_obj import Sentence
        self.sentence = Sentence(None)

    def test_parse_bad_sentence_raises_error(self):
        from certbot import errors
        with pytest.raises(errors.MisconfigurationError):
            self.sentence.parse('lol')
        with pytest.raises(errors.MisconfigurationError):
            self.sentence.parse([[]])
        with pytest.raises(errors.MisconfigurationError):
            self.sentence.parse([5])

    def test_parse_sentence_words_hides_spaces(self):
        og_sentence = ['\r\n', 'hello', ' ', ' ', '\t\n  ', 'lol', ' ', 'spaces']
        self.sentence.parse(og_sentence)
        assert self.sentence.words == ['hello', 'lol', 'spaces']
        assert self.sentence.dump() == ['hello', 'lol', 'spaces']
        assert self.sentence.dump(True) == og_sentence

    def test_parse_sentence_with_add_spaces(self):
        self.sentence.parse(['hi', 'there'], add_spaces=True)
        assert self.sentence.dump(True) == ['hi', ' ', 'there']
        self.sentence.parse(['one', ' ', 'space', 'none'], add_spaces=True)
        assert self.sentence.dump(True) == ['one', ' ', 'space', ' ', 'none']

    def test_iterate(self):
        expected = [['1', '2', '3']]
        self.sentence.parse(['1', ' ', '2', ' ', '3'])
        for i, sentence in enumerate(self.sentence.iterate()):
            assert sentence.dump() == expected[i]

    def test_set_tabs(self):
        self.sentence.parse(['tabs', 'pls'], add_spaces=True)
        self.sentence.set_tabs()
        assert self.sentence.dump(True)[0] == '\n    '
        self.sentence.parse(['tabs', 'pls'], add_spaces=True)

    def test_get_tabs(self):
        self.sentence.parse(['no', 'tabs'])
        assert self.sentence.get_tabs() == ''
        self.sentence.parse(['\n \n  ', 'tabs'])
        assert self.sentence.get_tabs() == '  '
        self.sentence.parse(['\n\t  ', 'tabs'])
        assert self.sentence.get_tabs() == '\t  '
        self.sentence.parse(['\n\t \n', 'tabs'])
        assert self.sentence.get_tabs() == ''


class BlockTest(unittest.TestCase):
    def setUp(self):
        from certbot_nginx._internal.parser_obj import Block
        self.bloc = Block(None)
        self.name = ['server', 'name']
        self.contents = [['thing', '1'], ['thing', '2'], ['another', 'one']]
        self.bloc.parse([self.name, self.contents])

    def test_iterate(self):
        # Iterates itself normally
        assert self.bloc == next(self.bloc.iterate())
        # Iterates contents while expanded
        expected = [self.bloc.dump()] + self.contents
        for i, elem in enumerate(self.bloc.iterate(expanded=True)):
            assert expected[i] == elem.dump()

    def test_iterate_match(self):
        # can match on contents while expanded
        from certbot_nginx._internal.parser_obj import Block
        from certbot_nginx._internal.parser_obj import Sentence
        expected = [['thing', '1'], ['thing', '2']]
        for i, elem in enumerate(self.bloc.iterate(expanded=True,
            match=lambda x: isinstance(x, Sentence) and 'thing' in x.words)):
            assert expected[i] == elem.dump()
        # can match on self
        assert self.bloc == next(self.bloc.iterate(
            expanded=True,
            match=lambda x: isinstance(x, Block) and 'server' in x.names))

    def test_parse_with_added_spaces(self):
        import copy
        self.bloc.parse([copy.copy(self.name), self.contents], add_spaces=True)
        assert self.bloc.dump() == [self.name, self.contents]
        assert self.bloc.dump(True) == [
            ['server', ' ', 'name', ' '],
            [['thing', ' ', '1'],
             ['thing', ' ', '2'],
             ['another', ' ', 'one']]]

    def test_bad_parse_raises_error(self):
        from certbot import errors
        with pytest.raises(errors.MisconfigurationError):
            self.bloc.parse([[[]], [[]]])
        with pytest.raises(errors.MisconfigurationError):
            self.bloc.parse(['lol'])
        with pytest.raises(errors.MisconfigurationError):
            self.bloc.parse(['fake', 'news'])

    def test_set_tabs(self):
        self.bloc.set_tabs()
        assert self.bloc.names.dump(True)[0] == '\n    '
        for elem in self.bloc.contents.dump(True)[:-1]:
            assert elem[0] == '\n        '
        assert self.bloc.contents.dump(True)[-1][0] == '\n'

    def test_get_tabs(self):
        self.bloc.parse([[' \n  \t', 'lol'], []])
        assert self.bloc.get_tabs() == '  \t'

class StatementsTest(unittest.TestCase):
    def setUp(self):
        from certbot_nginx._internal.parser_obj import Statements
        self.statements = Statements(None)
        self.raw = [
            ['sentence', 'one'],
            ['sentence', 'two'],
            ['and', 'another']
        ]
        self.raw_spaced = [
            ['\n  ', 'sentence', ' ', 'one'],
            ['\n  ', 'sentence', ' ', 'two'],
            ['\n  ', 'and', ' ', 'another'],
            '\n\n'
        ]

    def test_set_tabs(self):
        self.statements.parse(self.raw)
        self.statements.set_tabs()
        for statement in self.statements.iterate():
            assert statement.dump(True)[0] == '\n    '

    def test_set_tabs_with_parent(self):
        # Trailing whitespace should inherit from parent tabbing.
        self.statements.parse(self.raw)
        self.statements.parent = mock.Mock()
        self.statements.parent.get_tabs.return_value = '\t\t'
        self.statements.set_tabs()
        for statement in self.statements.iterate():
            assert statement.dump(True)[0] == '\n    '
        assert self.statements.dump(True)[-1] == '\n\t\t'

    def test_get_tabs(self):
        self.raw[0].insert(0, '\n \n  \t')
        self.statements.parse(self.raw)
        assert self.statements.get_tabs() == '  \t'
        self.statements.parse([])
        assert self.statements.get_tabs() == ''

    def test_parse_with_added_spaces(self):
        self.statements.parse(self.raw, add_spaces=True)
        assert self.statements.dump(True)[0] == ['sentence', ' ', 'one']

    def test_parse_bad_list_raises_error(self):
        from certbot import errors
        with pytest.raises(errors.MisconfigurationError):
            self.statements.parse('lol not a list')

    def test_parse_hides_trailing_whitespace(self):
        self.statements.parse(self.raw + ['\n\n  '])
        assert isinstance(self.statements.dump()[-1], list)
        assert self.statements.dump(True)[-1].isspace() is True
        assert self.statements.dump(True)[-1] == '\n\n  '

    def test_iterate(self):
        self.statements.parse(self.raw)
        expected = [['sentence', 'one'], ['sentence', 'two']]
        for i, elem in enumerate(self.statements.iterate(match=lambda x: 'sentence' in x)):
            assert expected[i] == elem.dump()


if __name__ == "__main__":
    sys.exit(pytest.main(sys.argv[1:] + [__file__]))  # pragma: no cover
