import io

import pytest

from translate.storage import mozilla_lang

from . import test_base


@pytest.mark.parametrize(
    ("orig", "stripped"),
    [
        ("", ""),
        ("String", "String"),  # No {ok}
        ("String {ok}", "String"),  # correct form
        ("String {OK}", "String"),  # capitals
        ("Şŧřīƞɠ {ok}", "Şŧřīƞɠ"),  # Unicode
        ("String{ok}", "String"),  # No leading space
        ("String{OK}", "String"),  # Caps no leading space
        ("String  {ok}", "String"),  # multispace leading
        ("String {ok} ", "String"),  # trailing space
    ],
)
def test_strip_ok(orig, stripped) -> None:
    """Test various permutations of {ok} stripping."""
    assert mozilla_lang.strip_ok(orig) == stripped


class TestMozLangUnit(test_base.TestTranslationUnit):
    UnitClass = mozilla_lang.LangUnit

    def test_translate_but_same(self) -> None:
        """
        Mozilla allows {ok} to indicate a line that is the
        same in source and target on purpose.
        """
        unit = self.UnitClass("Open")
        unit.target = "Open"
        assert unit.target == "Open"
        assert str(unit).endswith(" {ok}")

    def test_untranslated(self) -> None:
        """
        The target is always written to files and is never blank. If it is
        truly untranslated then it won't end with '{ok}.
        """
        unit = self.UnitClass("Open")
        assert unit.target is None
        assert str(unit).find("Open") == 1
        assert str(unit).find("Open", 2) == 6
        assert not str(unit).endswith(" {ok}")

        unit = self.UnitClass("Closed")
        unit.target = ""
        assert unit.target == ""
        assert str(unit).find("Closed") == 1
        assert str(unit).find("Closed", 2) == 8
        assert not str(unit).endswith(" {ok}")

    def test_comments(self) -> None:
        """Comments start with #, tags start with ## TAG:."""
        unit = self.UnitClass("One")
        unit.addnote("Hello")
        assert str(unit).find("Hello") == 2
        assert str(unit).find("# Hello") == 0
        unit.addnote("# TAG: goodbye")
        assert "# TAG: goodbye" in unit.getnotes(origin="developer").split("\n")

    def test_copy_target(self) -> None:
        """
        Validate that self.rawtarget does not break a valid translation.

        self.rawtarget is used to preserve strange format anomalies related to
        {ok}.  But when units got translated it sometimes caused issues, in
        that the unit was correctly translated but when serialised it used
        self.rawtarget and thus output an untranslated unit.  This checks that
        translation actually results in rawformat being ignored.
        """
        unit = self.UnitClass("Device")
        assert unit.target is None
        unit.rawtarget = "Device"
        assert not unit.istranslated()
        assert str(unit) == ";Device\nDevice"

        other = self.UnitClass("Device")
        other.target = "Device"
        assert other.istranslated()
        assert str(other) == ";Device\nDevice {ok}"

        unit.target = other.target
        assert unit.istranslated()
        assert unit.target == "Device"
        assert str(unit) == ";Device\nDevice {ok}"


class TestMozLangFile(test_base.TestTranslationStore):
    StoreClass = mozilla_lang.LangStore

    def test_nonascii(self) -> None:
        # FIXME investigate why this doesn't pass or why we even do this
        # text with UTF-8 encoded strings
        pass

    def test_format_layout(self) -> None:
        """General test of layout of the format."""
        lang = "# Comment\n;Source\nTarget\n\n\n"
        store = self.StoreClass.parsestring(lang)
        store.mark_active = False
        unit = store.units[0]
        assert unit.source == "Source"
        assert unit.target == "Target"
        assert "Comment" in unit.getnotes()
        assert bytes(store).decode("utf-8") == lang

    def test_crlf(self) -> None:
        r"""While \n is preferred \r\n is allowed."""
        lang = "# Comment\r\n;Source\r\nTarget\r\n\r\n\r\n"
        store = self.StoreClass.parsestring(lang)
        store.mark_active = False
        unit = store.units[0]
        assert unit.source == "Source"
        assert unit.target == "Target"
        assert "Comment" in unit.getnotes()
        assert bytes(store).decode("utf-8") == lang

    def test_active_flag(self) -> None:
        """Test the ## active ## flag."""
        lang = "## active ##\n;Source\nTarget\n\n\n"
        store = self.StoreClass.parsestring(lang)
        assert store.is_active
        assert bytes(store).decode("utf-8") == lang

    def test_multiline_comments(self) -> None:
        """Ensure we can handle and preserve multiline comments."""
        lang = (
            "## active ##\n"
            "# First comment\n"
            "# Second comment\n"
            "# Third comment\n"
            ";Source\n"
            "Target\n"
            "\n\n"
        )
        store = self.StoreClass.parsestring(lang)
        assert bytes(store).decode("utf-8") == lang

    def test_template(self) -> None:
        """A template should have source == target, though it could be blank."""
        lang = ";Source\nSource\n\n\n"
        store = self.StoreClass.parsestring(lang)
        unit = store.units[0]
        assert unit.source == "Source"
        assert unit.target == ""
        assert bytes(store).decode("utf-8") == lang
        lang2 = ";Source\n\n\n;Source2\n\n\n"
        store2 = self.StoreClass.parsestring(lang2)
        assert store2.units[0].source == "Source"
        assert store2.units[0].target == ""
        assert store2.units[1].source == "Source2"
        assert store2.units[1].target == ""

    @pytest.mark.parametrize(
        ("ok", "target", "istranslated"),
        [
            ("", "", False),  # Untranslated, no {ok}
            (" ", "Source ", True),  # Excess whitespace, translated
            (" {ok}", "Source", True),  # Valid {ok}
            (" {ok} ", "Source", True),  # {ok} trailing WS
            ("{ok}", "Source", True),  # {ok} no WS
        ],
    )
    def test_ok_translations(self, ok, target, istranslated) -> None:
        """Various renderings of {ok} to ensure that we parse it correctly."""
        lang = ";Source\nSource%s\n"
        store = self.StoreClass.parsestring(lang % ok)
        unit = store.units[0]
        assert unit.source == "Source"
        assert unit.target == target
        assert unit.istranslated() == istranslated

    def test_headers(self) -> None:
        """Ensure we can handle and preserve file headers."""
        lang = (
            "## active ##\n"
            "## some_tag ##\n"
            "## another_tag ##\n"
            "## NOTE: foo\n"
            "\n\n"
            ";Source\n"
            "Target\n"
            "\n\n"
        )
        store = self.StoreClass.parsestring(lang)
        assert store.getlangheaders() == [
            "## some_tag ##",
            "## another_tag ##",
            "## NOTE: foo",
            "",
            "",
        ]
        out = io.BytesIO()
        store.serialize(out)
        out.seek(0)
        assert out.read() == (
            b"## active ##\n"
            b"## some_tag ##\n"
            b"## another_tag ##\n"
            b"## NOTE: foo\n"
            b"\n\n"
            b";Source\n"
            b"Target\n"
            b"\n\n"
        )

    def test_not_headers(self) -> None:
        """Ensure we dont treat a tag immediately after headers as header."""
        lang = (
            "## active ##\n"
            "## some_tag ##\n"
            "## another_tag ##\n"
            "## NOTE: foo\n"
            "## TAG: fooled_you ##\n"
            ";Source\n"
            "Target\n"
            "\n\n"
        )
        store = self.StoreClass.parsestring(lang)
        assert "## TAG: fooled_you ##" not in store.getlangheaders()

    @pytest.mark.parametrize("nl", [0, 1, 2, 3])
    def test_header_blanklines(self, nl) -> None:
        """Ensure that blank lines following a header are recorded."""
        lang_header = "## active ##\n## some_tag ##\n"
        lang_unit1 = "# Comment\n;Source\nTarget\n\n\n"
        lang = lang_header + "\n" * nl + lang_unit1
        store = self.StoreClass.parsestring(lang)
        assert bytes(store).decode("utf-8") == lang

    def test_tag_comments(self) -> None:
        """Ensure we can handle comments and distinguish from headers."""
        lang = (
            "## active ##\n"
            "# First comment\n"
            "## TAG: important_tag\n"
            "# Second comment\n"
            "# Third comment\n"
            "## TAG: another_important_tag\n"
            ";Source\n"
            "Target\n"
            "\n\n"
        )
        store = self.StoreClass.parsestring(lang)
        assert not store.getlangheaders()
        assert bytes(store).decode("utf-8") == lang
        assert "# TAG: important_tag" in store.units[0].getnotes(
            origin="developer"
        ).split("\n")
        lang = (
            "## active ##\n"
            "# First comment\n"
            "## TAG: important_tag\n"
            "# Second comment\n"
            "# Third comment\n"
            "## TAG: another_important_tag\n"
            "# Another comment\n"
            ";Source\n"
            "Target\n"
            "\n\n"
        )
        store = self.StoreClass.parsestring(lang)
        assert not store.getlangheaders()
        assert "First comment" in store.units[0].getnotes(origin="developer").split(
            "\n"
        )
        assert "Second comment" in store.units[0].getnotes(origin="developer").split(
            "\n"
        )
        assert "Another comment" in store.units[0].getnotes(origin="developer").split(
            "\n"
        )
        assert "# TAG: another_important_tag" in store.units[0].getnotes(
            origin="developer"
        ).split("\n")

    def test_maxlength(self) -> None:
        """Ensure we can handle MAX_LENGTH meta data."""
        lang = "## MAX_LENGTH: 80\n# Comment\n;Source\nTarget\n\n\n"
        store = self.StoreClass.parsestring(lang)
        assert not store.getlangheaders()
        assert bytes(store).decode("utf-8") == lang
        assert "# MAX_LENGTH: 80" in store.units[0].getnotes(origin="developer").split(
            "\n"
        )

    def test_line_number(self) -> None:
        """Test that line numbers are correctly tracked for mozilla_lang units."""
        lang = (
            "## active ##\n"
            "# First comment\n"
            ";First Source\n"
            "First Target\n"
            "\n\n"
            "# Second comment\n"
            ";Second Source\n"
            "Second Target\n"
            "\n\n"
            ";Third Source\n"
            "Third Target\n"
            "\n\n"
        )
        store = self.StoreClass.parsestring(lang)

        # First unit should start at line 3 (;First Source)
        assert store.units[0].line_number == 3
        assert store.units[0].source == "First Source"

        # Second unit should start at line 8 (;Second Source)
        assert store.units[1].line_number == 8
        assert store.units[1].source == "Second Source"

        # Third unit should start at line 12 (;Third Source)
        assert store.units[2].line_number == 12
        assert store.units[2].source == "Third Source"
