#
#  Copyright (c) 2021 Canonical Ltd.
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License as
#  published by the Free Software Foundation; either version 2 of the
#  License, or (at your option) any later version.
#
#  This program 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 General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
#  USA

import dbus
import os
from gettext import gettext as _
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GdkPixbuf, Gio, Gtk
from softwareproperties.gtk.utils import current_distro, is_dark_theme

from .DialogUaAttach import DialogUaAttach
from .DialogUaDetach import DialogUaDetach
from .DialogUaFipsEnable import DialogUaFipsEnable

class UaService:
    def __init__(self, bus_object, name, entitled, status):
        self.bus_object = bus_object
        self.name = name
        self.entitled = entitled
        self.status = status
        self.request_in_progress = False

class UbuntuProPage(object):

    def __init__(self, parent):
        self._parent = parent

        self.stack_ua_attach = parent.stack_ua_attach
        self.box_ua_attached = parent.box_ua_attached
        self.box_ua_unattached = parent.box_ua_unattached
        self.stack_ua_main = parent.stack_ua_main
        self.box_ua_options = parent.box_ua_options
        self.box_ua_fips_setup = parent.box_ua_fips_setup
        self.switch_ua_esm_infra = parent.switch_ua_esm_infra
        self.label_ua_esm_infra = parent.label_ua_esm_infra
        self.label_ua_esm_infra_error = parent.label_ua_esm_infra_error
        self.label_ua_esm_infra_error_messages = {
            "enable": _("Could not enable ESM Infra. Please try again."),
            "disable": _("Could not disable ESM Infra. Please try again."),
        }
        self.switch_ua_esm_apps = parent.switch_ua_esm_apps
        self.label_ua_esm_apps = parent.label_ua_esm_apps
        self.label_ua_esm_apps_error = parent.label_ua_esm_apps_error
        self.label_ua_esm_apps_error_messages = {
            "enable": _("Could not enable ESM Apps. Please try again."),
            "disable": _("Could not disable ESM Apps. Please try again."),
        }
        self.switch_ua_livepatch = parent.switch_ua_livepatch
        self.checkbutton_livepatch_topbar = parent.checkbutton_livepatch_topbar
        self.label_ua_livepatch = parent.label_ua_livepatch
        self.label_ua_livepatch_error = parent.label_ua_livepatch_error
        self.label_ua_livepatch_error_messages = {
            "enable": _("Could not enable Livepatch. Please try again."),
            "disable": _("Could not disable Livepatch. Please try again."),
        }
        self.button_ua_fips = parent.button_ua_fips
        self.label_ua_fips_status = parent.label_ua_fips_status
        self.label_ua_fips_description = parent.label_ua_fips_description
        self.button_ua_usg = parent.button_ua_usg
        self.label_ua_usg_button = parent.label_ua_usg_button
        self.label_ua_usg_status = parent.label_ua_usg_status
        self.label_ua_usg_description = parent.label_ua_usg_description

        if is_dark_theme(self.stack_ua_attach):
            ubuntu_pro_logo = GdkPixbuf.Pixbuf.new_from_file_at_scale(os.path.join(parent.datadir, 'ubuntu-pro-logo-dark.svg'), -1, 50, True)
        else:
            ubuntu_pro_logo = GdkPixbuf.Pixbuf.new_from_file_at_scale(os.path.join(parent.datadir, 'ubuntu-pro-logo.svg'), -1, 50, True)
        parent.image_ubuntu_pro_logo.set_from_pixbuf(ubuntu_pro_logo)

        # Display the tab on init which is only on LTS series
        self.stack_ua_main.set_visible(True)

        parent.button_ua_attach.connect('clicked', self.on_button_ua_attach_clicked)
        parent.button_ua_detach.connect('clicked', self.on_button_ua_detach_clicked)
        self.on_ua_esm_infra_changed_handler = self.switch_ua_esm_infra.connect('notify::active', self.on_ua_esm_infra_changed)
        self.on_ua_esm_apps_changed_handler = self.switch_ua_esm_apps.connect('notify::active', self.on_ua_esm_apps_changed)
        self.on_ua_livepatch_changed_handler = self.switch_ua_livepatch.connect('notify::active', self.on_ua_livepatch_changed)
        parent.button_ua_fips.connect('clicked', self.on_button_ua_fips_clicked)
        parent.button_ua_usg.connect('clicked', self.on_button_ua_usg_clicked)
        parent.expander_compliance_and_hardening.connect('notify::expanded', self.on_compliance_and_hardening_expand_changed)

        # Set date dependent labels
        distro = current_distro()
        if distro.eol_esm is not None:
            eol_year = distro.eol_esm.year
            self.label_ua_esm_infra.set_markup(_('<b>ESM Infra</b> provides security updates for over 2,300 Ubuntu Main packages until %d.') % eol_year)
            self.label_ua_esm_apps.set_markup(_('<b>ESM Apps</b>; provides security updates for over 23,000 Ubuntu Universe packages until %d.') % eol_year)
        else:
            self.label_ua_esm_infra.set_markup(_('<b>ESM Infra</b> provides security updates for over 2,300 Ubuntu Main packages.'))
            self.label_ua_esm_apps.set_markup(_('<b>ESM Apps</b>; provides security updates for over 23,000 Ubuntu Universe packages.'))

        self.update_notifier_settings = None
        source = Gio.SettingsSchemaSource.get_default()
        if source is not None:
            schema = source.lookup('com.ubuntu.update-notifier', True)
            if schema is not None:
                settings = Gio.Settings.new('com.ubuntu.update-notifier')
                if schema.has_key('show-livepatch-status-icon'):
                    self.update_notifier_settings = settings

        if self.update_notifier_settings is not None:
            self.on_checkbutton_livepatch_topbar_toggled_handler = self.checkbutton_livepatch_topbar.connect('toggled', self.on_checkbutton_livepatch_topbar_toggled)
            self.on_update_notifier_settings_changed_handler = self.update_notifier_settings.connect('changed::show-livepatch-status-icon', self.on_update_notifier_settings_changed)
            self.on_update_notifier_settings_changed(self.update_notifier_settings, 'show-livepatch-status-icon')

        bus = dbus.SystemBus()
        self.ua_object = bus.get_object('com.canonical.UbuntuAdvantage', '/com/canonical/UbuntuAdvantage/Manager')

        # Monitor services
        self.attached = False
        self.services = {}
        def on_interfaces_added(path, interfaces_and_properties):
            if path == '/com/canonical/UbuntuAdvantage/Manager':
                self.attached = interfaces_and_properties['com.canonical.UbuntuAdvantage.Manager']['Attached']
            elif path.startswith('/com/canonical/UbuntuAdvantage/Services/'):
                properties = interfaces_and_properties.get('com.canonical.UbuntuAdvantage.Service')
                bus_object = bus.get_object('com.canonical.UbuntuAdvantage', path)
                self.services[path] = UaService(bus_object, properties['Name'], properties['Entitled'], properties['Status'])
            self.update_status()
        def on_interfaces_removed(path, interfaces):
            if 'com.canonical.UbuntuAdvantage.Service' in interfaces:
                self.services.pop(path)
            self.update_status()
        def on_properties_changed(interface, changed_properties, invalidated_properties, path):
            def get_property(properties, name, default):
                value = properties.get(name)
                if value is None:
                    value = default
                return value
            if path == '/com/canonical/UbuntuAdvantage/Manager' and interface == 'com.canonical.UbuntuAdvantage.Manager':
                self.attached = get_property(changed_properties, 'Attached', self.attached)
            elif path.startswith('/com/canonical/UbuntuAdvantage/Services/') and interface == 'com.canonical.UbuntuAdvantage.Service':
                service = self.services[path]
                service.entitled = get_property(changed_properties, 'Entitled', service.entitled)
                service.status = get_property(changed_properties, 'Status', service.status)
            self.update_status()
        object_manager_object = bus.get_object('com.canonical.UbuntuAdvantage', '/')
        object_manager_object.connect_to_signal('InterfacesAdded', on_interfaces_added, dbus_interface='org.freedesktop.DBus.ObjectManager')
        object_manager_object.connect_to_signal('InterfacesRemoved', on_interfaces_removed, dbus_interface='org.freedesktop.DBus.ObjectManager')
        bus.add_signal_receiver(on_properties_changed, bus_name='com.canonical.UbuntuAdvantage', signal_name='PropertiesChanged', dbus_interface='org.freedesktop.DBus.Properties', path_keyword='path')
        objects = object_manager_object.GetManagedObjects(dbus_interface='org.freedesktop.DBus.ObjectManager')
        for path in objects:
            on_interfaces_added(path, objects[path])

    def get_service(self, name):
        for service in self.services.values():
            if service.name == name:
                return service
        return None

    def update_status(self):
        if self.attached:
            self.stack_ua_attach.set_visible_child(self.box_ua_attached)
        else:
            self.stack_ua_attach.set_visible_child(self.box_ua_unattached)

        def entitled_to_service(service):
            return service is not None and service.entitled == 'yes'
        def service_request_in_progress(service):
            return service is not None and service.request_in_progress
        def service_is_enabled(service):
            return service is not None and service.status == 'enabled'

        def update_switch(switch, service, handler):
            if service is not None and service.request_in_progress:
                return
            switch.handler_block(handler)
            switch.set_active(service_is_enabled(service))
            switch.handler_unblock(handler)

        esm_infra_service = self.get_service('esm-infra')
        for widget in [self.switch_ua_esm_infra, self.label_ua_esm_infra, self.label_ua_esm_infra_error]:
            widget.set_sensitive(entitled_to_service(esm_infra_service) and not service_request_in_progress(esm_infra_service))
        update_switch(self.switch_ua_esm_infra, esm_infra_service, self.on_ua_esm_infra_changed_handler)

        esm_apps_service = self.get_service('esm-apps')
        for widget in [self.switch_ua_esm_apps, self.label_ua_esm_apps, self.label_ua_esm_apps_error]:
            widget.set_sensitive(entitled_to_service(esm_apps_service) and not service_request_in_progress(esm_apps_service))
        update_switch(self.switch_ua_esm_apps, esm_apps_service, self.on_ua_esm_apps_changed_handler)

        livepatch_service = self.get_service('livepatch')
        for widget in [self.switch_ua_livepatch, self.label_ua_livepatch, self.label_ua_livepatch_error]:
            widget.set_sensitive(entitled_to_service(livepatch_service) and not service_request_in_progress(livepatch_service))
        update_switch(self.switch_ua_livepatch, livepatch_service, self.on_ua_livepatch_changed_handler)
        self.checkbutton_livepatch_topbar.set_sensitive(self.update_notifier_settings is not None and self.switch_ua_livepatch.get_active())

        fips_service = self.get_service('fips')
        fips_updates_service = self.get_service('fips-updates')
        fips_in_progress = service_request_in_progress(fips_service) or service_request_in_progress(fips_updates_service)
        self.button_ua_fips.set_sensitive(entitled_to_service(fips_service) and not fips_in_progress)
        if fips_in_progress:
            self.stack_ua_main.set_visible_child(self.box_ua_fips_setup)
        else:
            self.stack_ua_main.set_visible_child(self.box_ua_options)

        usg_service = self.get_service('usg')
        if not service_request_in_progress(usg_service):
            if service_is_enabled(usg_service):
                self.label_ua_usg_button.set_label(_('Disable _USG'))
            else:
                self.label_ua_usg_button.set_label(_('Enable _USG'))
        self.button_ua_usg.set_sensitive(entitled_to_service(usg_service) and not service_request_in_progress(usg_service))

    def on_button_ua_attach_clicked(self, button):
        dialog = DialogUaAttach(self._parent.window_main, self._parent.datadir, self.ua_object)
        dialog.run()

    def on_button_ua_detach_clicked(self, button):
        dialog = DialogUaDetach(self._parent.window_main, self._parent.datadir, self.ua_object)
        dialog.run()

    def set_service_enabled(self, service_name, enabled, error_label, error_label_messages):
        if error_label is not None:
            error_label.set_visible(False)
        service = self.get_service(service_name)
        if service is None:
            return
        def on_reply():
            service.request_in_progress = False
            self.update_status()
        def on_error(error):
            print(error)
            if error_label is not None:
                error_label.set_visible(True)
                if enabled:
                    error_label.set_label(error_label_messages["enable"])
                else:
                    error_label.set_label(error_label_messages["disable"])
            service.request_in_progress = False
            self.update_status()
        if enabled:
            service.bus_object.Enable(reply_handler=on_reply, error_handler=on_error, dbus_interface='com.canonical.UbuntuAdvantage.Service', timeout=600)
        else:
            service.bus_object.Disable(reply_handler=on_reply, error_handler=on_error, dbus_interface='com.canonical.UbuntuAdvantage.Service', timeout=600)
        service.request_in_progress = True
        self.update_status()

    def on_ua_esm_infra_changed(self, switch, param):
        self.set_service_enabled('esm-infra', self.switch_ua_esm_infra.get_active(), self.label_ua_esm_infra_error, self.label_ua_esm_infra_error_messages)

    def on_ua_esm_apps_changed(self, switch, param):
        self.set_service_enabled('esm-apps', self.switch_ua_esm_apps.get_active(), self.label_ua_esm_apps_error, self.label_ua_esm_apps_error_messages)

    def on_ua_livepatch_changed(self, switch, param):
        self.set_service_enabled('livepatch', self.switch_ua_livepatch.get_active(), self.label_ua_livepatch_error, self.label_ua_livepatch_error_messages)

    def on_checkbutton_livepatch_topbar_toggled(self, button):
        self.update_notifier_settings.handler_block(self.on_update_notifier_settings_changed_handler)
        self.update_notifier_settings.set_boolean('show-livepatch-status-icon', self.checkbutton_livepatch_topbar.get_active())
        self.update_notifier_settings.handler_unblock(self.on_update_notifier_settings_changed_handler)

    def on_update_notifier_settings_changed(self, settings, key):
        self.checkbutton_livepatch_topbar.handler_block(self.on_checkbutton_livepatch_topbar_toggled_handler)
        self.checkbutton_livepatch_topbar.set_active(self.update_notifier_settings.get_boolean('show-livepatch-status-icon'))
        self.checkbutton_livepatch_topbar.handler_unblock(self.on_checkbutton_livepatch_topbar_toggled_handler)

    def on_button_ua_fips_clicked(self, button):
        dialog = DialogUaFipsEnable(self._parent.window_main, self._parent.datadir, self.ua_object)
        service_name = dialog.run()
        if service_name is None:
            return

        dialog = Gtk.MessageDialog(parent=self._parent.window_main,
                                   flags=Gtk.DialogFlags.MODAL,
                                   type=Gtk.MessageType.QUESTION,
                                   message_format=None)
        dialog.add_button(_('No, go back'), Gtk.ResponseType.CANCEL)
        dialog.add_button(_('Enable FIPS'), Gtk.ResponseType.OK)
        dialog.set_markup(_('Enabling FIPS could take a few minutes. This action cannot be reversed. Are you sure you want to enable FIPS?'))
        result = dialog.run()
        dialog.destroy()
        if result != Gtk.ResponseType.OK:
            return

        self.set_service_enabled(service_name, True, None, None)

    def on_button_ua_usg_clicked(self, button):
        service = self.get_service('usg')
        is_enabled = service is not None and service.status == 'enabled'
        self.set_service_enabled('usg', not is_enabled, None, None)

    def on_compliance_and_hardening_expand_changed(self, widget, param):
        self._parent.window_main.resize(1, 1)
