import os
import warnings
from io import BytesIO

import pytest

from translate.storage import oo


def test_makekey() -> None:
    """Checks the makekey function for consistency."""
    assert (
        oo.makekey(
            (
                "project",
                r"path\to\the\sourcefile.src",
                "resourcetype",
                "GROUP_ID",
                "LOCAL_ID",
                "platform",
            ),
            False,
        )
        == "sourcefile.src#GROUP_ID.LOCAL_ID.resourcetype"
    )
    # Testwith long_key i.e. used in multifile options
    assert (
        oo.makekey(
            (
                "project",
                r"path\to\the\sourcefile.src",
                "resourcetype",
                "GROUP_ID",
                "LOCAL_ID",
                "platform",
            ),
            True,
        )
        == "project/path/to/the/sourcefile.src#GROUP_ID.LOCAL_ID.resourcetype"
    )
    assert (
        oo.makekey(
            (
                "project",
                r"path\to\the\sourcefile.src",
                "resourcetype",
                "GROUP_ID",
                "",
                "platform",
            ),
            False,
        )
        == "sourcefile.src#GROUP_ID.resourcetype"
    )
    assert (
        oo.makekey(
            (
                "project",
                r"path\to\the\sourcefile.src",
                "resourcetype",
                "",
                "LOCAL_ID",
                "platform",
            ),
            False,
        )
        == "sourcefile.src#LOCAL_ID.resourcetype"
    )
    assert (
        oo.makekey(
            (
                "project",
                r"path\to\the\sourcefile.src",
                "",
                "GROUP_ID",
                "LOCAL_ID",
                "platform",
            ),
            False,
        )
        == "sourcefile.src#GROUP_ID.LOCAL_ID"
    )
    assert (
        oo.makekey(
            ("project", r"path\to\the\sourcefile.src", "", "GROUP_ID", "", "platform"),
            False,
        )
        == "sourcefile.src#GROUP_ID"
    )


def test_escape_help_text() -> None:
    """Check the help text escape function."""
    assert (
        oo.escape_help_text("If we don't know <tag> we don't <br> escape it")
        == "If we don't know <tag> we don't <br> escape it"
    )
    # Bug 694
    assert oo.escape_help_text("A szó: <nyelv>") == "A szó: <nyelv>"
    assert (
        oo.escape_help_text(
            """...következő: "<kiszolgáló> <témakör> <elem>", ahol..."""
        )
        == """...következő: "<kiszolgáló> <témakör> <elem>", ahol..."""
    )
    # See bug 694 comments 8-10 not fully resolved.
    assert (
        oo.escape_help_text(r"...törtjel (\) létrehozásához...")
        == r"...törtjel (\\) létrehozásához..."
    )


class TestOO:
    @staticmethod
    def ooparse(oosource):
        """Helper that parses oo source without requiring files."""
        dummyfile = BytesIO(oosource.encode())
        return oo.oofile(dummyfile)

    def ooregen(self, oosource):
        """Helper that converts oo source to oofile object and back."""
        oofile = self.ooparse(oosource)
        return bytes(oofile).decode("utf-8")

    def test_simpleentry(self) -> None:
        """Checks that a simple oo entry is parsed correctly."""
        oosource = r"svx	source\dialog\numpages.src	0	string	RID_SVXPAGE_NUM_OPTIONS	STR_BULLET			0	en-US	Character				20050924 09:13:58"
        oofile = self.ooparse(oosource)
        assert len(oofile.units) == 1
        oe = oofile.units[0]
        assert list(oe.languages.keys()) == ["en-US"]
        ol = oofile.oolines[0]
        assert ol.getkey() == (
            "svx",
            r"source\dialog\numpages.src",
            "string",
            "RID_SVXPAGE_NUM_OPTIONS",
            "STR_BULLET",
            "",
        )
        assert ol.text == "Character"
        assert str(ol) == oosource

    def test_simpleentry_quickhelptest(self) -> None:
        """Checks that a simple entry with quickhelptext is parsed correctly."""
        oosource = r"sd	source\ui\dlg\sdobjpal.src	0	imagebutton	FLTWIN_SDOBJPALETTE	BTN_SYMSIZE			16	en-US	-		Toggle Symbol Size		20051017 21:40:56"
        oofile = self.ooparse(oosource)
        assert len(oofile.units) == 1
        oe = oofile.units[0]
        assert list(oe.languages.keys()) == ["en-US"]
        ol = oofile.oolines[0]
        assert ol.getkey() == (
            "sd",
            r"source\ui\dlg\sdobjpal.src",
            "imagebutton",
            "FLTWIN_SDOBJPALETTE",
            "BTN_SYMSIZE",
            "",
        )
        assert ol.quickhelptext == "Toggle Symbol Size"
        assert str(ol) == oosource

    def test_simpleentry_title(self) -> None:
        """Checks that a simple entry with title text is parsed correctly."""
        oosource = r"dbaccess	source\ui\dlg\indexdialog.src	0	querybox	QUERY_SAVE_CURRENT_INDEX				0	en-US	Do you want to save the changes made to the current index?			Exit Index Design	20051017 21:40:56"
        oofile = self.ooparse(oosource)
        assert len(oofile.units) == 1
        oe = oofile.units[0]
        assert list(oe.languages.keys()) == ["en-US"]
        ol = oofile.oolines[0]
        assert ol.getkey() == (
            "dbaccess",
            r"source\ui\dlg\indexdialog.src",
            "querybox",
            "QUERY_SAVE_CURRENT_INDEX",
            "",
            "",
        )
        assert ol.title == "Exit Index Design"
        assert str(ol) == oosource

    def test_blankline(self) -> None:
        """Checks that a blank line is parsed correctly."""
        oosource = "\n"
        warnings.simplefilter("error")
        oofile = self.ooparse(oosource)
        assert len(oofile.units) == 0

    def test_fieldlength(self) -> None:
        """Checks that we process the length field correctly."""
        # Since the actual field is 18 characters long and the field width in this example is 16 we're not sure if they even use this!
        oosource = r"sd	source\ui\dlg\sdobjpal.src	0	imagebutton	FLTWIN_SDOBJPALETTE	BTN_SYMSIZE			16	en-US	-		Toggle Symbol Size		20051017 21:40:56"
        oofile = self.ooparse(oosource)
        assert len(oofile.units) == 1
        oe = oofile.units[0]
        assert list(oe.languages.keys()) == ["en-US"]
        ol = oofile.oolines[0]
        assert int(ol.width) == 16

    def test_escapes(self) -> None:
        """Checks that we escape properly."""
        oosource = r"svx	source\dialog\numpages.src	0	string	RID_SVXPAGE_NUM_OPTIONS	STR_BULLET			0	en-US	size *2 \\langle x \\rangle				20050924 09:13:58"
        oofile = self.ooregen(oosource)
        assert r"size *2 \\langle x \\rangle" in oofile


class TestOOMultifile:
    def test_getsubfilename_normalizes_safe_path(self, tmp_path) -> None:
        source = tmp_path / "input.sdf"
        multifile = oo.oomultifile(str(source), mode="w")
        try:
            result = multifile.getsubfilename(r"svx	source\dialog\numpages.src	0")
        finally:
            multifile.multifile.close()
        assert result == os.path.join("svx", "source", "dialog") + os.extsep + "oo"

    def test_getsubfilename_rejects_path_traversal(self, tmp_path) -> None:
        source = tmp_path / "input.sdf"
        multifile = oo.oomultifile(str(source), mode="w")
        try:
            with pytest.raises(oo.UnsafeOOSubfilePath, match="unsafe subfile path"):
                multifile.getsubfilename(r"..	../escape/sub/file.src	0")
        finally:
            multifile.multifile.close()

    def test_createsubfileindex_skips_unsafe_subfiles(self, tmp_path) -> None:
        source = tmp_path / "input.sdf"
        safe_line = r"svx	source\dialog\numpages.src	0"
        unsafe_line = r"..	../escape/sub/file.src	0"
        source.write_text(
            f"{safe_line}\n{unsafe_line}",
            encoding="utf-8",
        )
        with pytest.warns(UserWarning, match="unsafe subfile path"):
            multifile = oo.oomultifile(str(source))
        try:
            assert list(multifile.listsubfiles()) == [
                os.path.join("svx", "source", "dialog") + os.extsep + "oo"
            ]
        finally:
            multifile.multifile.close()
