import os
import unittest
from tempfile import TemporaryDirectory
from typing import Any

from dhpython.debhelper import DebHelper, build_options, Options


class DebHelperTestCase(unittest.TestCase):
    impl = "cpython3"
    control: list[str] = []
    options: dict[str, Any] = {}
    compat: int | None = None
    parse_control = True

    def build_options(self) -> Options:
        return build_options(**self.options)

    def setUp(self) -> None:
        self.tempdir = TemporaryDirectory()  # pylint: disable=consider-using-with
        self.addCleanup(self.tempdir.cleanup)

        old_wd = os.getcwd()
        os.chdir(self.tempdir.name)
        self.addCleanup(os.chdir, old_wd)

        os.mkdir("debian")
        with open("debian/control", "w", encoding="UTF-8") as f:
            f.write("\n".join(self.control))
        if self.compat:
            with open("debian/compat", "w", encoding="UTF-8") as f:
                f.write(f"{self.compat}\n")
        if self.parse_control:
            self.dh = DebHelper(self.build_options(), impl=self.impl)


CONTROL = [
    "Source: foo-src",
    "Build-Depends: debhelper-compat (= 13), ",
    " python3-all, ",
    " python-all",
    "\t, bar (<< 2) [amd64],",
    " baz (>= 1.0)",
    "X-Python3-Version: >= 3.1, << 3.10",
    "",
    "Architecture: all",
    "Package: python3-foo",
    "Depends: ${python3:Depends}",
    "",
    "Package: python3-foo-ext",
    "Architecture: any",
    "Depends: ${python3:Depends}, " "# COMMENT",
    " ${shlibs:Depends},",
    "",
    "Package: python-foo",
    "Architecture: all",
    "Depends: ${python:Depends}",
    "",
    "",
    "Package: foo",
    "Architecture: all",
    "Depends: ${python3:Depends}",
    "",
    "",
    "Package: recfoo",
    "Architecture: all",
    "Recommends: ${python3:Depends}",
    "",
    "",
]


class TestControlBlockParsing(DebHelperTestCase):
    control = CONTROL

    def test_parses_source(self) -> None:
        self.assertEqual(self.dh.source_name, "foo-src")

    def test_parses_build_depends(self) -> None:
        self.assertEqual(
            self.dh.build_depends,
            {
                "debhelper-compat": {None: "= 13"},
                "python3-all": {None: None},
                "python-all": {None: None},
                "bar": {"amd64": "<< 2"},
                "baz": {None: ">= 1.0"},
            },
        )

    def test_parses_XPV(self) -> None:
        self.assertEqual(self.dh.python_version, ">= 3.1, << 3.10")

    def test_parses_packages(self) -> None:
        self.assertEqual(
            list(self.dh.packages.keys()),
            ["python3-foo", "python3-foo-ext", "foo", "recfoo"],
        )

    def test_parses_arch(self) -> None:
        self.assertEqual(self.dh.packages["python3-foo-ext"].arch, "any")

    def test_parses_arch_all(self) -> None:
        self.assertEqual(self.dh.packages["python3-foo"].arch, "all")

    def test_compat(self) -> None:
        self.assertEqual(self.dh.compat(), 13)


class TestControlSkipIndep(DebHelperTestCase):
    control = CONTROL
    options = {
        "arch": True,
    }

    def test_skip_indep(self) -> None:
        self.assertEqual(list(self.dh.packages.keys()), ["python3-foo-ext"])


class TestControlSkipArch(DebHelperTestCase):
    control = CONTROL
    options = {
        "arch": False,
    }

    def test_skip_arch(self) -> None:
        self.assertEqual(
            list(self.dh.packages.keys()), ["python3-foo", "foo", "recfoo"]
        )


class TestControlSinglePkg(DebHelperTestCase):
    control = CONTROL
    options = {
        "package": ["python3-foo"],
    }

    def test_parses_packages(self) -> None:
        self.assertEqual(list(self.dh.packages.keys()), ["python3-foo"])


class TestControlSkipSinglePkg(DebHelperTestCase):
    control = CONTROL
    options = {
        "no_package": ["python3-foo"],
    }

    def test_parses_packages(self) -> None:
        self.assertEqual(
            list(self.dh.packages.keys()), ["python3-foo-ext", "foo", "recfoo"]
        )


class TestControlNoBinaryPackages(DebHelperTestCase):
    control = [
        "Source: foo-src",
        "Build-Depends: python3-all",
        "",
    ]
    parse_control = False

    def test_throws_error(self) -> None:
        msg = "Unable to parse debian/control, found less than 2 paragraphs"
        with self.assertRaisesRegex(Exception, msg):
            DebHelper(self.build_options())


class TestControlMissingPackage(DebHelperTestCase):
    control = [
        "Source: foo-src",
        "Build-Depends: python3-all",
        "",
        "Architecture: all",
    ]
    parse_control = False

    def test_parses_packages(self) -> None:
        msg = "Unable to parse debian/control, paragraph 2 missing Package " "field"
        with self.assertRaisesRegex(Exception, msg):
            DebHelper(self.build_options())


class TestControlMinimalSpacing(DebHelperTestCase):
    control = [
        "Source:foo-src",
        "Build-Depends:debhelper-compat(=13),python3-all",
        "",
        "Architecture:all",
        "Package:python3-foo",
        "Depends:${python3:Depends}",
    ]

    def test_parses_source(self) -> None:
        self.assertEqual(self.dh.source_name, "foo-src")

    def test_parses_build_depends(self) -> None:
        self.assertEqual(
            self.dh.build_depends,
            {"debhelper-compat": {None: "=13"}, "python3-all": {None: None}},
        )

    def test_parses_packages(self) -> None:
        self.assertEqual(list(self.dh.packages), ["python3-foo"])

    def test_parses_arch_all(self) -> None:
        self.assertEqual(self.dh.packages["python3-foo"].arch, "all")

    def test_compat(self) -> None:
        self.assertEqual(self.dh.compat(), 13)


class TestRemainingPackages(DebHelperTestCase):
    control = CONTROL
    options = {
        "remaining_packages": True,
    }
    parse_control = False

    def setUp(self) -> None:
        super().setUp()
        with open("debian/python3-foo.debhelper.log", "w", encoding="UTF-8") as f:
            f.write("dh_python3\n")
        with open("debian/python3-foo-ext.debhelper.log", "w", encoding="UTF-") as f:
            f.write("dh_foobar\n")
        self.dh = DebHelper(self.build_options(), impl=self.impl)

    def test_skips_logged_packages(self) -> None:
        self.assertEqual(
            list(self.dh.packages.keys()), ["python3-foo-ext", "foo", "recfoo"]
        )


class TestLegacyCompatFile(DebHelperTestCase):
    control = [
        "Source: foo-src",
        "Build-Depends: python3-all",
        "",
        "Package: foo",
        "Architecture: all",
    ]
    compat = 10
    parse_control = True

    def test_compat(self) -> None:
        self.assertEqual(self.dh.compat(), 10)


class TestXDHCompat(DebHelperTestCase):
    control = [
        "Source: foo-src",
        "Build-Depends: debhelper",
        "X-DH-Compat: 14",
        "",
        "Package: foo",
        "Architecture: all",
    ]

    def test_compat(self) -> None:
        self.assertEqual(self.dh.compat(), 14)


class TestXDHCompatInvalid(DebHelperTestCase):
    control = [
        "Source: foo-src",
        "Build-Depends: debhelper",
        "X-DH-Compat: not-a-number",
        "",
        "Package: foo",
        "Architecture: all",
    ]
    parse_control = False

    def test_compat(self) -> None:
        with self.assertRaisesRegex(Exception, r"Invalid X-DH-Compat value"):
            dh = DebHelper(self.build_options(), impl=self.impl)
            dh.compat()


class TestNoCompatLevel(DebHelperTestCase):
    control = [
        "Source: foo-src",
        "Build-Depends: python3-all",
        "",
        "Package: foo",
        "Architecture: all",
    ]
    parse_control = False

    def test_compat(self) -> None:
        with self.assertRaisesRegex(
            Exception, r"Unable to determine debhelper compatibility version."
        ):
            dh = DebHelper(self.build_options(), impl=self.impl)
            dh.compat()
