//===========================================
//  PC-BSD source code
//  Copyright (c) 2015, PC-BSD Software/iXsystems
//  Available under the 3-clause BSD license
//  See the LICENSE file for full details
//===========================================
#include <QUuid>
#include "sysadm-general.h"
#include "sysadm-update.h"
#include "sysadm-global.h"
#include "globals.h"

#define UP_PIDFILE "/tmp/.updateInProgress"
#define UP_RBFILE "/tmp/.rebootRequired"
#define UP_UPFILE "/tmp/.updatesAvailable"

#define UP_CONFFILE "/usr/local/etc/trueos.conf"

using namespace sysadm;

//PLEASE: Keep the functions in the same order as listed in pcbsd-general.h

// Return a list of updates available
QJsonObject Update::checkUpdates(bool fast) {
  //NOTE: The "fast" option should only be used for automated/timed checks (to prevent doing this long check too frequently)
  QJsonObject retObject;
  qDebug() << "Check for updates: fast=" << fast;
  //Quick check to ensure the tool is available
  if(!QFile::exists("/usr/local/bin/pc-updatemanager")){ 
    return retObject;
  }
  //Check if the system is waiting to reboot
  if(QFile::exists(UP_RBFILE)){
    retObject.insert("status","rebootrequired");
    if(QFile::exists(UP_UPFILE)){ QFile::remove(UP_UPFILE); } //ensure the next fast update does a full check
    return retObject;
  }
  //Check if an update is currently running
  if(QFile::exists(UP_PIDFILE)){
    //See if the process is actually running
    if( General::RunQuickCommand(QString("pgrep -F ")+UP_PIDFILE) ){
      //Success if return code == 0
      retObject.insert("status","updaterunning");
      if(QFile::exists(UP_UPFILE)){ QFile::remove(UP_UPFILE); } //ensure the next fast update does a full check
      return retObject;
    }
  }
  //Get the list of deatils from the update checks (fast/full)
  QStringList output;
  QDateTime cdt = QDateTime::currentDateTime();
  QDateTime fdt = cdt.addDays(-1); //just enough to trip the checks below if needed
  if(QFile::exists(UP_UPFILE)){ fdt = QFileInfo(UP_UPFILE).lastModified(); }
  //qDebug() << "Time Stamps (now/file):" << cdt << fdt;
  //qDebug() << " - File earlier than now:" << (fdt<cdt);
  //qDebug() << " - File +1 day earlier than now (+day):" << (fdt.addDays(1)<cdt);
  //qDebug() << " - File +1 day earlier than now (+secs):" << (fdt.addSecs(24*60)<cdt);
  //qDebug() << " - Seconds from File->Day time:" << fdt.secsTo(cdt);
  int secs = fdt.secsTo(cdt);
  if(fast && (secs<43200) ){
    //Note: The "fast" check will only be used if the last full check was less than 12 hours earlier.
    qDebug() << " - UseFast Re-read";
    output = General::readTextFile(UP_UPFILE);
  }else if(secs<600 ){
    //Note: This will re-use the previous check if it was less than 10 minutes ago (prevent hammering servers from user checks)
    qDebug() << " - Use Fast Re-read (failsafe -  less than 10 minute interval)";
    output = General::readTextFile(UP_UPFILE);
  }else{	  
    qDebug() << " - Run full check";
    output = General::RunCommand("pc-updatemanager check").split("\n");
    output.append( General::RunCommand("pc-updatemanager pkgcheck").split("\n") );
    General::writeTextFile(UP_UPFILE, output); //save this check for later "fast" updates
  }
  //qDebug() << "pc-updatemanager checks:" << output;
  
  QString nameval;
  int pnum=1;
  for ( int i = 0; i < output.size(); i++)
  {
     if ( output.at(i).indexOf("NAME: ") != -1 )
       nameval = output.at(i).section(" ", 1, 1);

     if ( output.at(i).indexOf("TYPE: SECURITYUPDATE") != -1 ) {
       QJsonObject itemvals;
       itemvals.insert("name", nameval);
       retObject.insert("security", itemvals);
     }
     if ( output.at(i).indexOf("TYPE: SYSTEMUPDATE") != -1 ) {
       QJsonObject itemvals;
       itemvals.insert("name", nameval);
       if ( output.size() > ( i + 1) )
         itemvals.insert("tag", output.at(i+1).section(" ", 1, 1));
       if ( output.size() > ( i + 2) )
         itemvals.insert("version", output.at(i+2).section(" ", 1, 1));
       retObject.insert("majorupgrade", itemvals);
     }
     if ( output.at(i).indexOf("TYPE: PATCH") != -1 ) {
       QJsonObject itemvals;
       itemvals.insert("name", nameval);
       if ( output.size() > ( i + 1) )
         itemvals.insert("tag", output.at(i+1).section(" ", 1, 1));
       if ( output.size() > ( i + 2) )
         itemvals.insert("details", output.at(i+2).section(" ", 1, 1));
       if ( output.size() > ( i + 3) )
         itemvals.insert("date", output.at(i+3).section(" ", 1, 1));
       if ( output.size() > ( i + 4) )
         itemvals.insert("size", output.at(i+4).section(" ", 1, 1));
       retObject.insert("patch"+QString::number(pnum), itemvals);
       pnum++;
     }
     if ( output.at(i).indexOf("TYPE: PKGUPDATE") != -1 ) {
       QJsonObject itemvals;
       itemvals.insert("name", nameval);
       retObject.insert("packageupdate", itemvals);
     }
  }
  if(retObject.isEmpty()){
    retObject.insert("status", "noupdates");
  }else{
    // Update status that we have updates
    retObject.insert("status", "updatesavailable");
  }
  retObject.insert("last_check",QFileInfo(UP_UPFILE).lastModified().toString(Qt::ISODate) );
  return retObject;
}

// List available branches we can switch to
QJsonObject Update::listBranches() {
  QJsonObject retObject;

  QStringList output = General::RunCommand("pc-updatemanager branches").split("\n");

  bool inSection = false;
  for ( int i = 0; i < output.size(); i++)
  {
    if ( output.at(i).indexOf("-----------------") != -1 ) {
      inSection = true;
      continue;
    }

    if (!inSection)
      continue;

    if ( output.at(i).isEmpty() )
      break;

    QString tmp = output.at(i).section(" ", 0, 0);
    QString tmp2 = output.at(i).section(" ", 1, 1);
    if ( tmp2 == "*" )
      retObject.insert(tmp, "active");
    else
      retObject.insert(tmp, "available");
  }

  return retObject;
}

// Kickoff an update process
QJsonObject Update::startUpdate(QJsonObject jsin) {
  QJsonObject retObject;

  QStringList keys = jsin.keys();
  if (! keys.contains("target") ) {
    retObject.insert("error", "Missing required key 'target'");
    return retObject;
  }
  // Save the target
  QString target;
  target = jsin.value("target").toString();

  QString flags;
  if ( target == "chbranch" ) {
    if (! keys.contains("branch") ) {
      retObject.insert("error", "Missing required key 'branch'");
      return retObject;
    }
    flags = "chbranch " + jsin.value("branch").toString();
  } else if ( target == "pkgupdate" ) {
    flags = "pkgupdate";
/*  } else if ( target == "fbsdupdate" ) {
    flags = "fbsdupdate";
  } else if ( target == "fbsdupdatepkgs" ) {
    flags = "fbsdupdatepkgs";*/
  } else if ( target == "standalone" ) {
    if (! keys.contains("tag") ) {
      retObject.insert("error", "Missing required key 'tag'");
      return retObject;
    }
    flags = "install " + jsin.value("tag").toString();
  } else {
    // Ruh-roh
    retObject.insert("error", "Unknown target key: " + target);
    return retObject;
  } 

  // Create a unique ID for this queued action
  QString ID = QUuid::createUuid().toString();

  // Queue the update action
  DISPATCHER->queueProcess("sysadm_update_runupdates::"+ID, "pc-updatemanager " + flags);
  
  if(QFile::exists(UP_UPFILE)){ QFile::remove(UP_UPFILE); } //ensure the next fast update does a full check
  
  // Return some details to user that the action was queued
  retObject.insert("command", "pc-updatemanger " + flags);
  retObject.insert("comment", "Task Queued");
  retObject.insert("queueid", ID);
  return retObject;
}

// Stop an update process
QJsonObject Update::stopUpdate() {
  //See if the update is running in the dispatcher
  QJsonObject ret;
  QJsonObject cup = DISPATCHER->listJobs();
  QStringList ids = cup.keys().filter("sysadm_update_runupdates::");
  if(!ids.isEmpty()){
    //Found a dispatcher process - go ahead and request that it stop
    DISPATCHER->killJobs(ids);
    ret.insert("result","success");
  }else if( QFile::exists(UP_PIDFILE) ){
    //See if the process is actually running
    if( General::RunQuickCommand(QString("pgrep -F ")+UP_PIDFILE) ){
      //Success if return code == 0
      int rtc = General::RunQuickCommand( QString("pkill -F ")+UP_PIDFILE );
      if(rtc==0){ ret.insert("result","success"); }
      else{ ret.insert("result","error: could not kill update process"); }
    }
  }else{
    ret.insert("result","error: no update processes found");
  }
  return ret;
}

//SETTINGS OPTIONS
QJsonObject Update::readSettings(){
  QJsonObject ret;
  QStringList knownsettings;
  knownsettings << "PACKAGE_SET" << "PACKAGE_URL" << "AUTO_UPDATE" << "MAXBE";// << "CDN_TYPE";

  QStringList info = General::readTextFile(UP_CONFFILE);
  for(int i=0; i<info.length(); i++){
    if(info[i].section("#",0,0).simplified().isEmpty()){ continue; } //nothing on this line
    QString line = info[i].section("#",0,0).simplified();
    QString var = line.section(":",0,0).simplified();
    if(knownsettings.contains(var)){
      ret.insert(var.toLower(), line.section(":",1,-1).simplified());
    }
  }
  return ret;
}

QJsonObject Update::writeSettings(QJsonObject obj){
  QJsonObject ret;
  //Check inputs
  QStringList knownsettings;
  knownsettings << "PACKAGE_SET" << "PACKAGE_URL" << "AUTO_UPDATE" << "MAXBE";// << "CDN_TYPE";
  QStringList keys = obj.keys();
  QStringList vals;
  for(int i=0; i<keys.length(); i++){
    if(knownsettings.contains(keys[i].toUpper())){
      vals << obj.value(keys[i]).toString();
      keys[i] = keys[i].toUpper();
    }else{
      keys.removeAt(i); i--;
    }
  }
  if(keys.isEmpty()){ ret.insert("result","no changes"); return ret; }
  //Note: Now the keys/vals lists are "paired" up, with same size and info for the same index in the list
  //Save Settings
  // - first replace existing variables in the config file
  QStringList info = General::readTextFile(UP_CONFFILE);
  for(int i=0; i<info.length(); i++){
    if(info[i].section("#",0,0).simplified().isEmpty()){ continue; } //nothing on this line
    QString line = info[i].section("#",0,0).simplified();
    QString var = line.section(":",0,0).simplified();
    int index = keys.indexOf(var);
    if(index>=0){
      info[i] = keys[index]+": "+vals[index];
      keys.removeAt(index); vals.removeAt(index);
    }
  }
  // if the variable was not previously defined, just add it to the end
  for(int i=0; i<keys.length(); i++){
    info << keys[i]+": "+vals[i];
  }
  if( General::writeTextFile(UP_CONFFILE, info, true) ){
    ret.insert("result","success");
  }else{
    ret.insert("result","error");
  }
  return ret;
}
