#!/usr/bin/python3
import sys, os, locale, signal, re, pickle
import gi, subprocess, time, threading
from locale import gettext as _
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib, Pango
if os.path.exists('updaterlib.py'):
  from updaterlib import *
else:
  from update_applet.updaterlib import *

import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)

INSTALL = True
PULSE = False
DOMAIN = 'rosa-update-system'
LOCALE_DIR = '/usr/share/locale'
locale.bindtextdomain(DOMAIN, LOCALE_DIR)
locale.textdomain(DOMAIN)

install_cmd = '/usr/libexec/rpmgrab.helper install '
reinstall_cmd = '/usr/libexec/rpmgrab.helper reinstall '

UI = '/usr/share/update_applet/update_applet.ui'
iconpath='/usr/share/icons'
if os.path.exists('update_applet'):
  UI = './update_applet.glade'
  iconpath = os.path.abspath('./')
ICON = os.path.join(iconpath, 'rpmgrab.svg')

cfgdir = os.environ['HOME'] + '/.config/rpmgrab'

config = {}
config['show_log'] = False

def pickle_write():
    with open(cfgdir + '/dump', 'wb') as f:
       pickle.dump(config, f)

class Rpms:
  def __init__(self, rpms, app):
    self.rpms = rpms
    self.absrpms = list(map(os.path.abspath, rpms))
    self.list(app)
    if not all(map(os.path.exists, self.absrpms)):
      notify_send(_('Some files from rpms list not found:'), self.rpms, 'warning', None, None)
      echo(app, _('Some files from rpms list not found!'), _('Some files from rpms list not found!'))
      return None
    self.rpms = True
    self.installed = self.check_inst(app)
    if self.installed:
      echo(app, '\n==> ' + _('Already installed packages:'), _('Found already installed' ))
      self.strlist = ''
      for a in self.installed:
        s = f"{a['name']}: {a['old_ver']} --> {a['new_ver']}"
        self.strlist += s + '\n'
        echo(app, s,  _('Found already installed packages') )
    app.install_button.set_sensitive(True)
    app.install_button.set_label(_('Install list of rpms'))

  def check_inst(self,app):
    installed = []
    for pack  in self.absrpms:
      info = info_to_dict(pack)
      oldinfo = info_to_dict(info['Name'])
      if oldinfo:
        old_epoch = oldinfo.get('Epoch', '0')
        new_epoch = info.get('Epoch', '0')
        new_ver = f"{new_epoch}:{info['Version']}-{info['Release']}"
        old_ver = f"{old_epoch}:{oldinfo['Version']}-{oldinfo['Release']}"
        if  old_ver  >=  new_ver:
          installed.append({'name' : info['Name'],
                            'file' : pack,
                            'old_ver' : old_ver,
                            'new_ver' : new_ver})
    return installed

  def list(self, app):
    echo(app, '==> ' + _('RPMS list: '), _('multiply install'))
    for a in self.absrpms:
      echo(app, a, _('multiply install')  )

  def install(self, app):
      GLib.idle_add(app.install_button.hide)
      if self.installed:
        reinstall = [item['file'] for item in self.installed if item['old_ver'] == item['new_ver'] ]
        newlist = [rpm for rpm in self.absrpms if rpm not in reinstall]
        self.inst = " ".join(f'"{item}"' for item in newlist)
        self.reinst = " ".join(f'"{item}"' for item in reinstall)
        choose = dialog(first=_('Found already installed packages'), second=self.strlist,
                 btn=[ _('Skip'), 0, _('Reinstall'), 1, _('Abort'), 3] )
        if choose == 1:
          if not runit(app, f'{reinstall_cmd} {self.reinst}', _('Reinstall rpm packages')):
            echo(app, 'ERROR!!!', 'ERROR: ' + reinstall_cmd + _(" <packages list>"))
            GLib.idle_add(app.install_button.show)
            start_pulse(app, False)
            app.progress.set_fraction(0.0)
            notify_send(_('Reinstallation failed'), \
                        _('Try later, or use system package manager'), 'warning', None, None)
            return False
          else:
            echo(app, '', _("Reinstall done"))
        elif choose == 3:
          exit()
      else:
        self.inst = " ".join(f'"{item}"' for item in self.absrpms)
      if self.inst:
        if not runit(app, f'{install_cmd} {self.inst}', _('Install rpm packages')):
          echo(app, 'ERROR!!!', 'ERROR: ' + install_cmd + _(" <packages list>"))
          GLib.idle_add(app.install_button.show)
          start_pulse(app, False)
          app.progress.set_fraction(0.0)
          notify_send(_('Installation failed'), \
                      _('Try later, or use system package manager'), 'warning', None, None)
          return False
      else:
        echo(app, '\n==> ' + _('No packages selected to install'), _('No packages selected to install'))
      start_pulse(app, False)
      app.progress.set_fraction(1.0)
      app.progress.set_text(_('Complete'))
      echo(app, '', _("Complete"))


class Rpm:
  def __init__(self, rpm, app):
    start_pulse(app, True)
    self.cmd = install_cmd
    self.rpm = self.get_rpm(rpm, app)
    if not self.rpm or not os.path.exists(self.rpm):
      notify_send(_('Package not found:'), rpm, 'warning', None, None)
      self.rpm = None
      return None
    self.info = info_to_dict(self.rpm)
    echo(app, '==> ' + _('Meta info from RPM package: '), _('Package info: ') + self.info['Name'])
    echo(app, 'FILE: ' + self.rpm , '')
    app.window.set_title(f"RpmGrab: {self.info['Name']}")
    for key, val in self.info.items():
      echo(app, f'{key.upper()}: {val}', _("Rpm info"))
    self.req(app)
    self.prov(app)
    self.list(app)
    oldinfo = info_to_dict(self.info['Name'])
    if oldinfo:
      old_epoch = oldinfo.get('Epoch', '0')
      new_epoch = self.info.get('Epoch', '0')
      if f"{old_epoch}:{oldinfo['Version']}-{oldinfo['Release']}"  ==  f"{new_epoch}:{self.info['Version']}-{self.info['Release']}":
        self.cmd = reinstall_cmd
        app.install_button.set_label(_('Renstall package'))
      elif f"{old_epoch}:{oldinfo['Version']}-{oldinfo['Release']}"  <=  f"{new_epoch}:{self.info['Version']}-{self.info['Release']}":
        app.install_button.set_label(_('Update package'))
      else:
        app.install_button.set_label(_('Downgrade'))
        # без GLib.idle_add падает с ошибкой X сервера
        GLib.idle_add(lambda new=f"{new_epoch}:{self.info['Version']}-{self.info['Release']}", old=f"{old_epoch}:{oldinfo['Version']}-{oldinfo['Release']}" :
                     (dialog(first=_("Warning: Downgrade Attempt"),
                      second=_("You are trying to install a package version ({new}) \
that is lower than the currently installed version ({old}). \
This may cause compatibility issues.").format(new=new,old=old),  btn=[_("I Understand"), 1]), False)[1])
    else:
        app.install_button.set_label(_('Install package'))
    start_pulse(app, False)
    app.progress.set_fraction(0.0)
    echo(app, ' ', f"{app.install_button.get_label()} {self.info['Name']}")
    app.install_button.set_sensitive(True)

  def get_rpm(self, rpm, app):
    if rpm.endswith('.rpm'):
      rpmfile = os.path.abspath(rpm)
      return rpmfile
    echo(app, '==> ' + _('Download rpm:\n    ') + rpm, _('Download rpm: ') + rpm)
    if rpm.startswith('http'):
      rpmfile = download_file(rpm, '.rpm', '/tmp', app=app)
    else:
      result = subprocess.run(['dnf', 'download', '-q', '--url', '--disablerepo', '*686*', rpm], stdout=subprocess.PIPE, text=True)
      last_rpm = result.stdout.strip().split('\n')[-1]
      rpmfile = download_file(last_rpm, '.rpm', '/tmp', app=app)
    echo(app, '', _("Downloaded"))
    return rpmfile

  def info(self, app):
    runit(app, f'timeout 10s rpm -q --info "{self.rpm}"', _('Rpm info: '))

  def req(self, app):
    echo(app, '\n', _('Requires: ') + self.rpm)
    runit(app, f'timeout 10s rpm -q --requires "{self.rpm}"', _('Requires: '))
    echo(app, '\n', _('Recommends: ') + self.rpm)
    runit(app, f'timeout 10s rpm -q --recommends "{self.rpm}"', _('Recommends: '))

  def prov(self, app):
    echo(app, '\n', _('Provides: ') + self.rpm)
    runit(app, f'timeout 10s rpm -q --provides "{self.rpm}"', _('Provides: '))

  def list(self, app):
    echo(app, '\n', _('RPM list: ') + self.rpm)
    runit(app, f'timeout 10s rpm -ql "{self.rpm}"', _('RPM list: '))

  def install(self, app):
      GLib.idle_add(app.install_button.hide)
      if not runit(app, f'{self.cmd} "{self.rpm}"', _('Install: ') + self.rpm):
        echo(app, 'ERROR!!!', 'ERROR: ' + install_cmd)
        GLib.idle_add(app.install_button.show)
        start_pulse(app, False)
        app.progress.set_fraction(0.0)
        notify_send(self.rpm + _(': installation failed'), \
                    _('Try later, or use system package manager'), 'warning', None, None)
      else:
        start_pulse(app, False)
        app.progress.set_fraction(1.0)
        app.progress.set_text(_('Complete'))
        echo(app, _('Complete'), _('Install complete: ') + os.path.basename(self.rpm))
      return False


def info_to_dict(rpm_name):
    command = ['rpm', '-qi', rpm_name]
    result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
    if result.returncode != 0:
      return None
    info_dict = {}
    lines = result.stdout.splitlines()
    i = 0
    while i < len(lines):
      line = lines[i]
      if line.startswith('Description'):
          key = 'Description'
          value_parts = [line.split(':', 1)[1].strip()] if ':' in line else []
          i += 1
          while i < len(lines):
            value_parts.append(lines[i].strip())
            i += 1
          info_dict[key] = '\n'.join(value_parts)
      else:
        match = re.match(r'^([^:]+)\s*:\s*(.*)$', line)
        if match:
            key = match.group(1).strip()
            value = match.group(2).strip()
            info_dict[key] = value
        i += 1
    return info_dict

def clear():
    global INSTALL
    app.window.set_title(_('RpmGrab - simple rpm installator gui'))
    app.textbuffer.set_text('\n')
    app.statusbar.push(app.context_id, '')
    app.progress.set_pulse_step(0.2)
    app.progress.set_text(_('Install'))
    app.text_window.hide()
    app.exit_button.set_label(_('Exit'))
    app.install_button.set_label(_('Install'))
    app.install_button.set_sensitive(False)
    app.pwf_button.hide()
    app.install_button.show()
    start_pulse(app, False)
    app.log_chkbx.set_label(_('Info'))

    if config['show_log']:
        app.log_chkbx.set_active(True)
        app.text_window.show()
    else:
        app.log_chkbx.set_active(False)
        app.text_window.hide()

def exit(*args):
  Gtk.main_quit()

class WinHandler:
    def on_CHECK_HALT_activate(self, button):
        pass

    def on_show_log_toggled(self, button):
      global config
      if app.log_chkbx.get_active():
          app.text_window.show()
          config['show_log'] = True
      else:
          w, h = app.window.get_size()
          app.text_window.hide()
          app.window.resize(w, app.window.get_preferred_height()[1])
          config['show_log'] = False
      pickle_write()

    def onSUBMIT(self, button):
        global INSTALL
        clear()
        bat = powercheck()
        if bat < 10:
            if not  dialog(first=_("Attention!\nThe battery charge status is low: ") + f'{bat}%',
                           second=_("Continue?"), btn=[Gtk.STOCK_YES, 1, Gtk.STOCK_NO, 0 ] ):
                return
        start_pulse(app, True)
        GLib.idle_add(lambda : rpm.install(app))

    def onEXIT(self, button):
        exit()

class Applet:
  def __init__(self):

    # window
    signal.signal(signal.SIGINT, signal.SIG_DFL)
    builder = Gtk.Builder()
    self.builder = builder
    builder.set_translation_domain(DOMAIN)
    builder.add_from_file(UI)

    textaria = builder.get_object('TEXTAREA')
    textaria.override_font(Pango.FontDescription('Monospace 10'))
    self.textbuffer = textaria.get_buffer()

    self.progress = builder.get_object('PROGRESS')
    self.statusbar = builder.get_object('STATUS')
    self.text_window = builder.get_object('SCROLL')
    self.context_id = self.statusbar.get_context_id('updater')

    self.log_chkbx = builder.get_object('show_log')
    self.install_button = builder.get_object('SUBMIT')
    self.exit_button = builder.get_object('EXIT')
    self.pwf_button = builder.get_object('CHECK_HALT')
    builder.connect_signals(WinHandler())
    self.scroll = builder.get_object('SCROLL')
    self.v = self.scroll.get_vadjustment()
    self.h = self.scroll.get_hadjustment()

    self.window = builder.get_object('window_main')
    self.window.set_icon_from_file(ICON)
    self.window.connect("destroy", Gtk.main_quit)

  def run(self):
    self.window.show_all()
    self.pwf_button.hide()
    Gtk.main()

def scroll_back(app):
  app.v.set_value(0)
  app.h.set_value(0)
  return False

def HLP():
    print(_("""Usage: rpmgrab [OPTIONS] [RPM_FILE|PACKAGE_NAME...]
Simple GUI RPM installer

Options:
  --help     Show this help message
  --force    Install immediately without confirmation
  --version  Show version information

Examples:
  rpmgrab package.rpm
  rpmgrab package-name
  rpmgrab http://example.com/package.rpm
  rpmgrab package1.rpm package2.rpm
  rpmgrab --force package.rpm"""))
    sys.exit(0)

if __name__ == '__main__':
  if os.path.exists(cfgdir + '/dump'):
    try:
      with open(cfgdir + '/dump', 'rb') as f:
        config = pickle.load(f)
    except pickle.UnpicklingError as e:
      print(f"Pickle read data error: {e}")
      try:
        os.remove(cfgdir + '/dump')
      except:
            pass
    except PermissionError as e:
          print(f"Permissions error: {e}")
  else:
    os.makedirs(cfgdir, exist_ok=True)
    pickle_write()

  notify_init('RpmGrab')
  app = Applet()
  clear()
  force = False
  rpmfile = []

  # Parse command line arguments
  i = 1
  while i < len(sys.argv):
      arg = sys.argv[i]
      if arg == '--help':
          HLP()
      elif arg == '--force':
          force = True
      elif arg == '--version':
          print("RpmGrab 1.0")
          sys.exit(0)
      elif arg.startswith('file://'):
          filepath = arg[7:]
          if filepath.startswith('///'):
              filepath = filepath[2:]
          rpmfile.append(filepath)
      elif arg.startswith('rpmgrab://'):
          try:
              protocol, rest = arg[10:].split('/', 1)
              if protocol == 'name':
                  rpmfile.append(rest)
              elif protocol in ['http', 'https', 'ftp']:
                  rpmfile.append(f"{protocol}://{rest}")
              else:
                  print(f"Unknown protoсol: {protocol}")
                  sys.exit(1)
          except ValueError:
              print(f"Illegal format: {arg}")
              sys.exit(1)
      else:
          rpmfile.append(arg)
      i += 1

  if not rpmfile:
      rpmfile = dialog(first=_('Enter rpm file'), entry='yes')
      if not rpmfile:
          quit()
      if isinstance(rpmfile, str):
          rpmfile = [rpmfile]

  rpm = None
  def rpm_init():
    global rpm
    global force
    rpm = threading_me(Rpm, rpmfile[0], app)
    if not rpm.rpm:
      exit()
    if not force:
      GLib.timeout_add_seconds(2, lambda : scroll_back(app))
    else:
      WinHandler().onSUBMIT(None)
    return False

  def rpms_init():
    global rpm
    rpm = threading_me(Rpms, rpmfile, app)
    if not rpm.rpms:
      exit()
    GLib.timeout_add_seconds(1, lambda : scroll_back(app))
    return False
  print(rpmfile)
  if len(rpmfile) > 1:
    GLib.idle_add(rpms_init)
  else:
    GLib.idle_add(rpm_init)
  app.run()

