import io
from contextlib import redirect_stdout
from pathlib import Path
import tempfile
from unittest import TestCase

from sohstationviewer.view.util.functions import (
    get_soh_messages_for_view, log_str, is_doc_file,
    create_search_results_file, create_table_of_content_file,
    check_chan_wildcards_format, check_masspos
)

from sohstationviewer.view.util.enums import LogType
from sohstationviewer.conf import constants as const
from sohstationviewer.conf.constants import (WF_1ST, WF_2ND, WF_3RD)


class TestGetSOHMessageForView(TestCase):

    def test_no_or_empty_textlog(self):
        soh_msg_channels = {"ACE": ["test1\ntest2", "test3"],
                            "LOG": ["test4"]}
        soh_msg_for_view = {'ACE': ['test1', 'test2', 'test3'],
                            'LOG': ['test4']}

        with self.subTest('test_no_TEXT_str_dataset_key'):
            soh_messages = {"key1": soh_msg_channels}
            ret = get_soh_messages_for_view("key1", soh_messages)
            self.assertNotIn('TEXT', list(ret.keys()))
            self.assertEqual(ret, soh_msg_for_view)

        with self.subTest('test_empty_TEXT_tupple_dataset_key'):
            soh_messages = {"TEXT": [], ("key1", "key2"):  soh_msg_channels}
            ret = get_soh_messages_for_view(("key1", "key2"), soh_messages)
            self.assertNotIn('TEXT', list(ret.keys()))
            self.assertEqual(ret, soh_msg_for_view)

        # no key "TEXT", dataset has no channels
        with self.subTest('test_no_TEXT_no_SOH_channels_for_dataset'):
            soh_messages = {"key1": {}}
            ret = get_soh_messages_for_view("key1", soh_messages)
            self.assertEqual(ret, {})

    def test_some_empty_soh_message(self):
        soh_messages = {"TEXT": ['text1', 'text2\ntext3'],
                        "key1": {"ACE": ["test1\ntest2", "test3"],
                                 "LOG": []}}
        # channel LOG is empty
        ret = get_soh_messages_for_view("key1", soh_messages)
        self.assertEqual(ret,
                         {'TEXT': ['text1', 'text2', 'text3'],
                          'ACE': ['test1', 'test2', 'test3'],
                          'LOG': []})


class TestLogStr(TestCase):
    def test_log_str(self):
        log = ('info line 1', LogType.INFO)
        ret = log_str(log)
        self.assertEqual(ret, 'INFO: info line 1')


class TestIsDocFile(TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        cls.temp_dir = tempfile.TemporaryDirectory()

    def _run_is_doc_file(self, filename, include_table_of_contents=False):
        test_file = Path(self.temp_dir.name).joinpath(filename)
        with open(test_file, 'w'):
            pass
        return is_doc_file(
            test_file, include_table_of_contents=include_table_of_contents)

    def test_not_md_file(self):
        self.assertFalse(self._run_is_doc_file("doc.md"))

    def test_table_of_contents_file(self):
        self.assertFalse(self._run_is_doc_file(
            "./" + const.TABLE_CONTENTS,
            include_table_of_contents=False))

        self.assertTrue(self._run_is_doc_file(
            "./" + const.TABLE_CONTENTS,
            include_table_of_contents=True))

    def test_doc_file(self):
        self.assertTrue(self._run_is_doc_file('doc.help.md'))


class TestCreateSearchResultFile(TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        cls.temp_dir = tempfile.TemporaryDirectory()
        cls.temp_dir_path = Path(cls.temp_dir.name)
        with open(cls.temp_dir_path.joinpath('file1.help.md'), 'w') as file1:
            file1.write('exist1')
        with open(cls.temp_dir_path.joinpath(
                '01 _ file2.help.md'), 'w') as file2:
            file2.write('exist2')
        with open(cls.temp_dir_path.joinpath('file3.md'), 'w') as file3:
            file3.write('exist')
        with open(cls.temp_dir_path.joinpath(const.SEARCH_RESULTS), 'w'):
            pass
        with open(cls.temp_dir_path.joinpath(const.TABLE_CONTENTS), 'w'):
            pass

    def test_search_text_in_no_files(self):
        search_result_filename = create_search_results_file(
            self.temp_dir_path, 'non_exist')
        self.assertEqual(search_result_filename.name, const.SEARCH_RESULTS)
        with open(search_result_filename, 'r') as file:
            content = file.read()
            self.assertEqual(
                content,
                "# Search results\n\nText 'non_exist' not found.")

    def test_search_text_in_one_file(self):
        search_result_filename = create_search_results_file(
            self.temp_dir_path, 'exist1')
        self.assertEqual(search_result_filename.name, const.SEARCH_RESULTS)
        with open(search_result_filename, 'r') as file:
            content = file.read()
            self.assertEqual(
                content,
                "# Search results\n\n"
                "Text 'exist1' found in the following files:\n\n"
                "---------------------------\n\n"
                "+ [file1](file1.help.md)\n\n")

    def test_search_text_in_all_files(self):
        search_result_filename = create_search_results_file(
            self.temp_dir_path, 'exist')
        self.assertEqual(search_result_filename.name, const.SEARCH_RESULTS)
        with open(search_result_filename, 'r') as file:
            content = file.read()
            self.assertEqual(
                content,
                "# Search results\n\n"
                "Text 'exist' found in the following files:\n\n"
                "---------------------------\n\n"
                "+ [file2](01%20_%20file2.help.md)\n\n"
                "+ [file1](file1.help.md)\n\n")

    def test_empty_search_text(self):
        # This case is excluded in help_view
        search_result_filename = create_search_results_file(
            self.temp_dir_path, '')
        self.assertEqual(search_result_filename.name, const.SEARCH_RESULTS)
        with open(search_result_filename, 'r') as file:
            content = file.read()
            self.assertEqual(
                content,
                "# Search results\n\n"
                "Text '' found in the following files:\n\n"
                "---------------------------\n\n"
                "+ [file2](01%20_%20file2.help.md)\n\n"
                "+ [file1](file1.help.md)\n\n")

    def test_no_search_result_file_exist(self):
        search_result_file_path = self.temp_dir_path.joinpath(
            const.SEARCH_RESULTS)
        search_result_file_path.unlink()   # remove file
        search_result_filename = create_search_results_file(
            self.temp_dir_path, 'exist2')
        self.assertEqual(search_result_filename.name, const.SEARCH_RESULTS)
        with open(search_result_filename, 'r') as file:
            content = file.read()
            self.assertEqual(
                content,
                "# Search results\n\n"
                "Text 'exist2' found in the following files:\n\n"
                "---------------------------\n\n"
                "+ [file2](01%20_%20file2.help.md)\n\n")


class TestCreateTableOfContentFile(TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        cls.temp_dir = tempfile.TemporaryDirectory()
        cls.temp_dir_path = Path(cls.temp_dir.name)
        with open(cls.temp_dir_path.joinpath('file1.help.md'), 'w') as file1:
            file1.write('exist1')
        with open(cls.temp_dir_path.joinpath(
                '01 _ file2.help.md'), 'w') as file2:
            file2.write('exist2')
        with open(cls.temp_dir_path.joinpath('file3.md'), 'w') as file3:
            file3.write('exist')
        with open(cls.temp_dir_path.joinpath(const.SEARCH_RESULTS), 'w'):
            pass
        with open(cls.temp_dir_path.joinpath(const.TABLE_CONTENTS), 'w'):
            pass

    def test_create_table_of_contents_file(self):
        table_contents_path = self.temp_dir_path.joinpath(const.TABLE_CONTENTS)
        f = io.StringIO()
        with redirect_stdout(f):
            create_table_of_content_file(self.temp_dir_path)
        output = f.getvalue()
        self.assertEqual(
            f"{table_contents_path.as_posix()} has been created.",
            output.strip())
        self.assertIn(table_contents_path, list(self.temp_dir_path.iterdir()))
        with open(table_contents_path, 'r') as file:
            content = file.read()
            self.assertTrue(content.endswith(
                "# Table of Contents\n\n"
                "+ [Table of Contents](01%20_%20Table%20of%20Contents.help.md)"
                "\n\n"
                "+ [file2](01%20_%20file2.help.md)\n\n"
                "+ [file1](file1.help.md)\n\n",
                )
            )


class TestCheckChanWildcardsFormat(TestCase):
    def test_len1(self):
        with self.subTest("Wildcard is *"):
            wc = '*'
            try:
                check_chan_wildcards_format(wc)
            except Exception:
                self.fail(f"Wildcard '{wc}' raise Exception unexpectedly")
        with self.subTest("Wildcard isn't *"):
            wc = 'L'
            with self.assertRaises(Exception) as context:
                check_chan_wildcards_format(wc)
            self.assertEqual(
                str(context.exception),
                f"Request '{wc}' has length=1 which must be '*'."
            )

    def test_len2(self):
        with self.subTest("Wildcard with first char matched"):
            wc = 'L*'
            try:
                check_chan_wildcards_format(wc)
            except Exception:
                self.fail(f"Wildcard '{wc}' raise Exception unexpectedly")

        with self.subTest("Wildcard with first char not matched"):
            wc = 'W*'
            pattern = f"[{WF_1ST}*]"
            with self.assertRaises(Exception) as context:
                check_chan_wildcards_format(wc)
            self.assertEqual(
                str(context.exception),
                f"Request '{wc}' has first character not match {pattern}."
            )

        with self.subTest("Wildcard with last char matched"):
            wc = '*N'
            try:
                check_chan_wildcards_format(wc)
            except Exception:
                self.fail(f"Wildcard '{wc}' raise Exception unexpectedly")

        with self.subTest("Wildcard with last char not matched"):
            wc = '*L'
            pattern = f"[{WF_3RD}*]"
            with self.assertRaises(Exception) as context:
                check_chan_wildcards_format(wc)
            self.assertEqual(
                str(context.exception),
                f"Request '{wc}' has last character not match {pattern}."
            )

        with self.subTest("Wildcard is **"):
            wc = '**'
            with self.assertRaises(Exception) as context:
                check_chan_wildcards_format(wc)
            self.assertEqual(
                str(context.exception),
                f"Request '{wc}' includes '**' which isn't allowed."
            )

    def test_len3(self):
        with self.subTest("Wildcard with first char matched"):
            wc = 'HL*'
            try:
                check_chan_wildcards_format(wc)
            except Exception:
                self.fail(f"Wildcard '{wc}' raise Exception unexpectedly")

        with self.subTest("Wildcard with first char is a '*'"):
            wc = '*L*'
            try:
                check_chan_wildcards_format(wc)
            except Exception:
                self.fail(f"Wildcard '{wc}' raise Exception unexpectedly")

        with self.subTest("Wildcard with first char not matched"):
            wc = 'WL*'
            pattern = f"[{WF_1ST}*]"
            with self.assertRaises(Exception) as context:
                check_chan_wildcards_format(wc)
            self.assertEqual(
                str(context.exception),
                f"Request '{wc}' has first character not match {pattern}."
            )

        with self.subTest("Wildcard with last char matched"):
            wc = '*LN'
            try:
                check_chan_wildcards_format(wc)
            except Exception:
                self.fail(f"Wildcard '{wc}' raise Exception unexpectedly")

        with self.subTest("Wildcard with last char is a '*'"):
            wc = 'HL*'
            try:
                check_chan_wildcards_format(wc)
            except Exception:
                self.fail(f"Wildcard '{wc}' raise Exception unexpectedly")

        with self.subTest("Wildcard with last char not matched"):
            wc = '*LL'
            pattern = f"[{WF_3RD}*]"
            with self.assertRaises(Exception) as context:
                check_chan_wildcards_format(wc)
            self.assertEqual(
                str(context.exception),
                f"Request '{wc}' has last character not match {pattern}."
            )

        with self.subTest("Wildcard with second char matched"):
            wc = 'HHN'
            try:
                check_chan_wildcards_format(wc)
            except Exception:
                self.fail(f"Wildcard '{wc}' raise Exception unexpectedly")

        with self.subTest("Wildcard with second char is a star"):
            wc = 'H*N'
            try:
                check_chan_wildcards_format(wc)
            except Exception:
                self.fail(f"Wildcard '{wc}' raise Exception unexpectedly")

        with self.subTest("Wildcard with second char not matched"):
            wc = 'HWE'
            pattern = f"[{WF_2ND}*]"
            with self.assertRaises(Exception) as context:
                check_chan_wildcards_format(wc)
            self.assertEqual(
                str(context.exception),
                f"Request '{wc}' has second character not match {pattern}."
            )

        with self.subTest("Wildcard start with '**'"):
            wc = '**E'
            with self.assertRaises(Exception) as context:
                check_chan_wildcards_format(wc)
            self.assertEqual(
                str(context.exception),
                f"Request '{wc}' includes '**' which isn't allowed."
            )

        with self.subTest("Wildcard end with '**'"):
            wc = 'H**'
            with self.assertRaises(Exception) as context:
                check_chan_wildcards_format(wc)
            self.assertEqual(
                str(context.exception),
                f"Request '{wc}' includes '**' which isn't allowed."
            )

    def test_len_gt_3(self):
        wc = 'HHLL'
        with self.assertRaises(Exception) as context:
            check_chan_wildcards_format(wc)
        self.assertEqual(
            str(context.exception),
            f"Request '{wc}' has length={len(wc)} > 3 which isn't allowed."
        )


class TestCheckMassPos(TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        cls.mp_data = {'MP1': {'chan_id': 'MP1', 'samplerate': 1},
                       'MP3': {'chan_id': 'MP3', 'samplerate': 1},
                       'MP4': {'chan_id': 'MP4', 'samplerate': 1}}
        cls.sel_key = '1378'

    def test_include_mp123(self):
        with self.assertRaises(Exception) as context:
            check_masspos(self.mp_data, self.sel_key,
                          include_mp123=True, include_mp456=False)
        self.assertEqual(
            str(context.exception),
            f"Data set {self.sel_key} doesn't include mass position 2")

    def test_include_mp456(self):
        with self.assertRaises(Exception) as context:
            check_masspos(self.mp_data, self.sel_key,
                          include_mp123=False, include_mp456=True)
        self.assertEqual(
            str(context.exception),
            f"Data set {self.sel_key} doesn't include mass position 5,6")

    def test_include_mp123456(self):
        with self.assertRaises(Exception) as context:
            check_masspos(self.mp_data, self.sel_key,
                          include_mp123=True, include_mp456=True)
        self.assertEqual(
            str(context.exception),
            f"Data set {self.sel_key} doesn't include mass position 2,5,6")

    def test_not_include_mp(self):
        try:
            check_masspos(self.mp_data, self.sel_key,
                          include_mp123=False, include_mp456=False)
        except Exception:
            self.fail("check_masspos() raise Exception unexpectedly")