package ru.micord.ervu.audit.service.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletRequest;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import ervu.model.fileupload.FileInfo;
import ervu.model.fileupload.UploadOrgInfo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Service;
import ru.micord.ervu.audit.config.AuditEnabledCondition;
import ru.micord.ervu.audit.constants.AuditConstants;
import ru.micord.ervu.audit.model.*;
import ru.micord.ervu.audit.service.AuditKafkaPublisher;
import ru.micord.ervu.audit.service.AuditService;
import ru.micord.ervu.exception.JsonParsingException;
import ru.micord.ervu.kafka.model.OrgInfo;
import ru.micord.ervu.security.esia.model.EsiaAccessToken;
import ru.micord.ervu.security.esia.service.UlDataService;
import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService;
import ru.micord.ervu.util.DateUtils;
import ru.micord.ervu.util.NetworkUtils;

/**
 * @author Adel Kalimullin
 */
@Service
@Conditional(AuditEnabledCondition.class)
public class BaseAuditService implements AuditService {
  private final AuditKafkaPublisher auditKafkaPublisher;
  private final JwtTokenService jwtTokenService;
  private final UlDataService ulDataService;
  private final ObjectMapper objectMapper;
  @Value("${audit.kafka.authorization.topic:ervu.lkrp.auth.events}")
  private String authorizationTopic;
  @Value("${audit.kafka.action.topic:ervu.lkrp.action.events}")
  private String actionTopic;
  @Value("${audit.kafka.file.upload.topic:ervu.lkrp.download.request}")
  private String fileUploadTopic;
  @Value("${audit.kafka.file.download.topic:ervu.lkrp.import.file}")
  private String fileDownloadTopic;

  public BaseAuditService(AuditKafkaPublisher auditKafkaPublisher, JwtTokenService jwtTokenService,
      UlDataService ulDataService, ObjectMapper objectMapper) {
    this.auditKafkaPublisher = auditKafkaPublisher;
    this.jwtTokenService = jwtTokenService;
    this.ulDataService = ulDataService;
    this.objectMapper = objectMapper;
  }

  @Override
  public void processActionEvent(HttpServletRequest request,
      AuditActionRequest auditActionRequest) {
    String orgId = getEsiaOrgId(request);
    String userAccountId = jwtTokenService.getUserAccountId(request);
    String description = AuditConstants.getRouteDescription(auditActionRequest.getRoute());

    List<SearchCriteria> searchAttributes = null;
    if (auditActionRequest.getFilterInfo() != null && !auditActionRequest.getFilterInfo().isEmpty()) {
      searchAttributes = getSearchCriteriaList(auditActionRequest.getFilterInfo());
    }

    AuditActionEvent event = new AuditActionEvent(
        orgId,
        userAccountId,
        DateUtils.getClientDateTimeWithZoneFromRequest(request),
        auditActionRequest.getEventType(),
        description,
        auditActionRequest.getSourceUrl(),
        searchAttributes,
        auditActionRequest.getFileName()
    );

    String message = convertToMessage(event);
    auditKafkaPublisher.publishEvent(actionTopic, message);
  }

  @Override
  public void processAuthEvent(HttpServletRequest request, OrgInfo orgInfo, String prnOid,
      String status, String eventType) {
    String serverIp = NetworkUtils.getServerIp();
    String clientIp = NetworkUtils.getClientIp(request);
    String serverHostName = NetworkUtils.getHostName(serverIp);
    String clientHostName = NetworkUtils.getHostName(clientIp);

    AuditAuthorizationEvent event = new AuditAuthorizationEvent(
        orgInfo.getOrgOid(),
        prnOid,
        DateUtils.getClientDateTimeWithZoneFromRequest(request),
        orgInfo.getOrgFullName(),
        orgInfo.getSenderInfo().getFirstName(),
        orgInfo.getSenderInfo().getLastName(),
        orgInfo.getSenderInfo().getMiddleName(),
        orgInfo.getInn(),
        status,
        eventType,
        serverIp,
        serverHostName,
        clientIp,
        clientHostName
    );

    String message = convertToMessage(event);
    auditKafkaPublisher.publishEvent(authorizationTopic, message);
  }

  @Override
  public void processUploadEvent(UploadOrgInfo orgInfo, FileInfo fileInfo) {
    AuditUploadEvent auditUploadEvent = new AuditUploadEvent(
        orgInfo,
        fileInfo
    );

    String message = convertToMessage(auditUploadEvent);
    auditKafkaPublisher.publishEvent(fileUploadTopic, message);
  }

  @Override
  public void processDownloadEvent(
      HttpServletRequest request, long fileSize, String fileName, int formatRegistry,
      String status, String s3FileUrl) {
    String userAccountId = jwtTokenService.getUserAccountId(request);

    AuditDownloadEvent event = new AuditDownloadEvent(
        getEsiaOrgId(request),
        userAccountId,
        DateUtils.getClientDateTimeWithZoneFromRequest(request),
        AuditConstants.getDownloadType(formatRegistry),
        fileName,
        s3FileUrl,
        String.valueOf(fileSize),
        status
    );

    String message = convertToMessage(event);
    auditKafkaPublisher.publishEvent(fileDownloadTopic, message);
  }

  private String getEsiaOrgId(HttpServletRequest request) {
    String accessToken = jwtTokenService.getAccessToken(request);
    EsiaAccessToken esiaAccessToken = ulDataService.readToken(accessToken);
    String scope = esiaAccessToken.getScope();
    return scope.substring(scope.indexOf('=') + 1, scope.indexOf(' '));
  }

  public List<SearchCriteria> getSearchCriteriaList(Map<String, FilterInfo> filterInfoMap) {
    List<SearchCriteria> searchCriteriaList = new ArrayList<>();

    for (Map.Entry<String, FilterInfo> entry : filterInfoMap.entrySet()) {
      String searchAttribute = entry.getKey();
      FilterInfo filterInfo = entry.getValue();
      String searchValue = filterInfo.getConditions().stream()
          .map(condition -> condition.getFilterValue() + " " + condition.getFilterType())
          .collect(Collectors.joining(", "));

      if (filterInfo.getConditionOperator() != null) {
        searchValue += "| Operator: " + filterInfo.getConditionOperator();
      }

      SearchCriteria searchCriteria = new SearchCriteria(searchAttribute, searchValue);
      searchCriteriaList.add(searchCriteria);
    }

    return searchCriteriaList;
  }

  private String convertToMessage(Object event) {
    try {
      return objectMapper.writeValueAsString(event);
    }
    catch (JsonProcessingException e) {
      throw new JsonParsingException(e);
    }
  }
}
