package com.airlock.iam.common.domain.service.logs;

import com.airlock.iam.common.domain.model.logs.Log;
import com.airlock.iam.common.domain.model.logs.LogSignatureVerification;
import com.airlock.iam.elog.api.ELogger;
import com.airlock.iam.elog.api.ELoggerFactory;
import com.airlock.iam.log.audit.AuditLogVerifier;
import com.airlock.iam.log.audit.AuditLoggerKeyHandler;
import com.airlock.iam.log.audit.AuditLogVerifier.VerifyStatus;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.FastDateFormat;

public class ElogParser implements LogParser {
   private static final ELogger LOG = ELoggerFactory.getLogger(ElogParser.class);
   private static final Pattern SEPARATOR = Pattern.compile(Pattern.quote("|||"));
   private static final Pattern VERTICAL_LINES = Pattern.compile("\\s*\\|\\s*");
   private static final Pattern MESSAGE_AND_TIMESTAMP = Pattern.compile("MEDUSA Audit Log Message.*? (.*?) \\[tim=(.*?) seq.*");
   private static AuditLoggerKeyHandler keyHandler;
   private static final FastDateFormat[] DATE_FORMATS = new FastDateFormat[]{
      FastDateFormat.getInstance("yyyy-MM-dd HH:mm:ss,SSS"), FastDateFormat.getInstance("dd.MM.yyyy HH:mm:ss.SSS")
   };
   private static final Date EPOCH = new Date(0L);

   @Override
   public List<Log> parse(List<String> unparsedLines) {
      if (unparsedLines.isEmpty()) {
         return Collections.emptyList();
      }

      long auditLogLastSeenSequenceNumber = 0L;
      long auditLogLastUniqueId = 0L;
      List<Log> result = new ArrayList<>(unparsedLines.size());
      Log.LogBuilder lastParsedLine = null;
      String lastParsedLineSequenceNumber = null;
      boolean preformattedMessage = false;

      for (String line : unparsedLines) {
         String requestId = "";
         String configContextId = null;
         String clientIp = null;
         String[] tokens = SEPARATOR.split(line, 8);
         String timestamp;
         String level;
         String sessionId;
         String seqString;
         String messageLine;
         if (tokens.length == 1) {
            timestamp = "";
            level = "";
            sessionId = "";
            seqString = "";
            messageLine = tokens[0].trim();
         } else if (tokens.length >= 4 && tokens.length <= 8) {
            timestamp = tokens[0].trim();
            level = tokens[1].trim();
            sessionId = tokens[2].trim();
            if (tokens.length == 8) {
               requestId = tokens[3].trim();
               configContextId = tokens[4].trim();
               clientIp = tokens[5].trim();
               seqString = tokens[6].trim();
               messageLine = tokens[7].trim();
            } else if (tokens.length == 6) {
               requestId = tokens[3].trim();
               seqString = tokens[4].trim();
               messageLine = tokens[5].trim();
            } else if (tokens.length == 4) {
               seqString = tokens[3].trim().substring(0, 3).trim();
               messageLine = tokens[3].trim().substring(3).trim();
            } else {
               requestId = tokens[3].trim();
               String seqNrAndMessage;
               if (tokens.length == 7) {
                  configContextId = tokens[4].trim();
                  clientIp = tokens[5].trim();
                  seqNrAndMessage = tokens[6].trim();
               } else {
                  seqNrAndMessage = tokens[4].trim();
               }

               int seqLen = 0;

               while (seqLen < seqNrAndMessage.length() && Character.isDigit(seqNrAndMessage.charAt(seqLen))) {
                  seqLen++;
               }

               if (seqLen > 0) {
                  seqString = seqNrAndMessage.substring(0, seqLen).trim();
                  if (seqNrAndMessage.length() > seqLen + 1) {
                     messageLine = seqNrAndMessage.substring(seqLen + 1);
                  } else {
                     messageLine = "";
                  }
               } else {
                  seqString = "";
                  messageLine = tokens[4].trim();
               }
            }
         } else {
            timestamp = "";
            level = "";
            sessionId = "";
            seqString = "";
            messageLine = "[Log line could not be parsed]";
         }

         LogSignatureVerification veriState = LogSignatureVerification.STATUS_NO_AUDITLOG_ENTRY;
         AuditLoggerKeyHandler keyHandler = getKeyHandler();
         if (AuditLogVerifier.isAuditLogLine(line) && keyHandler.isInitializationOk()) {
            VerifyStatus verificationStatus = new VerifyStatus();
            verificationStatus.setLastSeenSequenceNumber(auditLogLastSeenSequenceNumber);
            verificationStatus.setLastSeenUniqueId(auditLogLastUniqueId);
            synchronized (keyHandler) {
               AuditLogVerifier.verifyLogLine(line, timestamp, keyHandler.getVerifier(), keyHandler.getPublicKeyCrc(), verificationStatus);
            }

            if (verificationStatus.getLastSeenUniqueId() != auditLogLastUniqueId) {
               auditLogLastUniqueId = verificationStatus.getLastSeenUniqueId();
               auditLogLastSeenSequenceNumber = 1L;
            } else {
               auditLogLastSeenSequenceNumber++;
            }

            veriState = this.toLogVerification(verificationStatus);
            Matcher messageMatcher = MESSAGE_AND_TIMESTAMP.matcher(messageLine);
            if (messageMatcher.find()) {
               messageLine = "MEDUSA Audit Log Message<br/>" + messageMatcher.group(1);
               timestamp = messageMatcher.group(2);
            }
         }

         if (this.isPreformattedLine(messageLine)) {
            preformattedMessage = true;
         } else {
            messageLine = VERTICAL_LINES.matcher(messageLine).replaceAll("<br/>");
            preformattedMessage = false;
         }

         boolean isNewLogMessage = this.isNewLogMessage(lastParsedLine, lastParsedLineSequenceNumber, seqString);
         if (isNewLogMessage) {
            if (lastParsedLine != null) {
               result.add(lastParsedLine.build());
            }

            lastParsedLineSequenceNumber = seqString;
            lastParsedLine = Log.builder()
               .timestamp(toDate(timestamp))
               .level(level)
               .sessionId(sessionId)
               .requestId(requestId)
               .sequenceNumber(seqString)
               .configContextIdentifier(Strings.isNullOrEmpty(configContextId) ? "[DEFAULT]" : configContextId)
               .clientIp(Strings.isNullOrEmpty(clientIp) ? null : clientIp)
               .signatureVerification(veriState)
               .rawMessageLine(line);
            if (preformattedMessage) {
               lastParsedLine.preformatted(true);
            }

            this.addMessageLine(messageLine, preformattedMessage, lastParsedLine);
         } else if (lastParsedLine != null) {
            lastParsedLineSequenceNumber = seqString;
            lastParsedLine.rawMessageLine(line).sequenceNumber(seqString);
            if (preformattedMessage) {
               lastParsedLine.preformatted(true);
            }

            this.addMessageLine(messageLine, preformattedMessage, lastParsedLine);
         } else {
            lastParsedLineSequenceNumber = seqString;
            lastParsedLine = Log.builder()
               .timestamp(toDate(timestamp))
               .level(level)
               .sessionId(sessionId)
               .requestId(requestId)
               .sequenceNumber(seqString)
               .configContextIdentifier(Strings.isNullOrEmpty(configContextId) ? "[DEFAULT]" : configContextId)
               .clientIp(Strings.isNullOrEmpty(clientIp) ? null : clientIp)
               .signatureVerification(veriState)
               .rawMessageLine(line);
            if (preformattedMessage) {
               lastParsedLine.preformatted(true);
            }

            this.addMessageLine(messageLine, preformattedMessage, lastParsedLine);
         }
      }

      if (lastParsedLine != null) {
         if (preformattedMessage) {
            lastParsedLine.preformatted(true);
         }

         result.add(lastParsedLine.build());
      }

      return result;
   }

   private static synchronized AuditLoggerKeyHandler getKeyHandler() {
      if (keyHandler == null) {
         keyHandler = new AuditLoggerKeyHandler();
      }

      return keyHandler;
   }

   private LogSignatureVerification toLogVerification(VerifyStatus verificationStatus) {
      LogSignatureVerification veriState = LogSignatureVerification.STATUS_NO_AUDITLOG_ENTRY;
      if (verificationStatus.getVerifiedLines() == 1L) {
         veriState = LogSignatureVerification.STATUS_VERIFIED;
      }

      if (verificationStatus.getLinesWithErrors() == 1L) {
         if (verificationStatus.getErrorStatistics().containsKey("Modified Log Lines        ")) {
            veriState = LogSignatureVerification.STATUS_TAMPERED;
         } else if (verificationStatus.getErrorStatistics().containsKey("Parsing Error             ")) {
            veriState = LogSignatureVerification.STATUS_UNKNOWN;
         } else if (verificationStatus.getErrorStatistics().containsKey("Wrong Public Key          ")) {
            veriState = LogSignatureVerification.STATUS_WRONG_PUBLIC_KEY;
         } else if (verificationStatus.getErrorStatistics().containsKey("Wrong Log Sequence        ")) {
            veriState = LogSignatureVerification.STATUS_VERIFIED_NOT_IN_ORDER;
         } else {
            if (LOG.isDebugEnabled()) {
               LOG.debug("Fall through to STATUS_UNKNOWN, internal error in AuditLogVerifier.verifyLogLine?");
            }

            veriState = LogSignatureVerification.STATUS_UNKNOWN;
         }
      }

      return veriState;
   }

   private boolean isPreformattedLine(String messageLine) {
      if (messageLine == null) {
         return false;
      }

      for (int i = 0; i < messageLine.length(); i++) {
         if (Character.isLetter(messageLine.charAt(i)) || Character.isDigit(messageLine.charAt(i))) {
            return false;
         }
      }

      return true;
   }

   private boolean isNewLogMessage(Log.LogBuilder lastParsedLine, String lastParsedLineSequenceNumber, String seqString) {
      if (StringUtils.isBlank(seqString)) {
         return false;
      }

      if (lastParsedLine != null && lastParsedLineSequenceNumber != null) {
         long currentSeqNo;
         try {
            currentSeqNo = Long.parseLong(seqString);
         } catch (NumberFormatException e) {
            return false;
         }

         long lastSeqNo;
         try {
            lastSeqNo = Long.parseLong(lastParsedLineSequenceNumber);
         } catch (NumberFormatException e) {
            return currentSeqNo == 1L;
         }

         return currentSeqNo != lastSeqNo + 1L;
      } else {
         return true;
      }
   }

   private static Date toDate(String value) {
      if (Strings.isNullOrEmpty(value)) {
         return EPOCH;
      }

      for (FastDateFormat format : DATE_FORMATS) {
         try {
            return format.parse(value);
         } catch (ParseException var6) {
         }
      }

      return EPOCH;
   }

   private void addMessageLine(String messageLine, boolean isPreformatted, Log.LogBuilder log) {
      if (!isPreformatted && this.hasHtmlBreak(messageLine)) {
         for (String nextLine : Splitter.on("<br/>").trimResults().omitEmptyStrings().split(messageLine)) {
            log.messageLine(nextLine);
         }
      } else if (StringUtils.isNotBlank(messageLine)) {
         log.messageLine(messageLine);
      }
   }

   private boolean hasHtmlBreak(String message) {
      return org.apache.commons.lang3.Strings.CS.contains(message, "<br/>");
   }
}
