from unittest import mock

import pytest

from pkgcore.ebuild import domain as domain_mod
from pkgcore.ebuild import profiles
from pkgcore.ebuild.atom import atom
from pkgcore.fs.livefs import iter_scan
from pkgcore.restrictions import packages
from pkgcore.test.misc import FakePkg

from .test_profiles import profile_mixin


class TestDomain:
    @pytest.fixture(autouse=True, scope="function")
    def _setup(self, tmp_path_factory):
        self.confdir = tmp_path_factory.mktemp("conf")
        self.rootdir = tmp_path_factory.mktemp("root")
        self.pmixin = profile_mixin()
        self.profile_base = tmp_path_factory.mktemp("profiles")
        self.profile1 = self.profile_base / "profile1"
        self.pmixin.mk_profile(self.profile_base, str(self.profile1))
        self.pusedir = self.confdir / "package.use"
        self.pusedir.mkdir()
        self.pkeywordsdir = self.confdir / "package.accept_keywords"
        self.pkeywordsdir.mkdir()

    def mk_domain(self):
        return domain_mod.domain(
            profiles.OnDiskProfile(str(self.profile_base), "profile1"),
            [],
            [],
            ROOT=self.rootdir,
            config_dir=self.confdir,
        )

    def test_sorting(self):
        """assert that configuration files are read in alphanum ordering"""
        # assert the base state; no files, no content.
        assert () == self.mk_domain().pkg_use

        (self.pusedir / "00").write_text("*/* X")
        (self.pusedir / "01").write_text("*/* -X Y")

        # Force the returned ordering to be reversed; this is to assert that
        # the domain forces a sort.
        orig_func = iter_scan

        def rev_iter_scan(*args, **kwargs):
            return iter(sorted(orig_func(*args, **kwargs), reverse=True))

        with (
            mock.patch("pkgcore.fs.livefs.iter_scan", side_effect=rev_iter_scan),
            mock.patch("pkgcore.ebuild.domain.iter_scan", side_effect=rev_iter_scan),
        ):
            assert (
                (packages.AlwaysTrue, ((), ("X",))),
                (packages.AlwaysTrue, (("X",), ("Y",))),
            ) == self.mk_domain().pkg_use

    def test_use_expand_syntax(self):
        (self.pusedir / "a").write_text(
            """
            */* x_y1
            # unrelated is there to verify that it's unaffected by the USE_EXPAND
            */* unrelated X: -y1 y2
            # multiple USE_EXPANDs
            */* unrelated X: -y1 y2 Z: -z3 z4
            # cleanup previous
            */* x y -* z
            # cleanup previous USE_EXPAND
            */* unrelated Y: y1 -* y2 Z: z1 -* -z2
            """
        )

        assert (
            (packages.AlwaysTrue, ((), ("x_y1",))),
            (
                packages.AlwaysTrue,
                (
                    ("x_y1",),
                    (
                        "unrelated",
                        "x_y2",
                    ),
                ),
            ),
            (
                packages.AlwaysTrue,
                (
                    ("x_y1", "z_z3"),
                    (
                        "unrelated",
                        "x_y2",
                        "z_z4",
                    ),
                ),
            ),
            (
                packages.AlwaysTrue,
                (
                    ("*",),
                    ("z",),
                ),
            ),
            (
                packages.AlwaysTrue,
                (
                    (
                        "y_*",
                        "z_*",
                        "z_z2",
                    ),
                    (
                        "unrelated",
                        "y_y2",
                    ),
                ),
            ),
        ) == self.mk_domain().pkg_use

    def test_stable_use_defaults(self):
        """EAPI 9 use.stable/package.use.stable: default USE flags that only
        apply to packages merged via a stable keyword"""

        (self.profile1 / "make.defaults").write_text(
            'ARCH="amd64"\nACCEPT_KEYWORDS="amd64"\n'
        )
        (self.profile1 / "eapi").write_text("9")
        (self.profile1 / "use.stable").write_text("globalflag")
        (self.profile1 / "package.use").write_text("dev-util/foo baseflag")
        (self.profile1 / "package.use.stable").write_text("dev-util/foo stableflag")

        domain = self.mk_domain()
        iuse = ("globalflag", "baseflag", "stableflag")

        # package merged as stable -> use.stable + package.use.stable apply
        stable_pkg = FakePkg("dev-util/foo-1", iuse=iuse, keywords=["amd64"])
        _, enabled, _ = domain.get_package_use_unconfigured(stable_pkg)
        assert {"baseflag", "stableflag", "globalflag"} <= enabled

        # package merged as unstable -> only package.use applies
        unstable_pkg = FakePkg("dev-util/foo-1", iuse=iuse, keywords=["~amd64"])
        _, enabled, _ = domain.get_package_use_unconfigured(unstable_pkg)
        assert "baseflag" in enabled
        assert "stableflag" not in enabled
        assert "globalflag" not in enabled

    def test_use_flag_parsing_enforcement(self, caplog):
        (self.pusedir / "a").write_text("*/* X:")
        assert ((packages.AlwaysTrue, ((), ())),) == self.mk_domain().pkg_use
        assert caplog.text == ""  # no problems with nothing after USE_EXPAND:
        caplog.clear()

        (self.pusedir / "a").write_text("*/* y $x")
        assert () == self.mk_domain().pkg_use
        assert "token $x is not a valid use flag" in caplog.text
        caplog.clear()

        (self.pusedir / "a").write_text("*/* y X: $z")
        assert () == self.mk_domain().pkg_use
        assert "token x_$z is not a valid use flag" in caplog.text
        caplog.clear()

    @pytest.mark.xfail(
        reason="pruning of tokens isn't yet implemented for package.keywords"
    )
    def test_package_keywords(self):
        (self.pkeywordsdir / "a").write_text(
            """
            # control case.
            dev-util/normal x86 ~amd64

            # all stable means x86 is redundant
            dev-util/stable x86 *

            # all unstable (~*) implicitly includes stable, so drop the x64
            dev-util/unstable ~* x64

            # ** matches everything, including no keywords.  the x is redundant
            dev-util/always amd64 ** x
            """
        )
        keys = self.mk_domain().pkg_accept_keywords
        assert keys == (
            (atom("dev-util/normal"), ("x86", "~amd64")),
            (atom("dev-util/test-stable"), ("*",)),
            (atom("dev-util/test-unstable"), ("~*")),
            # The '**' needs validation pkgcore internally translates this
            # into the correct restriction (AlwaysTrue)
            (atom("dev-util/test-unstable"), ("**")),
        )
