import psutil
import logging
from gi.repository import Adw, Gtk, NM, GLib, Gio, PackageKitGlib

from .checks import *
from .packagekit import PackageKit

DEBUG = False

class Card:
  parent = None

  def add_to(self, parent):
    while child := parent.get_adjacent_child(Adw.NavigationDirection.FORWARD):
      parent.remove(child)
      child.destroy()

    parent.append(self)
    parent.set_visible_child(self)
    self.parent = parent

  def wants_forward_button(self):
    return False

  def wants_restart_button(self):
    return False

  def can_navigate_back(self):
    return True

  def forward(self):
    pass

  def activate(self):
    pass

  def destroy(self):
    pass

class ChecksCard(Card, Adw.PreferencesPage):
  def __init__(self, **kwargs):
    super().__init__(**kwargs)
    self.group = Adw.PreferencesGroup(title=_("Checklist:"))
    Adw.PreferencesPage.add(self, self.group)

    self.checks = [
      ConnectionCheck(),
      UpToDateCheck(),
      ExternalPowerCheck(),
      BatteryChargeCheck(),
      DiskSpaceCheck(),
      CustomRepoCheck(),
      UnknownPackagesCheck(),
    ]

    for check in self.checks:
      check.connect("error", lambda obj, msg: ErrorLogCard(msg).add_to(self.parent))
      self.group.add(check)

    Adw.PreferencesPage.add(self, Adw.PreferencesGroup(description=_("Make sure that you have a valid backup of important data before continuing.")))

  def wants_forward_button(self):
    return all([check.checked for check in self.checks]) or DEBUG

  def wants_restart_button(self):
    return any([not check.checked for check in self.checks]) or DEBUG

  def forward(self):
    dialog = Adw.MessageDialog(transient_for=self.get_root(), modal=True, heading=_("Start the upgrade?"), body=_("You're about to start the upgrade process. This may take a long time and cannot be canceled once started. Are you sure you want to continue?"))
    dialog.add_response("cancel", _("Cancel"))
    dialog.add_response("continue", _("Upgrade"))
    dialog.set_response_appearance("continue", Adw.ResponseAppearance.SUGGESTED)
    dialog.set_default_response("cancel")
    dialog.set_close_response("cancel")
    dialog.present()

    dialog.connect("response", self._on_response)

  def _on_response(self, dialog, response):
    if response == "continue":
      for proc in psutil.process_iter(['name']):
        if proc.info['name'] in ['gnome-software', 'plasma-discover', 'DiscoverNotifier']:
          proc.terminate()

      output = subprocess.run(["pkexec", "/usr/libexec/pureos-upgrade/setup-repos"], capture_output=True, text=True)

      if output.returncode != 0:
        logging.error(output.stderr)
        ErrorCard().add_to(self.parent)
        return

      self.parent.get_root().get_application().set_repos_changed(True)

      RefreshListsCard(DownloadCard, precheck=HeldPackagesCheck).add_to(self.parent)

class StatusCard(Card, Adw.Bin):
  def __init__(self, title, description, icon, child, **kwargs):
    super().__init__(**kwargs)
    box = Gtk.CenterBox()
    box.set_center_widget(child)
    self.status = Adw.StatusPage(title=title, description=description, icon_name=icon, child=box, vexpand=True)
    self.set_child(self.status)

class ProgressCard(StatusCard):

  def _on_timer(self, bar):
    if self.percentage <= 0:
      bar.pulse()
    else:
      bar.set_fraction(self.percentage / 100)
    return True

  def _on_progress(self, percentage):
    self.percentage = percentage

  def __init__(self, title=_("Working hard"), description=_("Doing something..."), icon="system-run-symbolic", **kwargs):
    self.percentage = -1
    bar = Gtk.ProgressBar()
    bar.pulse()
    self._timer_id = GLib.timeout_add(64, self._on_timer, bar)
    super().__init__(title, description, icon, bar, **kwargs)

  def add_to(self, parent):
    super().add_to(parent)
    window = self.parent.get_root()
    self.inhibitor = window.get_application().inhibit(window, Gtk.ApplicationInhibitFlags.SUSPEND | Gtk.ApplicationInhibitFlags.LOGOUT, _("Performing an action"))

  def _on_error(self, *args):
    ErrorCard().add_to(self.parent)
    ProgressCard.forward(self)

  def destroy(self):
    GLib.source_remove(self._timer_id)
    self.parent.get_root().get_application().uninhibit(self.inhibitor)

  def can_navigate_back(self):
    return False

  def forward(self):
    if Adw.get_enable_animations(self.parent):
      self.parent.connect_object("notify::child-transition-running", lambda *_: self._remove(), self)
    else:
      self._remove()

  def _remove(self):
    if not self.parent or self.parent.get_child_transition_running():
      return
    self.parent.remove(self)
    self.destroy()
    self.parent = None

class RefreshListsCard(ProgressCard):
  def __init__(self, next_card, precheck = None, **kwargs):
    super().__init__(_("Refreshing package lists"), _("Looking for available updates..."), "view-refresh-symbolic", **kwargs)
    PackageKit().refresh_cache(lambda c, r: self._on_progress(c.get_percentage()), self._on_finished, self._on_error)
    self.next_card = next_card
    self.precheck = precheck

  def _on_finished(self, result):
    self.forward()

  def forward(self):
    if self.precheck and not self.precheck().checked:
      ErrorCard().add_to(self.parent)
    else:
      self.next_card().add_to(self.parent)
    super().forward()

class ErrorLogCard(Card, Adw.Bin):
  def __init__(self, text, **kwargs):
    super().__init__(**kwargs)
    scroll = Gtk.ScrolledWindow()
    textview = Gtk.TextView(editable=False, top_margin=10, left_margin=10, right_margin=10, bottom_margin=10, wrap_mode=Gtk.WrapMode.WORD_CHAR, **kwargs)
    buf = textview.get_buffer()
    buf.set_text(text)
    scroll.set_child(textview)
    self.set_child(scroll)

class ErrorCard(StatusCard):
  def __init__(self, **kwargs):
    btn = Gtk.Button(label=_("Show error log"))
    btn.connect("clicked", lambda _: self.forward())
    super().__init__(_("Upgrade failed"), _("The upgrade process encountered an error and can not continue."), "action-unavailable-symbolic", btn, **kwargs)

  def add_to(self, parent):
    super().add_to(parent)

    app = self.parent.get_root().get_application()

    if app.repos_changed:
      output = subprocess.run(["pkexec", "/usr/libexec/pureos-upgrade/restore-repos"], capture_output=True, text=True)

      if output.returncode != 0:
        logging.error("Could not restore the repository lists to their old state!")
        logging.error(output.stderr)

    handler = app.logHandler
    self.messages = handler.messages
    handler.messages = []
    app.set_repos_changed(False)

  def forward(self):
    ErrorLogCard("\n".join(self.messages)).add_to(self.parent)

class DownloadCard(ProgressCard):
  def __init__(self, **kwargs):
    super().__init__(_("Downloading packages"), _("This may take a while..."), "folder-download-symbolic", **kwargs)
    PackageKit().download_updates(lambda c, r: self._on_progress(c.get_percentage()), self._on_finished, self._on_error)

  def _on_finished(self, result):
    if PackageKit().trigger_offline_upgrade():
      self.forward()
    else:
      self._on_error()

  def forward(self):
    RebootCard().add_to(self.parent)
    super().forward()

class RebootCard(StatusCard):
  def __init__(self, **kwargs):
    btn = Gtk.Button(label=_("Reboot and continue"))
    btn.add_css_class("suggested-action")
    btn.connect("clicked", lambda _: self.forward())
    super().__init__(_("Reboot required"), _("The upgrade process will continue after a reboot.\nKeep the device plugged in during the upgrade."), "system-reboot-symbolic", btn, **kwargs)

  def forward(self):
    win = self.parent.get_root()
    try:
      proxy = Gio.DBusProxy.new_sync(Gio.bus_get_sync(Gio.BusType.SESSION, None), Gio.DBusProxyFlags.NONE, None, "org.gnome.SessionManager", "/org/gnome/SessionManager", "org.gnome.SessionManager", None)
      proxy.call_sync("Reboot", None, Gio.DBusCallFlags.NONE, -1, None)
      win.quit()
    except:
      proxy = Gio.DBusProxy.new_sync(Gio.bus_get_sync(Gio.BusType.SESSION, None), Gio.DBusProxyFlags.NONE, None, "org.kde.LogoutPrompt", "/LogoutPrompt", "org.kde.LogoutPrompt", None)
      proxy.call_sync("promptReboot", None, Gio.DBusCallFlags.NONE, -1, None)
      win.quit()

  def can_navigate_back(self):
    return False

class WelcomeCard(StatusCard):
  def __init__(self, **kwargs):
    btn = Gtk.Button(label=_("Upgrade to PureOS {version}").format(version=11))
    btn.add_css_class("suggested-action")
    btn.connect("clicked", lambda _: self.forward())
    super().__init__(_("PureOS Upgrade"), _("A new version of PureOS is available."), "software-update-urgent-symbolic", btn, **kwargs)

    self.nm = NM.Client.new()
    self.nm.check_connectivity_async(None, lambda *_: None, None)

    if DEBUG:
      return

    distro = PackageKitGlib.get_distro_id()
    if distro:
      distro = distro.split(';')
    else:
      distro = ["", ""]

    if distro[0] != "pureos":
      self.status.set_description(_("Unsupported system."))
      btn.hide()
    elif distro[1] != "10":
      self.status.set_description(_("Unsupported version."))
      btn.hide()

  def forward(self):
    if self.nm.get_connectivity() not in [NM.ConnectivityState.FULL, NM.ConnectivityState.UNKNOWN] or self.nm.get_state() not in [NM.State.CONNECTED_GLOBAL, NM.State.UNKNOWN]:
      ChecksCard().add_to(self.parent)
      return

    if self.nm.get_metered() not in [NM.Metered.YES, NM.Metered.GUESS_YES]:
      self._on_response(None, "continue")
      return

    dialog = Adw.MessageDialog(transient_for=self.get_root(), modal=True, heading=_("Metered connection"), body=_("Downloading the upgrades may incur excessive costs on metered connections. Are you sure you want to continue?"))
    dialog.add_response("cancel", _("Cancel"))
    dialog.add_response("continue", _("Download"))
    dialog.set_response_appearance("continue", Adw.ResponseAppearance.SUGGESTED)
    dialog.set_default_response("cancel")
    dialog.set_close_response("cancel")
    dialog.present()

    dialog.connect("response", self._on_response)

  def _on_response(self, dialog, response):
    if response == "continue":
      RefreshListsCard(ChecksCard).add_to(self.parent)

