import shutil
import subprocess
import time
from urllib.error import URLError, HTTPError
from urllib.request import urlopen

import mwclient
import pytest


_CONTAINER_RUNTIME = None
if shutil.which("podman"):
    _CONTAINER_RUNTIME = "podman"
elif shutil.which("docker"):
    _CONTAINER_RUNTIME = "docker"
else:
    raise RuntimeError("Neither podman nor docker is installed")

@pytest.fixture(
    scope="class",
    params=[("latest", "5002"), ("legacy", "5003"), ("lts", "5004")],
    ids=["latest", "legacy", "lts"]
)
def site(request):
    """
    Run a mediawiki container for the duration of the class, yield
    a Site instance for it, then clean it up on exit. This is
    parametrized so we get three containers and run the tests three
    times for three mediawiki releases. We use podman because it's
    much easier to use rootless than docker.
    """
    (tag, port) = request.param
    container = f"mwclient-{tag}"
    # create the container, using upstream's official image. see
    # https://hub.docker.com/_/mediawiki
    args = (_CONTAINER_RUNTIME, "run", "--name", container, "-p", f"{port}:80",
            "-d", f"docker.io/library/mediawiki:{tag}")
    subprocess.run(args)
    # configure the wiki far enough that we can use the API. if you
    # use this interactively the CSS doesn't work, I don't know why,
    # don't think it really matters
    args = (_CONTAINER_RUNTIME, "exec", container, "runuser", "-u", "www-data", "--",
            "php", "/var/www/html/maintenance/install.php", "--server",
            f"http://localhost:{port}", "--dbtype", "sqlite", "--pass", "weakpassword",
            "--dbpath", "/var/www/data", "mwclient-test-wiki", "root")
    subprocess.run(args)
    # create a regular user
    args = (_CONTAINER_RUNTIME, "exec", container, "runuser", "-u", "www-data", "--",
            "php", "/var/www/html/maintenance/createAndPromote.php", "testuser",
            "weakpassword")
    subprocess.run(args)
    # create an admin user
    args = (_CONTAINER_RUNTIME, "exec", container, "runuser", "-u", "www-data", "--",
            "php", "/var/www/html/maintenance/createAndPromote.php", "sysop",
            "weakpassword", "--bureaucrat", "--sysop", "--interface-admin")
    subprocess.run(args)
    # create a bot user
    args = (_CONTAINER_RUNTIME, "exec", container, "runuser", "-u", "www-data", "--",
            "php", "/var/www/html/maintenance/createAndPromote.php", "testbot",
            "weakpassword", "--bot")
    subprocess.run(args)
    # disable anonymous editing (we can't use redirection via podman
    # exec for some reason, so we use sed)
    args = (_CONTAINER_RUNTIME, "exec", container, "runuser", "-u", "www-data", "--",
            "sed", "-i", r"$ a\$wgGroupPermissions['*']['edit'] = false;",
            "/var/www/html/LocalSettings.php")
    subprocess.run(args)
    # allow editing by users
    args = (_CONTAINER_RUNTIME, "exec", container, "runuser", "-u", "www-data", "--",
            "sed", "-i", r"$ a\$wgGroupPermissions['user']['edit'] = true;",
            "/var/www/html/LocalSettings.php")
    subprocess.run(args)
    # block until the server is actually running, up to 30 seconds
    start = int(time.time())
    resp = None
    while not resp:
        try:
            resp = urlopen(f"http://localhost:{port}")
        except (ValueError, URLError, HTTPError) as err:
            if int(time.time()) - start > 30:
                print("Waited more than 30 seconds for server to start!")
                raise err
            else:
                time.sleep(0.1)
    # set up mwclient.site instance and yield it
    yield mwclient.Site(f"localhost:{port}", path="/", scheme="http", force_login=False)
    # -t=0 just hard stops it immediately, saves time
    args = (_CONTAINER_RUNTIME, "stop", "-t=0", container)
    subprocess.run(args)
    args = (_CONTAINER_RUNTIME, "rm", container)
    subprocess.run(args)


class TestAnonymous:
    def test_page_load(self, site):
        """Test we can read a page from the sites."""
        pg = site.pages["Main_Page"]
        text = pg.text()
        assert text.startswith("<strong>MediaWiki has been installed")

    def test_page_create(self, site):
        """Test we get expected error if we try to create a page."""
        pg = site.pages["Anonymous New Page"]
        with pytest.raises(mwclient.errors.ProtectedPageError):
            pg.edit("Hi I'm a new page", "create new page")

class TestLogin:
    def test_login_wrong_password(self, site):
        """Test we raise correct error for login() with wrong password."""
        assert not site.logged_in
        with pytest.raises(mwclient.errors.LoginError):
            site.login(username="testuser", password="thisiswrong")
        assert not site.logged_in

    def test_login(self, site):
        """
        Test we can log in to the sites with login() and do authed
        stuff.
        """
        site.login(username="testuser", password="weakpassword")
        assert site.logged_in
        # test we can create a page
        pg = site.pages["Authed New Page"]
        pg.edit("Hi I'm a new page", "create new page")
        # we have to reinit because of Page.exists
        # https://github.com/mwclient/mwclient/issues/354
        pg = site.pages["Authed New Page"]
        assert pg.text() == "Hi I'm a new page"
        # test we can move it
        ret = pg.move("Authed Moved Page")
        pg = site.pages["Authed Moved Page"]
        assert pg.text() == "Hi I'm a new page"

    def test_page_delete(self, site):
        """Test we can login, create and delete a page as sysop."""
        site.login(username="sysop", password="weakpassword")
        pg = site.pages["Sysop New Page"]
        pg.edit("Hi I'm a new page", "create new page")
        pg = site.pages["Sysop New Page"]
        assert pg.text() == "Hi I'm a new page"
        assert pg.exists == True
        pg.delete()
        pg = site.pages["Sysop New Page"]
        assert pg.text() == ""
        assert pg.exists == False


class TestClientLogin:
    def test_clientlogin_wrong_password(self, site):
        """Test we raise correct error for clientlogin() with wrong password."""
        with pytest.raises(mwclient.errors.LoginError):
            site.clientlogin(username="testuser", password="thisiswrong")
        assert not site.logged_in

    def test_clientlogin(self, site):
        """
        Test we can log in to the site with clientlogin() and
        create a page.
        """
        site.clientlogin(username="testuser", password="weakpassword")
        assert site.logged_in
        pg = site.pages["Anonymous New Page"]
        pg.edit("Hi I'm a new page", "create new page")
        pg = site.pages["Anonymous New Page"]
        assert pg.text() == "Hi I'm a new page"