from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import os
import re
import time
import gzip
from six.moves import queue as Queue
import threading
from DIRAC import gLogger, S_OK, S_ERROR
from DIRAC.Core.Utilities.ThreadScheduler import gThreadScheduler
from DIRAC.Core.Utilities.File import mkDir


class SecurityFileLog(threading.Thread):

  def __init__(self, basePath, daysToLog=100):
    self.__basePath = basePath
    self.__messagesQueue = Queue.Queue()
    self.__requiredFields = ('timestamp',
                             'success',
                             'sourceIP',
                             'sourcePort',
                             'sourceIdentity',
                             'destinationIP',
                             'destinationPort',
                             'destinationService',
                             'action')
    threading.Thread.__init__(self)
    self.__secsToLog = daysToLog * 86400
    gThreadScheduler.addPeriodicTask(86400,
                                     self.__launchCleaningOldLogFiles,
                                     elapsedTime=(time.time() % 86400) + 3600)
    self.setDaemon(True)
    self.start()

  def run(self):
    while True:
      secMsg = self.__messagesQueue.get()
      msgTime = secMsg[0]
      path = "%s/%s/%02d" % (self.__basePath, msgTime.year, msgTime.month)
      mkDir(path)
      logFile = "%s/%s%02d%02d.security.log.csv" % (path, msgTime.year, msgTime.month, msgTime.day)
      if not os.path.isfile(logFile):
        fd = open(logFile, "w")
        fd.write("Time, Success, Source IP, Source Port, source Identity, destinationIP,\
           destinationPort, destinationService, action\n")
      else:
        fd = open(logFile, "a")
      fd.write("%s\n" % ", ".join([str(item) for item in secMsg]))
      fd.close()

  def __launchCleaningOldLogFiles(self):
    nowEpoch = time.time()
    self.__walkOldLogs(self.__basePath,
                       nowEpoch,
                       re.compile(r"^\d*\.security\.log\.csv$"),
                       86400 * 3,
                       self.__zipOldLog)
    self.__walkOldLogs(self.__basePath,
                       nowEpoch,
                       re.compile(r"^\d*\.security\.log\.csv\.gz$"),
                       self.__secsToLog,
                       self.__unlinkOldLog)

  def __unlinkOldLog(self, filePath):
    try:
      gLogger.info("Unlinking file %s" % filePath)
      os.unlink(filePath)
    except Exception as e:
      gLogger.error("Can't unlink old log file", "%s: %s" % (filePath, str(e)))
      return 1
    return 0

  def __zipOldLog(self, filePath):
    try:
      gLogger.info("Compressing file %s" % filePath)
      fd = gzip.open("%s.gz" % filePath, "w")
      with open(filePath) as fO:
        bS = 1048576  # 1MiB
        buf = fO.read(bS)
        while buf:
          fd.write(buf)
          buf = fO.read(bS)
      fd.close()
    except Exception as e:
      gLogger.exception("Can't compress old log file", filePath)
      return 1
    return self.__unlinkOldLog(filePath) + 1

  def __walkOldLogs(self, path, nowEpoch, reLog, executionInSecs, functor):
    initialEntries = os.listdir(path)
    files = []
    numEntries = 0
    for entry in initialEntries:
      entryPath = os.path.join(path, entry)
      if os.path.isdir(entryPath):
        numEntries += 1
        numEntriesSubDir = self.__walkOldLogs(entryPath, nowEpoch, reLog, executionInSecs, functor)
        if numEntriesSubDir == 0:
          gLogger.info("Removing dir %s" % entryPath)
          try:
            os.rmdir(entryPath)
            numEntries -= 1
          except Exception as e:
            gLogger.error("Can't delete directory", "%s: %s" % (entryPath, str(e)))
      elif os.path.isfile(entryPath):
        numEntries += 1
        if reLog.match(entry):
          if nowEpoch - os.stat(entryPath)[8] > executionInSecs:
            numEntries += functor(entryPath) - 1
    return numEntries

  def logAction(self, msg):
    if len(msg) != len(self.__requiredFields):
      return S_ERROR("Mismatch in the msg size, it should be %s and it's %s" % (len(self.__requiredFields),
                                                                                len(msg)))
    self.__messagesQueue.put(msg)
    return S_OK()
