import apt_pkg
import apt.cache
import aptsources.sourceslist
import subprocess
import logging
from gi.repository import Gtk, Adw, NM, UPowerGlib, Gio, GObject

from .packagekit import PackageKit

# FIXME: checks need to resolve synchronously at the moment

class Check(Adw.ActionRow):
  checked = False

  @GObject.Signal(arg_types=(str,))
  def error(self, msg):
    pass

  def __init__(self, title, **kwargs):
    super().__init__(title=title, icon_name="checkbox-symbolic", **kwargs)

  def check(self):
    self.set_icon_name("checkbox-checked-symbolic")
    self.set_subtitle("")
    self.checked = True
    return self

  def uncheck(self):
    self.add_css_class("error")
    self.checked = False
    return self

class ConnectionCheck(Check):
  def __init__(self):
    super().__init__(_("Connected to the Internet"), subtitle=_("Make sure you're online"))

    nm = NM.Client.new()
    if nm.get_connectivity() not in [NM.ConnectivityState.FULL, NM.ConnectivityState.UNKNOWN] or nm.get_state() not in [NM.State.CONNECTED_GLOBAL, NM.State.UNKNOWN]:
      self.uncheck()
      return

    self.check()

class UpToDateCheck(Check):

  def __init__(self):
    super().__init__(_("System up-to-date"), subtitle=_("Ensure there are no pending updates in PureOS Store"))

    if PackageKit().get_available_updates().get_size() > 0:
      self.uncheck()
      return

    cache = apt.cache.Cache()
    cache.open()

    SANITY_CHECKS = (("kodi", "2:19.1+dfsg2-2+deb11u2pureos1"), # byzantium
                     ("python3-gbinder", "1.3.0-0pureos1~byz1"), # byzantium-updates
                     ("inetutils-ping", "2:2.0-1+deb11u3")) # byzantium-security
    for pkg, ver in SANITY_CHECKS:
      if pkg in cache:
        versions = sorted(cache[pkg].versions, reverse=True)
        if apt_pkg.version_compare(versions[0].version, ver) >= 0:
          continue
        else:
          self.set_subtitle(_("Your system appears stuck on old package versions. Please contact support for assistance"))
      else:
        self.set_subtitle(_("Could not verify the package lists"))

      self.uncheck()
      return

    self.check()

class ExternalPowerCheck(Check):
  def __init__(self):
    super().__init__(_("Plugged in to external power"))

    if UPowerGlib.Client.new().get_on_battery():
      self.uncheck()
      return

    self.check()

class BatteryChargeCheck(Check):
  def __init__(self):
    super().__init__(_("Battery fully charged"), subtitle=_("Charge the battery to at least 90%"))

    devices = UPowerGlib.Client.new().get_devices()

    has_battery = False

    for device in devices:
      if device.props.kind == UPowerGlib.DeviceKind.BATTERY:
        has_battery = True
        if device.props.percentage < 90:
          self.uncheck()
          return

    if not has_battery:
      self.set_visible(False)

    self.check()

class DiskSpaceCheck(Check):
  def __init__(self):
    REQUIRED_GB = 8 # TODO: more on amd64?

    super().__init__(_("{size} GB disk space available").format(size=REQUIRED_GB))

    info = Gio.File.new_for_path("/").query_filesystem_info("filesystem::free")
    free = info.get_attribute_uint64("filesystem::free") / 1024 / 1024 / 1024
    if free < REQUIRED_GB:
      self.set_subtitle(_("Your root filesystem currently has only {size} GB available").format(size=round(free, 1)))
      self.uncheck()
      return

    self.check()

class CustomRepoCheck(Check):
  def __init__(self):
    super().__init__(_("No custom repositories"), subtitle=_("Remove any non-PureOS {version} package repositories").format(version=10))

    sources = aptsources.sourceslist.SourcesList()

    if not sources.list:
      self.set_subtitle(_("Your system has no package repositories configured."))
      self.uncheck()
      return

    foreign = []

    for entry in sources.list:
      is_pureos = entry.uri.startswith(("http://repo.pureos.net/", "https://repo.pureos.net/"))
      is_byzantium = entry.dist.startswith("byzantium")
      if not entry.disabled and not entry.invalid and (not is_pureos or not is_byzantium):
        foreign.append(entry)

    if foreign:
      btn = Gtk.Button(label=_("Details"))
      btn.connect("clicked", lambda btn: self.emit("error", _("Unknown software sources:") + "\n\n" + "\n\n".join(x.line.strip() for x in foreign)))
      btn.set_valign(Gtk.Align.CENTER)
      self.add_suffix(btn)
      self.uncheck()
      return

    self.check()

class UnknownPackagesCheck(Check):
  def __init__(self):
    super().__init__("No foreign packages", subtitle=_("Your system contains packages that don't come from PureOS repositories. Downgrade/remove these packages or perform the system upgrade manually"))

    output = subprocess.run(["apt-forktracer"], capture_output=True, text=True)

    if output.returncode != 0:
      self.set_subtitle(_("Could not check the system for foreign packages"))
      self.uncheck()
      return

    ALLOWLIST = ["u-boot-librem5", "waydroid", "pureos-upgrade"]

    foreign = []

    for line in output.stdout.split('\n'):
      pkg = line.strip().split(' ')[0]
      if not pkg:
        continue

      if not pkg.startswith(("linux-image-", "linux-headers-", "linux-libc-dev", "firmware-")) and pkg not in ALLOWLIST:
        foreign.append(line)

    if foreign:
      btn = Gtk.Button(label=_("Details"))
      btn.connect("clicked", lambda btn: self.emit("error", _("Unknown package versions:") + "\n\n" + "\n\n".join(x.strip() for x in foreign)))
      btn.set_valign(Gtk.Align.CENTER)
      self.add_suffix(btn)
      self.uncheck()
      return

    self.check()

class HeldPackagesCheck(Check):
  def __init__(self):
    super().__init__("No held packages", subtitle=_("Some packages have unresolvable dependencies or are being held by user request"))

    cache = apt.cache.Cache()
    cache.open()
    cache.upgrade(dist_upgrade = True)

    held = []
    for pkg in cache:
      if (pkg.is_installed and pkg.is_upgradable and not pkg.marked_upgrade and not pkg.marked_delete) or pkg.is_now_broken or pkg.is_inst_broken:
        held.append(pkg)

    if held:
      logging.error(_("Some packages have unresolvable dependencies or are being held by user request") + "\n\n" + "\n".join([pkg.name for pkg in held]))
      self.uncheck()
      return

    self.check()

