import os
import pytest

from moulinette.utils.process import check_output

from yunohost.app import app_setting
from yunohost.domain import _get_maindomain
from yunohost.utils.resources import (
    AppResource,
    AppResourceManager,
    AppResourceClassesByType,
)
from yunohost.permission import user_permission_list, permission_delete
from yunohost.firewall import firewall_list

dummyfile = "/tmp/dummyappresource-testapp"


class DummyAppResource(AppResource):
    type = "dummy"

    default_properties = {
        "file": "/tmp/dummyappresource-__APP__",
        "content": "foo",
    }

    def provision_or_update(self, context):
        open(self.file, "w").write(self.content)

        if self.content == "forbiddenvalue":
            raise Exception("Emeged you used the forbidden value!1!£&")

    def deprovision(self, context):
        os.system(f"rm -f {self.file}")


AppResourceClassesByType["dummy"] = DummyAppResource


def setup_function(function):
    clean()

    os.system("mkdir /etc/yunohost/apps/testapp")
    os.system("echo 'id: testapp' > /etc/yunohost/apps/testapp/settings.yml")
    os.system("echo 'packaging_format = 2' > /etc/yunohost/apps/testapp/manifest.toml")
    os.system("echo 'id = \"testapp\"' >> /etc/yunohost/apps/testapp/manifest.toml")


def teardown_function(function):
    clean()


def clean():
    os.system(f"rm -f {dummyfile}")
    os.system("rm -rf /etc/yunohost/apps/testapp")
    os.system("rm -rf /var/www/testapp")
    os.system("rm -rf /home/yunohost.app/testapp")
    os.system("apt remove lolcat sl nyancat yarn >/dev/null 2>/dev/null")
    os.system("userdel testapp 2>/dev/null")

    for p in user_permission_list()["permissions"]:
        if p.startswith("testapp."):
            permission_delete(p, force=True, sync_perm=False)


def test_provision_dummy():
    current = {"resources": {}}
    wanted = {"resources": {"dummy": {}}}

    assert not os.path.exists(dummyfile)
    AppResourceManager("testapp", current=current, wanted=wanted).apply(
        rollback_and_raise_exception_if_failure=False
    )
    assert open(dummyfile).read().strip() == "foo"


def test_deprovision_dummy():
    current = {"resources": {"dummy": {}}}
    wanted = {"resources": {}}

    open(dummyfile, "w").write("foo")

    assert open(dummyfile).read().strip() == "foo"
    AppResourceManager("testapp", current=current, wanted=wanted).apply(
        rollback_and_raise_exception_if_failure=False
    )
    assert not os.path.exists(dummyfile)


def test_provision_dummy_nondefaultvalue():
    current = {"resources": {}}
    wanted = {"resources": {"dummy": {"content": "bar"}}}

    assert not os.path.exists(dummyfile)
    AppResourceManager("testapp", current=current, wanted=wanted).apply(
        rollback_and_raise_exception_if_failure=False
    )
    assert open(dummyfile).read().strip() == "bar"


def test_update_dummy():
    current = {"resources": {"dummy": {}}}
    wanted = {"resources": {"dummy": {"content": "bar"}}}

    open(dummyfile, "w").write("foo")

    assert open(dummyfile).read().strip() == "foo"
    AppResourceManager("testapp", current=current, wanted=wanted).apply(
        rollback_and_raise_exception_if_failure=False
    )
    assert open(dummyfile).read().strip() == "bar"


def test_update_dummy_failwithrollback():
    current = {"resources": {"dummy": {}}}
    wanted = {"resources": {"dummy": {"content": "forbiddenvalue"}}}

    open(dummyfile, "w").write("foo")

    assert open(dummyfile).read().strip() == "foo"
    with pytest.raises(Exception):
        AppResourceManager("testapp", current=current, wanted=wanted).apply(
            rollback_and_raise_exception_if_failure=True
        )
    assert open(dummyfile).read().strip() == "foo"


def test_resource_system_user():
    r = AppResourceClassesByType["system_user"]

    conf = {}

    assert os.system("getent passwd testapp 2>/dev/null") != 0

    r(conf, "testapp").provision_or_update()

    assert os.system("getent passwd testapp >/dev/null") == 0
    assert os.system("groups testapp | grep -q 'sftp.app'") != 0

    conf["allow_sftp"] = True
    r(conf, "testapp").provision_or_update()

    assert os.system("getent passwd testapp >/dev/null") == 0
    assert os.system("groups testapp | grep -q 'sftp.app'") == 0

    r(conf, "testapp").deprovision()

    assert os.system("getent passwd testapp 2>/dev/null") != 0


def test_resource_install_dir():
    r = AppResourceClassesByType["install_dir"]
    conf = {"owner": "nobody:rx", "group": "nogroup:rx"}

    # FIXME: should also check settings ?
    # FIXME: should also check automigrate from final_path
    # FIXME: should also test changing the install folder location ?

    assert not os.path.exists("/var/www/testapp")

    r(conf, "testapp").provision_or_update()

    assert os.path.exists("/var/www/testapp")
    unixperms = check_output("ls -ld /var/www/testapp").split()
    assert unixperms[0] == "dr-xr-x---"
    assert unixperms[2] == "nobody"
    assert unixperms[3] == "nogroup"

    conf["owner"] = "nobody:rwx"
    conf["group"] = "www-data:x"

    r(conf, "testapp").provision_or_update()

    assert os.path.exists("/var/www/testapp")
    unixperms = check_output("ls -ld /var/www/testapp").split()
    assert unixperms[0] == "drwx--x---"
    assert unixperms[2] == "nobody"
    assert unixperms[3] == "www-data"

    r(conf, "testapp").deprovision()

    assert not os.path.exists("/var/www/testapp")


def test_resource_data_dir():
    r = AppResourceClassesByType["data_dir"]
    conf = {"owner": "nobody:rx", "group": "nogroup:rx"}

    assert not os.path.exists("/home/yunohost.app/testapp")

    r(conf, "testapp").provision_or_update()

    assert os.path.exists("/home/yunohost.app/testapp")
    unixperms = check_output("ls -ld /home/yunohost.app/testapp").split()
    assert unixperms[0] == "dr-xr-x---"
    assert unixperms[2] == "nobody"
    assert unixperms[3] == "nogroup"

    conf["owner"] = "nobody:rwx"
    conf["group"] = "www-data:x"

    r(conf, "testapp").provision_or_update()

    assert os.path.exists("/home/yunohost.app/testapp")
    unixperms = check_output("ls -ld /home/yunohost.app/testapp").split()
    assert unixperms[0] == "drwx--x---"
    assert unixperms[2] == "nobody"
    assert unixperms[3] == "www-data"

    r(conf, "testapp").deprovision()

    # FIXME : implement and check purge option
    # assert not os.path.exists("/home/yunohost.app/testapp")


def test_resource_ports():
    r = AppResourceClassesByType["ports"]
    conf = {}

    assert not app_setting("testapp", "port")

    r(conf, "testapp").provision_or_update()

    assert app_setting("testapp", "port")

    r(conf, "testapp").deprovision()

    assert not app_setting("testapp", "port")


def test_resource_ports_several():
    r = AppResourceClassesByType["ports"]
    conf = {"main": {"default": 12345}, "foobar": {"default": 23456}}

    assert not app_setting("testapp", "port")
    assert not app_setting("testapp", "port_foobar")

    r(conf, "testapp").provision_or_update()

    assert app_setting("testapp", "port")
    assert app_setting("testapp", "port_foobar")

    r(conf, "testapp").deprovision()

    assert not app_setting("testapp", "port")
    assert not app_setting("testapp", "port_foobar")


def test_resource_ports_firewall():
    r = AppResourceClassesByType["ports"]
    conf = {"main": {"default": 12345}}

    r(conf, "testapp").provision_or_update()

    assert 12345 not in firewall_list()["opened_ports"]

    conf = {"main": {"default": 12345, "exposed": "TCP"}}

    r(conf, "testapp").provision_or_update()

    assert 12345 in firewall_list()["opened_ports"]

    r(conf, "testapp").deprovision()

    assert 12345 not in firewall_list()["opened_ports"]


def test_resource_database():
    r = AppResourceClassesByType["database"]
    conf = {"type": "mysql"}

    assert os.system("mysqlshow 'testapp' >/dev/null 2>/dev/null") != 0
    assert not app_setting("testapp", "db_name")
    assert not app_setting("testapp", "db_user")
    assert not app_setting("testapp", "db_pwd")

    r(conf, "testapp").provision_or_update()

    assert os.system("mysqlshow 'testapp' >/dev/null 2>/dev/null") == 0
    assert app_setting("testapp", "db_name")
    assert app_setting("testapp", "db_user")
    assert app_setting("testapp", "db_pwd")

    r(conf, "testapp").deprovision()

    assert os.system("mysqlshow 'testapp' >/dev/null 2>/dev/null") != 0
    assert not app_setting("testapp", "db_name")
    assert not app_setting("testapp", "db_user")
    assert not app_setting("testapp", "db_pwd")


def test_resource_apt():
    r = AppResourceClassesByType["apt"]
    conf = {
        "packages": "nyancat, sl",
        "extras": {
            "yarn": {
                "repo": "deb https://dl.yarnpkg.com/debian/ stable main",
                "key": "https://dl.yarnpkg.com/debian/pubkey.gpg",
                "packages": "yarn",
            }
        },
    }

    assert os.system("dpkg --list | grep -q 'ii *nyancat '") != 0
    assert os.system("dpkg --list | grep -q 'ii *sl '") != 0
    assert os.system("dpkg --list | grep -q 'ii *yarn '") != 0
    assert os.system("dpkg --list | grep -q 'ii *lolcat '") != 0
    assert os.system("dpkg --list | grep -q 'ii *testapp-ynh-deps '") != 0

    r(conf, "testapp").provision_or_update()

    assert os.system("dpkg --list | grep -q 'ii *nyancat '") == 0
    assert os.system("dpkg --list | grep -q 'ii *sl '") == 0
    assert os.system("dpkg --list | grep -q 'ii *yarn '") == 0
    assert (
        os.system("dpkg --list | grep -q 'ii *lolcat '") != 0
    )  # Lolcat shouldnt be installed yet
    assert os.system("dpkg --list | grep -q 'ii *testapp-ynh-deps '") == 0

    conf["packages"] += ", lolcat"
    r(conf, "testapp").provision_or_update()

    assert os.system("dpkg --list | grep -q 'ii *nyancat '") == 0
    assert os.system("dpkg --list | grep -q 'ii *sl '") == 0
    assert os.system("dpkg --list | grep -q 'ii *yarn '") == 0
    assert os.system("dpkg --list | grep -q 'ii *lolcat '") == 0
    assert os.system("dpkg --list | grep -q 'ii *testapp-ynh-deps '") == 0

    r(conf, "testapp").deprovision()

    assert os.system("dpkg --list | grep -q 'ii *nyancat '") != 0
    assert os.system("dpkg --list | grep -q 'ii *sl '") != 0
    assert os.system("dpkg --list | grep -q 'ii *yarn '") != 0
    assert os.system("dpkg --list | grep -q 'ii *lolcat '") != 0
    assert os.system("dpkg --list | grep -q 'ii *testapp-ynh-deps '") != 0


def test_resource_permissions():
    maindomain = _get_maindomain()
    os.system(f"echo 'domain: {maindomain}' >> /etc/yunohost/apps/testapp/settings.yml")
    os.system("echo 'path: /testapp' >> /etc/yunohost/apps/testapp/settings.yml")

    # A manager object is required to set the label of the app...
    manager = AppResourceManager("testapp", current={}, wanted={"name": "Test App"})
    r = AppResourceClassesByType["permissions"]
    conf = {
        "main": {
            "url": "/",
            "allowed": "visitors"
            # TODO: test protected?
        },
    }

    res = user_permission_list(full=True)["permissions"]
    assert not any(key.startswith("testapp.") for key in res)

    r(conf, "testapp", manager).provision_or_update()

    res = user_permission_list(full=True)["permissions"]
    assert "testapp.main" in res
    assert "visitors" in res["testapp.main"]["allowed"]
    assert res["testapp.main"]["url"] == "/"
    assert "testapp.admin" not in res

    conf["admin"] = {"url": "/admin", "allowed": ""}

    r(conf, "testapp", manager).provision_or_update()

    res = user_permission_list(full=True)["permissions"]

    assert "testapp.main" in list(res.keys())
    assert "visitors" in res["testapp.main"]["allowed"]
    assert res["testapp.main"]["url"] == "/"

    assert "testapp.admin" in res
    assert not res["testapp.admin"]["allowed"]
    assert res["testapp.admin"]["url"] == "/admin"

    conf["admin"]["url"] = "/adminpanel"

    r(conf, "testapp", manager).provision_or_update()

    res = user_permission_list(full=True)["permissions"]

    assert res["testapp.admin"]["url"] == "/adminpanel"

    r(conf, "testapp").deprovision()

    res = user_permission_list(full=True)["permissions"]
    assert "testapp.main" not in res
