from unittest import TestCase

from pcs_test.tools import fixture
from pcs_test.tools.assertions import assert_report_item_list_equal

from pcs.common import report_codes
from pcs.lib.interface.config import ParserErrorException
from pcs.lib.booth import config_parser
from pcs.lib.booth.config_parser import ConfigItem


class BuildTest(TestCase):
    def test_build_file_content_from_parsed_structure(self):
        self.assertEqual(
            "\n".join([
                "authfile = /path/to/auth.file",
                "site = 1.1.1.1",
                "site = 2.2.2.2",
                "arbitrator = 3.3.3.3",
                'ticket = "TA"',
                'ticket = "TB"',
                "  timeout = 10",
                "", #newline at the end
            ]).encode("utf-8"),
            config_parser.Exporter.export([
                ConfigItem("authfile", "/path/to/auth.file"),
                ConfigItem("site", "1.1.1.1"),
                ConfigItem("site", "2.2.2.2"),
                ConfigItem("arbitrator", "3.3.3.3"),
                ConfigItem("ticket", "TA"),
                ConfigItem("ticket", "TB", [
                    ConfigItem("timeout", "10")
                ]),
            ])
        )


class OrganizeLinesTest(TestCase):
    def test_move_non_ticket_config_keys_above_tickets(self):
        self.assertEqual(
            [
                ConfigItem("site", "1.1.1.1"),
                ConfigItem('site', '2.2.2.2'),
                ConfigItem('arbitrator', '3.3.3.3'),
                ConfigItem("ticket", "TA"),
            ],
            # testing a function which should not be used outside of the module
            # pylint: disable=protected-access
            config_parser._organize_lines([
                ("site", "1.1.1.1"),
                ("ticket", "TA"),
                ('site', '2.2.2.2'),
                ('arbitrator', '3.3.3.3'),
            ])
        )

    def test_use_ticket_key_as_ticket_detail(self):
        # pylint: disable=invalid-name
        self.maxDiff = None
        self.assertEqual(
            [
                ConfigItem("site", "1.1.1.1"),
                ConfigItem('expire', '300'),
                ConfigItem('site', '2.2.2.2'),
                ConfigItem('arbitrator', '3.3.3.3'),
                ConfigItem("ticket", "TA", [
                    ConfigItem("timeout", "10"),
                    ConfigItem('--nonexistent', 'value'),
                    ConfigItem("expire", "300"),
                ]),
                ConfigItem("ticket", "TB", [
                    ConfigItem("timeout", "20"),
                    ConfigItem("renewal-freq", "40"),
                ]),
            ],
            # testing a function which should not be used outside of the module
            # pylint: disable=protected-access
            config_parser._organize_lines([
                ("site", "1.1.1.1"),
                ("expire", "300"), # out of ticket content is kept global
                ("ticket", "TA"),
                ("site", "2.2.2.2"), # move to global
                ("timeout", "10"),
                ("--nonexistent", "value"), # no global is kept under ticket
                ("expire", "300"),
                ("ticket", "TB"),
                ('arbitrator', '3.3.3.3'),
                ("timeout", "20"),
                ("renewal-freq", "40"),
            ])
        )


class ParseRawLinesTest(TestCase):
    def test_parse_simple_correct_lines(self):
        self.assertEqual(
            [
                ("site", "1.1.1.1"),
                ('site', '2.2.2.2'),
                ('arbitrator', '3.3.3.3'),
                ('syntactically_correct', 'nonsense'),
                ('line-with', 'hash#literal'),
            ],
            # testing a function which should not be used outside of the module
            # pylint: disable=protected-access
            config_parser._parse_to_raw_lines("\n".join([
                "site = 1.1.1.1",
                " site  =  2.2.2.2 ",
                "arbitrator=3.3.3.3",
                "syntactically_correct = nonsense",
                "line-with = hash#literal",
                "",
            ]))
        )

    def test_parse_lines_with_whole_line_comment(self):
        self.assertEqual(
            [("site", "1.1.1.1")],
            # testing a function which should not be used outside of the module
            # pylint: disable=protected-access
            config_parser._parse_to_raw_lines("\n".join([
                " # some comment",
                "site = 1.1.1.1",
            ]))
       )

    def test_skip_empty_lines(self):
        self.assertEqual(
            [("site", "1.1.1.1")],
            # testing a function which should not be used outside of the module
            # pylint: disable=protected-access
            config_parser._parse_to_raw_lines("\n".join([
                " ",
                "site = 1.1.1.1",
            ]))
       )

    def test_raises_when_unexpected_lines_appear(self):
        invalid_line_list = [
            "first invalid line",
            "second = 'invalid line' something else #comment",
            "third = 'invalid line 'something#'#",
        ]
        line_list = ["site = 1.1.1.1"] + invalid_line_list
        with self.assertRaises(config_parser.InvalidLines) as context_manager:
            # testing a function which should not be used outside of the module
            # pylint: disable=protected-access
            config_parser._parse_to_raw_lines("\n".join(line_list))
        self.assertEqual(context_manager.exception.args[0], invalid_line_list)

    def test_parse_lines_finishing_with_comment(self):
        self.assertEqual(
            [("site", "1.1.1.1")],
            # testing a function which should not be used outside of the module
            # pylint: disable=protected-access
            config_parser._parse_to_raw_lines("\n".join([
                "site = '1.1.1.1' #comment",
            ]))
       )

class ParseTest(TestCase):
    def test_raises_when_invalid_lines_appear(self):
        invalid_line_list = [
            "first invalid line",
            "second = 'invalid line' something else #comment"
        ]
        line_list = ["site = 1.1.1.1"] + invalid_line_list

        with self.assertRaises(ParserErrorException) as cm:
            config_parser.Parser.parse("\n".join(line_list).encode("utf-8"))

        assert_report_item_list_equal(
            config_parser.Parser.exception_to_report_list(
                cm.exception, "does not matter", "path",
                None, False
            ),
            [
                fixture.error(
                    report_codes.BOOTH_CONFIG_UNEXPECTED_LINES,
                    line_list=invalid_line_list,
                    file_path="path",
                ),
            ]
        )
        assert_report_item_list_equal(
            config_parser.Parser.exception_to_report_list(
                cm.exception, "does not matter", "path",
                report_codes.FORCE_BOOTH_DESTROY, False
            ),
            [
                fixture.error(
                    report_codes.BOOTH_CONFIG_UNEXPECTED_LINES,
                    force_code=report_codes.FORCE_BOOTH_DESTROY,
                    line_list=invalid_line_list,
                    file_path="path",
                ),
            ]
        )
        assert_report_item_list_equal(
            config_parser.Parser.exception_to_report_list(
                cm.exception, "does not matter", "path",
                report_codes.FORCE_BOOTH_DESTROY, True
            ),
            [
                fixture.warn(
                    report_codes.BOOTH_CONFIG_UNEXPECTED_LINES,
                    line_list=invalid_line_list,
                    file_path="path",
                ),
            ]
        )

    def test_do_not_raises_when_no_invalid_liens_there(self):
        parsed = config_parser.Parser.parse("site = 1.1.1.1".encode("utf-8"))
        self.assertEqual(len(parsed), 1)
        self.assertEqual(parsed[0].key, "site")
        self.assertEqual(parsed[0].value, "1.1.1.1")
        self.assertEqual(parsed[0].details, [])
