import os
from io import BytesIO
from urllib import parse

import pytest

from translate.convert import oo2po, po2oo
from translate.storage import oo, po
from translate.storage.poheader import poheader

from . import test_convert


def assert_recursive_archive_rejects_path_traversal(testcase, outputext) -> None:
    """Exercise recursive archive input with one safe and one unsafe subfile."""
    safe_line = r"svx	source\dialog\numpages.src	0	string	RID_SVXPAGE_NUM_OPTIONS	STR_BULLET			0	en-US	Character				20050924 09:13:58"
    unsafe_line = r"..	escape/sub/file.src	0	string	RID_ESCAPE	STR_ESCAPE			0	en-US	Escape				20050924 09:13:58"
    testcase.create_testfile("input.oo", f"{safe_line}\n{unsafe_line}")
    outputdir = "root"
    with pytest.warns(UserWarning, match="unsafe subfile path"):
        testcase.run_command("input.oo", outputdir, lang="ku")
    assert os.path.isfile(
        testcase.get_testfilename(
            os.path.join(outputdir, "svx", "source", "dialog" + os.extsep + outputext)
        )
    )
    assert not os.path.exists(
        testcase.get_testfilename(os.path.join("escape", "sub" + os.extsep + outputext))
    )


class TestOO2PO:
    target_filetype = po.pofile
    conversion_module = oo2po
    conversion_class = oo2po.oo2po

    def convert(self, oosource, sourcelanguage="en-US", targetlanguage="af-ZA"):
        """Helper that converts oo source to po source without requiring files."""
        if isinstance(oosource, str):
            oosource = oosource.encode("utf-8")
        inputoo = oo.oofile(oosource)
        convertor = self.conversion_class(sourcelanguage, targetlanguage)
        return convertor.convertstore(inputoo)

    @staticmethod
    def singleelement(pofile):
        """Checks that the pofile contains a single non-header element, and returns it."""
        if isinstance(pofile, poheader):
            assert len(pofile.units) == 2
            assert pofile.units[0].isheader()
            return pofile.units[1]
        assert len(pofile.units) == 1
        return pofile.units[0]

    def roundtripstring(self, filename, entitystring):
        """
        Convert the supplied string as part of an OpenOffice.org GSI file to po and back.

        Return the string once it has been through all the conversions.
        """
        ootemplate = r"helpcontent2	%s	0	help	par_id3150670 35				0	en-US	%s				2002-02-02 02:02:02"

        oosource = (ootemplate % (filename, entitystring)).encode("utf-8")
        ooinputfile = BytesIO(oosource)
        ootemplatefile = BytesIO(oosource)
        pooutputfile = BytesIO()

        self.conversion_module.convertoo(
            ooinputfile, pooutputfile, ootemplatefile, targetlanguage="en-US"
        )
        posource = pooutputfile.getvalue()

        poinputfile = BytesIO(posource)
        ootemplatefile = BytesIO(oosource)
        oooutputfile = BytesIO()
        po2oo.convertoo(
            poinputfile, oooutputfile, ootemplatefile, targetlanguage="en-US"
        )
        ooresult = oooutputfile.getvalue().decode("utf-8")
        print(
            "original oo:\n",
            oosource,
            "po version:\n",
            posource,
            "output oo:\n",
            ooresult,
        )
        return ooresult.split("\t")[10]

    def check_roundtrip(self, filename, text) -> None:
        """Checks that the text converted to po and back is the same as the original."""
        assert self.roundtripstring(filename, text) == text

    def test_simpleentity(self) -> None:
        """Checks that a simple oo entry converts properly to a po entry."""
        oosource = r"svx	source\dialog\numpages.src	0	string	RID_SVXPAGE_NUM_OPTIONS	STR_BULLET			0	en-US	Character				20050924 09:13:58"
        pofile = self.convert(oosource)
        pounit = self.singleelement(pofile)
        assert pounit.source == "Character"
        assert pounit.target == ""

    def test_escapes(self) -> None:
        """Checks that a simple oo entry converts escapes properly to a po entry."""
        oosource = r"wizards	source\formwizard\dbwizres.src	0	string	RID_DB_FORM_WIZARD_START + 19				0	en-US	Newline \n Newline Tab \t Tab CR \r CR				20050924 09:13:58"
        pofile = self.convert(oosource)
        pounit = self.singleelement(pofile)
        poelementsrc = str(pounit)
        print(poelementsrc)
        assert "Newline \n Newline" in pounit.source
        assert "Tab \t Tab" in pounit.source
        assert "CR \r CR" in pounit.source

    def test_roundtrip_escape(self) -> None:
        self.check_roundtrip(
            "strings.src",
            r"The given command is not a SELECT statement.\nOnly queries are allowed.",
        )
        self.check_roundtrip(
            "source\\ui\\dlg\\AutoControls_tmpl.hrc",
            r";\t59\t,\t44\t:\t58\t{Tab}\t9\t{Space}\t32",
        )
        self.check_roundtrip(
            "inc_openoffice\\windows\\msi_languages\\Nsis.ulf",
            r"The installation files must be unpacked and copied to your hard disk in preparation for the installation. After that, the %PRODUCTNAME installation will start automatically.\r\n\r\nClick \'Next\' to continue.",
        )
        self.check_roundtrip("file.xhp", r"\<ahelp\>")
        self.check_roundtrip("file.xhp", r"\<ahelp prop=\"value\"\>")
        self.check_roundtrip(
            "file.xhp", r"\<ahelp prop=\"value\"\>marked up text\</ahelp\>"
        )
        self.check_roundtrip("file.xhp", r"\<ahelp prop=\"value>>\"\>")
        self.check_roundtrip(
            "file.xhp",
            r"""\<ahelp prop=\"value>>\"\>'Next'>> or "<<Previous"\</ahelp\>""",
        )
        self.check_roundtrip(
            "address_auto.xhp",
            r"""example, \<item type=\"literal\"\>'Harry\\'s Bar'.\</item\>""",
        )

    def test_roundtrip_whitespaceonly(self) -> None:
        """Check items that are only special instances of whitespace."""
        self.check_roundtrip("choose_chart_type.xhp", r" ")
        self.check_roundtrip("choose_chart_type.xhp", b"\xc2\xa0".decode("utf-8"))

    def test_double_escapes(self) -> None:
        oosource = r"helpcontent2	source\text\shared\01\02100001.xhp	0	help	par_id3150670 35				0	en-US	\\<				2002-02-02 02:02:02"
        pofile = self.convert(oosource)
        pounit = self.singleelement(pofile)
        poelementsrc = str(pounit)
        print(poelementsrc)
        assert pounit.source == r"\<"

    def test_escapes_helpcontent2(self) -> None:
        """Checks that a helpcontent2 entry converts escapes properly to a po entry."""
        oosource = r"helpcontent2	source\text\smath\guide\parentheses.xhp	0	help	par_id3150344	4			0	en-US	size *2 \\langle x \\rangle				2002-02-02 02:02:02"
        pofile = self.convert(oosource)
        pounit = self.singleelement(pofile)
        poelementsrc = str(pounit)
        print(poelementsrc)
        assert pounit.source == r"size *2 \langle x \rangle"

    def test_msgid_bug_error_address(self) -> None:
        """Tests the we have the correct url for reporting msgid bugs."""
        oosource = r"wizards	source\formwizard\dbwizres.src	0	string	RID_DB_FORM_WIZARD_START + 19				0	en-US	Newline \n Newline Tab \t Tab CR \r CR				20050924 09:13:58"
        pofile = self.convert(oosource)
        assert pofile.units[0].isheader()
        assert pofile.parseheader()["Report-Msgid-Bugs-To"]
        bug_url = parse.urlparse(pofile.parseheader()["Report-Msgid-Bugs-To"])
        print(bug_url)
        assert bug_url[:3] == ("http", "qa.openoffice.org", "/issues/enter_bug.cgi")
        assert parse.parse_qs(bug_url[4], True) == {
            "comment": [""],
            "component": ["l10n"],
            "form_name": ["enter_issue"],
            "short_desc": ["Localization issue in file: "],
            "subcomponent": ["ui"],
        }

    def test_x_comment_inclusion(self) -> None:
        """Test that we can merge x-comment language entries into comment sections of the PO file."""
        en_USsource = r"wizards	source\formwizard\dbwizres.src	0	string	RID_DB_FORM_WIZARD_START + 19				0	en-US	Text		Quickhelp	Title	20050924 09:13:58"
        xcommentsource = r"wizards	source\formwizard\dbwizres.src	0	string	RID_DB_FORM_WIZARD_START + 19				0	x-comment	%s		%s	%s	20050924 09:13:58"
        # Real comment
        comment = "Comment"
        commentsource = f"{en_USsource}\n{xcommentsource % (comment, comment, comment)}"
        pofile = self.convert(commentsource)
        units = pofile.units[1:] if isinstance(pofile, poheader) else pofile.units
        textunit = units[0]
        assert textunit.source == "Text"
        assert comment in textunit.getnotes("developer")
        quickhelpunit = units[1]
        assert quickhelpunit.source == "Quickhelp"
        assert comment in quickhelpunit.getnotes("developer")
        titleunit = units[2]
        assert titleunit.source == "Title"
        assert comment in titleunit.getnotes("developer")
        # Whitespace and blank
        for comment in ("   ", ""):
            commentsource = (
                f"{en_USsource}\n{xcommentsource % (comment, comment, comment)}"
            )
            pofile = self.convert(commentsource)
            units = pofile.units[1:] if isinstance(pofile, poheader) else pofile.units
            textunit = units[0]
            assert textunit.source == "Text"
            assert textunit.getnotes("developer") == ""
            quickhelpunit = units[1]
            assert quickhelpunit.source == "Quickhelp"
            assert quickhelpunit.getnotes("developer") == ""
            titleunit = units[2]
            assert titleunit.source == "Title"
            assert titleunit.getnotes("developer") == ""


class TestOO2POCommand(test_convert.TestConvertCommand, TestOO2PO):
    """Tests running actual oo2po commands on files."""

    convertmodule = oo2po

    expected_options = [
        "--source-language=LANG",
        "-l LANG, --language=LANG",
        "-P, --pot",
        "--duplicates=DUPLICATESTYLE",
        "--multifile=MULTIFILESTYLE",
        "--nonrecursiveinput",
    ]

    def test_preserve_filename(self) -> None:
        """Ensures that the filename is preserved."""
        oosource = rb"svx	source\dialog\numpages.src	0	string	RID_SVXPAGE_NUM_OPTIONS	STR_BULLET			0	en-US	Character				20050924 09:13:58"
        self.create_testfile("snippet.sdf", oosource)
        oofile = oo.oofile(self.open_testfile("snippet.sdf"))
        assert oofile.filename.endswith("snippet.sdf")
        oofile.parse(oosource)
        assert oofile.filename.endswith("snippet.sdf")

    def test_simple_pot(self) -> None:
        """Tests the simplest possible conversion to a pot file."""
        oosource = r"svx	source\dialog\numpages.src	0	string	RID_SVXPAGE_NUM_OPTIONS	STR_BULLET			0	en-US	Character				20050924 09:13:58"
        self.create_testfile("simple.oo", oosource)
        self.run_command("simple.oo", "simple.pot", pot=True, nonrecursiveinput=True)
        pofile = self.target_filetype(self.open_testfile("simple.pot"))
        poelement = self.singleelement(pofile)
        assert poelement.source == "Character"
        assert poelement.target == ""

    def test_simple_po(self) -> None:
        """Tests the simplest possible conversion to a po file."""
        oosource1 = r"svx	source\dialog\numpages.src	0	string	RID_SVXPAGE_NUM_OPTIONS	STR_BULLET			0	en-US	Character				20050924 09:13:58"
        oosource2 = r"svx	source\dialog\numpages.src	0	string	RID_SVXPAGE_NUM_OPTIONS	STR_BULLET			0	ku	Karakter				20050924 09:13:58"
        self.create_testfile("simple.oo", f"{oosource1}\n{oosource2}")
        self.run_command("simple.oo", "simple.po", lang="ku", nonrecursiveinput=True)
        pofile = self.target_filetype(self.open_testfile("simple.po"))
        poelement = self.singleelement(pofile)
        assert poelement.source == "Character"
        assert poelement.target == "Karakter"

    def test_onefile_nonrecursive(self) -> None:
        """Tests the --multifile=onefile option and make sure it doesn't produce a directory."""
        oosource = r"svx	source\dialog\numpages.src	0	string	RID_SVXPAGE_NUM_OPTIONS	STR_BULLET			0	en-US	Character				20050924 09:13:58"
        self.create_testfile("simple.oo", oosource)
        self.run_command("simple.oo", "simple.pot", pot=True, multifile="onefile")
        assert os.path.isfile(self.get_testfilename("simple.pot"))

    def test_recursive_archive_rejects_path_traversal(self) -> None:
        assert_recursive_archive_rejects_path_traversal(self, "po")

    def test_remove_duplicates(self) -> None:
        """Test that removing of duplicates works correctly (bug 171)."""
        oosource = r"""
sd	source\ui\animations\SlideTransitionPane.src	0	checkbox	DLG_SLIDE_TRANSITION_PANE	CB_AUTO_PREVIEW	HID_SD_SLIDETRANSITIONPANE_CB_AUTO_PREVIEW		1	en-US	Automatic preview				20060725 03:26:42
sd	source\ui\animations\AnimationSchemesPane.src	0	checkbox	DLG_ANIMATION_SCHEMES_PANE	CB_AUTO_PREVIEW	HID_SD_ANIMATIONSCHEMESPANE_CB_AUTO_PREVIEW		1	en-US	Automatic preview				20060725 03:26:42
sd	source\ui\animations\CustomAnimationCreateDialog.src	0	checkbox	RID_TP_CUSTOMANIMATION_ENTRANCE	CBX_PREVIEW			143	en-US	Automatic preview				20060725 03:26:42
sd	source\ui\animations\CustomAnimationCreateDialog.src	0	checkbox	RID_TP_CUSTOMANIMATION_ENTRANCE	CBX_PREVIEW			143	fr	Aperçu automatique				20060725 03:26:42
sd	source\ui\animations\CustomAnimationSchemesPane.src	0	checkbox	DLG_CUSTOMANIMATION_SCHEMES_PANE	4			0	en-US	Automatic preview				20060725 03:26:42
sd	source\ui\animations\CustomAnimationSchemesPane.src	0	checkbox	DLG_CUSTOMANIMATION_SCHEMES_PANE	4			0	fr	Aperçu automatique				20060725 03:26:42
"""
        self.create_testfile("simple.oo", oosource)
        self.run_command(
            "simple.oo",
            "simple.po",
            language="fr",
            multifile="onefile",
            error="traceback",
            duplicates="merge",
        )
        pofile = self.target_filetype(self.open_testfile("simple.po"))
        assert len(pofile.units) == 2
        assert pofile.units[1].target == "Aperçu automatique"
