#!/usr/bin/python
# -*- coding: utf-8 -*-

# This file is part of Cockpit.
#
# Copyright (C) 2015 Red Hat, Inc.
#
# Cockpit is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Cockpit is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Cockpit; If not, see <http://www.gnu.org/licenses/>.

import parent
from testlib import *
import json
import os
import sys
import unittest
import testinfra

INSTALL_RPMS = [
    "empty-1-0.noarch",
    "cockpit-ostree-wip-0.1.noarch",
    "cockpit-kubernetes-wip-2.noarch",
]

REPO_LOCATION = "/var/local-repo"
CHECKOUT_LOCATION = "/var/local-tree"
RPM_LOCATION = "/usr/share/rpm"

def start_trivial_httpd(m):
    remote = m.execute("ostree remote show-url local")
    parts = remote.strip().split(":")

    # If ostree supports rpm-ostreed then it support the -P option
    args = [parts[-1], REPO_LOCATION]
    m.execute("ostree trivial-httpd -d -P {0} {1} ".format(*args))


def generate_new_commit(m, pkg_to_remove):
    # Make one change of each type to a new rpm tree
    branch = m.execute("ostree refs --repo={0}".format(REPO_LOCATION))

    m.upload(["verify/files/{0}.rpm".format(k) for k in INSTALL_RPMS],
              "/home/admin/")

    # move /usr/etc to /etc, makes rpm installs easier
    rpm_etc = os.path.join(CHECKOUT_LOCATION, "etc")
    usr_etc = os.path.join(CHECKOUT_LOCATION, "usr", "etc")
    m.execute("mv {0} {1}".format(usr_etc, rpm_etc))

    # Remove a package
    rpm_args = [CHECKOUT_LOCATION, RPM_LOCATION, pkg_to_remove]
    m.execute("rpm -e --root {0} --dbpath {1} {2}".format(*rpm_args))

    # Install our dummy packages, dbonly
    rpm_args[-1] = ' '.join(["{0}.rpm".format(os.path.join("/home/admin", x)) \
                                for x in INSTALL_RPMS])
    m.execute("rpm -U --oldpackage --root {0} --dbpath {1} --justdb {2}".format(*rpm_args))

    # move /etc back to /usr/etc to
    m.execute("mv {0} {1}".format(rpm_etc, usr_etc))

    commit_args = [REPO_LOCATION, branch.strip(), CHECKOUT_LOCATION]
    command = "ostree commit -s cockpit-tree2 --repo {0} -b {1} --add-metadata-string version=cockpit-base.2 --tree=dir={2}"
    m.execute(command.format(*commit_args))

@unittest.skipIf("atomic" not in os.environ.get("TEST_OS", ""), "Skipping check-ostree on non atomic systems.")
class OstreeRestartCase(MachineCase):

    def setUp(self):
        # we need a static ip for this test becuase we restart
        MachineCase.setUp(self, macaddr='52:54:00:9e:00:F5')

    def switch_to_packages(self, b, sel, required_classes):
        b.wait_not_visible("{0} div.packages".format(sel))
        b.wait_present('{0} ul'.format(sel))
        b.click('{0} ul li a:contains("Packages")'.format(sel))
        b.wait_visible("{0} div.packages".format(sel))
        for c in required_classes:
            b.wait_present("{0} div.packages dl.{1}".format(sel, c))

    def check_change_counts(self, b, sel):
        for k in ['adds', 'removes', 'updates', 'downgrades']:
            b.wait_present("{0} dd.{1}".format(sel, k))
            b.wait_text("{0} dd.{1}".format(sel, k),
                        "1 package")

    def testOstree(self):
        b = self.browser
        m = self.machine

        docker_pkg = m.execute("rpm -qa | grep cockpit-docker").strip()

        # HACK: https://github.com/candlepin/subscription-manager/issues/1404
        m.execute("systemctl disable rhsmcertd || true")
        m.execute("systemctl stop rhsmcertd || true")

        cockpit_shell = m.execute("rpm -qa | grep cockpit-ostree").strip()
        cockpit_kube = m.execute("rpm -qa | grep cockpit-kubernetes").strip()

        self.login_and_go("/updates")
        b.enter_page("/updates")

        if "rhel-atomic" in m.image:
            name = "rhel-atomic-host"
        elif 'continuous' in m.image:
            name = 'centos-atomic-host'
        else:
            name = "fedora-atomic"

        # Check current and rollback target
        b.wait_present('table.listing-ct')
        b.wait_text('table.listing-ct tbody:nth-child(3) div.listing-ct-head h3', name + " cockpit-base.1")
        b.wait_present('table.listing-ct tbody:nth-child(3) i.fa-check-circle-o')
        b.wait_present('table.listing-ct tbody:nth-child(3).active')
        b.wait_in_text('table.listing-ct tbody:nth-child(3) div.listing-ct-status', "Running")
        b.wait_not_present("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button")

        b.wait_visible("table.listing-ct tbody:nth-child(3) div.tree")
        b.wait_in_text("table.listing-ct tbody:nth-child(3) dd.os", name)
        b.wait_in_text("table.listing-ct tbody:nth-child(3) dd.version", "cockpit-base.1")
        self.switch_to_packages(b, "table.listing-ct tbody:nth-child(3)",
                                ['rpms-col1', 'rpms-col2'])
        b.wait_in_text("table.listing-ct tbody:nth-child(3) div.packages", docker_pkg)
        for pkg in INSTALL_RPMS:
            b.wait_not_in_text("table.listing-ct tbody:nth-child(3) div.packages", pkg)

        b.wait_not_in_text('table.listing-ct tbody:nth-child(4) div.listing-ct-head h3', "cockpit")
        b.wait_present('table.listing-ct tbody:nth-child(4) i.fa-circle')
        b.wait_in_text('table.listing-ct tbody:nth-child(4) div.listing-ct-status', "Available")
        b.wait_in_text("table.listing-ct tbody:nth-child(4) div.listing-ct-actions button", "Roll back")
        b.wait_in_text("table.listing-ct tbody:nth-child(4) dd.os", name)
        b.wait_visible("table.listing-ct tbody:nth-child(4) div.tree")
        b.wait_not_visible("table.listing-ct tbody:nth-child(4) div.packages")

        # Check for new commit, get error
        b.wait_present('table.listing-ct tbody:nth-child(2) i.fa-caret-up')
        b.wait_in_text("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button", "Check for updates")
        b.wait_not_present('table.listing-ct tbody:nth-child(2) div.alert')
        b.click("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button")
        b.wait_present('table.listing-ct tbody:nth-child(2) div.alert')
        b.wait_present("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button.enabled")

        # Serve repo
        start_trivial_httpd(m)

        # Check for new commit
        b.click("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button")
        b.wait_present("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button.disabled")
        b.wait_present("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button.enabled")

        # Generate new commit
        generate_new_commit(m, docker_pkg)

        # Check again have update data
        b.click("table.listing-ct tbody:nth-child(2) div.listing-ct-actions button")
        b.wait_present('table.listing-ct tbody:nth-child(3) i.fa-circle')
        b.wait_in_text('table.listing-ct tbody:nth-child(3) div.listing-ct-status', "Available")
        b.wait_in_text("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button", "Update")
        b.wait_present('table.listing-ct tbody:nth-child(4) i.fa-check-circle-o')

        # Check update data
        b.wait_visible("table.listing-ct tbody:nth-child(3) div.tree")
        b.wait_in_text("table.listing-ct tbody:nth-child(3) dd.os", name)
        b.wait_in_text("table.listing-ct tbody:nth-child(3) dd.version", "cockpit-base.2")
        self.check_change_counts(b, "table.listing-ct tbody:nth-child(3)")

        self.switch_to_packages(b, "table.listing-ct tbody:nth-child(3)",
                                ['upgrades', 'downgrades',
                                 'additions', 'removals'])

        b.wait_text("table.listing-ct tbody:nth-child(3) dl.upgrades dd", "cockpit-kubernetes-wip-2.noarch")
        b.wait_text("table.listing-ct tbody:nth-child(3) dl.downgrades dd", "cockpit-ostree-wip-0.1.noarch")
        b.wait_text("table.listing-ct tbody:nth-child(3) dl.additions dd", "empty-1-0.noarch")
        b.wait_text("table.listing-ct tbody:nth-child(3) dl.removals dd", docker_pkg)

        # Force an error
        pid = m.execute('ps -C ostree -o pid h')
        m.execute('kill {0}'.format(pid))
        b.wait_not_present('table.listing-ct tbody:nth-child(3) div.listing-ct-error')
        b.click("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button")
        b.wait_present("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button.disabled")
        b.wait_present("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button.enabled")
        b.wait_present('table.listing-ct tbody:nth-child(3) div.listing-ct-error')
        start_trivial_httpd(m)

        # Apply update
        b.click("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button")
        b.wait_present("table.listing-ct tbody:nth-child(3) div.ostree-progress")
        b.wait_not_present('table.listing-ct tbody:nth-child(3) div.listing-ct-error')

        b.switch_to_top()
        with b.wait_timeout(120):
            b.wait_visible(".curtains-ct")

        b.wait_in_text(".curtains-ct h1", "Disconnected")
        m.wait_reboot()
        m.start_cockpit()
        b.reload()
        b.login_and_go("/updates")
        m.reset_reboot_flag()

        # After reboot, check commit
        b.wait_present('table.listing-ct')
        b.wait_text('table.listing-ct tbody:nth-child(3) div.listing-ct-head h3', name + " cockpit-base.2")
        b.wait_present('table.listing-ct tbody:nth-child(3) i.fa-check-circle-o')
        b.wait_present('table.listing-ct tbody:nth-child(3) div.listing-ct-panel,active')
        b.wait_in_text('table.listing-ct tbody:nth-child(3) div.listing-ct-status', "Running")
        b.wait_not_present("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button")
        self.switch_to_packages(b, "table.listing-ct tbody:nth-child(3)",
                                ['rpms-col1', 'rpms-col2'])
        b.wait_not_in_text("table.listing-ct tbody:nth-child(3) div.packages", docker_pkg)
        for pkg in INSTALL_RPMS:
            b.wait_in_text("table.listing-ct tbody:nth-child(3) div.packages", pkg)

        b.wait_text('table.listing-ct tbody:nth-child(4) div.listing-ct-head h3', name + " cockpit-base.1")
        b.wait_present('table.listing-ct tbody:nth-child(4) i.fa-circle')
        b.wait_in_text('table.listing-ct tbody:nth-child(4) div.listing-ct-status', "Available")
        b.wait_in_text("table.listing-ct tbody:nth-child(4) div.listing-ct-actions button", "Roll back")
        b.wait_in_text("table.listing-ct tbody:nth-child(4) dd.os", name)
        self.check_change_counts(b, "table.listing-ct tbody:nth-child(4)")
        self.switch_to_packages(b, "table.listing-ct tbody:nth-child(4)",
                                ['upgrades', 'downgrades',
                                 'additions', 'removals'])

        b.wait_text("table.listing-ct tbody:nth-child(4) dl.upgrades dd", cockpit_shell)
        b.wait_text("table.listing-ct tbody:nth-child(4) dl.downgrades dd", cockpit_kube)
        b.wait_text("table.listing-ct tbody:nth-child(4) dl.additions dd", docker_pkg)
        b.wait_text("table.listing-ct tbody:nth-child(4) dl.removals dd", "empty-1-0.noarch")

        # Rollback
        b.wait_present("button:contains('Roll back')");
        b.click("table.listing-ct tbody:nth-child(4) div.listing-ct-actions button")
        b.wait_present("table.listing-ct tbody:nth-child(4) div.ostree-progress")

        b.switch_to_top()
        with b.wait_timeout(120):
            b.wait_visible(".curtains-ct")

        b.wait_in_text(".curtains-ct h1", "Disconnected")
        m.wait_reboot()
        m.start_cockpit()
        b.reload()
        b.login_and_go("/updates")
        m.reset_reboot_flag()

        # After reboot upgrade but no rollback target
        b.wait_present('table.listing-ct tbody:nth-child(3) i.fa-circle')
        b.wait_in_text("table.listing-ct tbody:nth-child(3) div.listing-ct-actions button", "Update")
        b.wait_present('table.listing-ct tbody:nth-child(4) i.fa-check-circle-o')
        b.wait_not_present('table.listing-ct tbody:nth-child(5)')
        b.wait_not_present("button:contains('Roll back')");

        self.allow_restart_journal_messages()


@unittest.skipIf("atomic" not in os.environ.get("TEST_OS", ""), "Skipping check-ostree on non atomic systems.")
class OstreeCase(MachineCase):

    def testPermission(self):
        m = self.machine
        b = self.browser

        # HACK: https://github.com/candlepin/subscription-manager/issues/1404
        m.execute("systemctl disable rhsmcertd || true")
        m.execute("systemctl stop rhsmcertd || true")

        # Create a user
        m.execute("useradd user -c 'User' || true")
        m.execute("echo foobar | passwd --stdin user")

        # login
        m.start_cockpit()
        b.open("/updates")
        b.wait_visible("#login")
        b.set_val("#login-user-input", "user")
        b.set_val("#login-password-input", "foobar")
        b.click('#login-button')

        b.enter_page("/updates")

        b.wait_present(".curtains-ct")
        b.wait_in_text(".curtains-ct", "Not authorized")
        self.assertIn("Reconnect", b.text(".blank-slate-pf-main-action button"))
        self.allow_authorize_journal_messages()

if __name__ == "__main__":
    test_main()
