import argparse
import glob
import itertools
import os
import sys
import time
import traceback

import six
from six.moves import configparser

sys.path.append(os.path.dirname(__file__))
from theia_common import copytree, copydb, hashwalk, parse_installed, remove


os.environ['LC_ALL'] = 'C'
os.umask(0o77)


BACKUP_WAIT = 10


def main():
  parser = argparse.ArgumentParser()
  parser.add_argument('--rsync', required=True)
  parser.add_argument('--sqlite3', required=True)
  parser.add_argument('--root', required=True)
  parser.add_argument('--backup', required=True)
  parser.add_argument('--cfg', required=True)
  parser.add_argument('--dirs', action='append')
  parser.add_argument('--exitfile', required=True)
  parser.add_argument('--errorfile', required=True)
  args = parser.parse_args()

  TheiaExport(args)()


class TheiaExport(object):

  def __init__(self, args):
    self.rsync_bin = args.rsync
    self.sqlite3_bin = args.sqlite3
    self.root_dir = args.root
    self.backup_dir = args.backup
    self.slapos_cfg = cfg = args.cfg
    self.dirs = args.dirs
    self.exit_file = args.exitfile
    self.error_file = args.errorfile
    configp = configparser.SafeConfigParser()
    configp.read(cfg)
    self.proxy_db = configp.get('slapproxy', 'database_uri')
    self.instance_dir = configp.get('slapos', 'instance_root')
    partitions = glob.glob(os.path.join(self.instance_dir, 'slappart*'))
    self.partition_dirs = [p for p in partitions if os.path.isdir(p)]
    self.copytree_partitions_args = {}
    self.logs = []

  def mirrorpath(self, src):
    return os.path.abspath(os.path.join(
      self.backup_dir, os.path.relpath(src, start=self.root_dir)))

  def backuptree(self, src, exclude=[], extrargs=[], verbosity='-v'):
    dst = self.mirrorpath(src)
    return copytree(self.rsync_bin, src, dst, exclude, extrargs, verbosity)

  def backupdb(self):
    copydb(self.sqlite3_bin, self.proxy_db, self.mirrorpath(self.proxy_db))

  def backuppartition(self, partition):
    installed = parse_installed(partition)
    rules = os.path.join(partition, 'srv', 'exporter.exclude')
    extrargs = ('--filter=.-/ ' + rules,) if os.path.exists(rules) else ()
    self.backuptree(partition, exclude=installed, extrargs=extrargs)
    self.copytree_partitions_args[partition] = (installed, extrargs)

  def sign(self, signaturefile):
    remove(signaturefile)
    pardir = os.path.abspath(os.path.join(self.backup_dir, os.pardir))
    tmpfile = os.path.join(pardir, 'backup.signature.tmp')
    mirror_partitions = [self.mirrorpath(p) for p in self.partition_dirs]
    with open(tmpfile, 'w') as f:
      for s in hashwalk(self.backup_dir, mirror_partitions):
        f.write(s + '\n')
    os.rename(tmpfile, signaturefile)

  def checkpartition(self, partition, pattern='/srv/backup/'):
    installed, extrargs = self.copytree_partitions_args[partition]
    output = self.backuptree(
      partition,
      exclude=installed,
      extrargs=extrargs + ('--dry-run', '--update'),
      verbosity='--out-format=%n',
    )
    return [path for path in output.splitlines() if pattern in path]

  def loginfo(self, msg):
    print(msg)
    self.logs.append(msg)

  def __call__(self):
    remove(self.error_file)
    exitcode = 0
    try:
      self.export()
    except Exception:
      exitcode = 1
      exc = traceback.format_exc()
      with open(self.error_file, 'w') as f:
        f.write('\n ... OK\n\n'.join(self.logs))
        f.write('\n ... ERROR !\n\n')
        f.write(exc)
      print('\n\nERROR\n\n' + exc)
    finally:
      with open(self.exit_file, 'w') as f:
        f.write(str(exitcode))
      sys.exit(exitcode)

  def export(self):
    export_start_date = int(time.time())

    etc_dir = os.path.join(self.root_dir, 'etc')
    with open(os.path.join(etc_dir, '.resilient_timestamp'), 'w') as f:
      f.write(str(export_start_date))

    self.loginfo('Backup directory ' + etc_dir)
    self.backuptree(etc_dir, extrargs=('--filter=- */', '--filter=-! .*'))

    for d in self.dirs:
      self.loginfo('Backup directory ' + d)
      self.backuptree(d)

    self.loginfo('Backup slapproxy database')
    self.backupdb()

    self.loginfo('Backup partitions')
    for p in self.partition_dirs:
      self.backuppartition(p)

    self.loginfo('Compute backup signature')
    self.sign(os.path.join(self.backup_dir, 'backup.signature'))

    time.sleep(10)
    self.loginfo('Check partitions')
    modified = list(itertools.chain.from_iterable(
      self.checkpartition(p) for p in self.partition_dirs))
    if modified:
      msg = 'Some files have been modified since the backup started'
      self.loginfo(msg + ':')
      self.loginfo('\n'.join(modified))
      self.loginfo("Let's wait %d minutes and try again" % BACKUP_WAIT)
      time.sleep(BACKUP_WAIT * 60)
      raise Exception(msg)

    self.loginfo('Done')


if __name__ == '__main__':
  main()
