package ervu.service.fileupload;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Locale;
import java.util.UUID;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import ervu.client.fileupload.WebDavClient;
import ervu.model.fileupload.DownloadResponse;
import ervu.model.fileupload.EmployeeInfoFileFormType;
import ervu.model.fileupload.EmployeeInfoKafkaMessage;
import ervu.model.fileupload.FileInfo;
import ervu.model.fileupload.FileStatus;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.tika.Tika;
import org.apache.tika.mime.MediaType;
import org.apache.tika.mime.MimeType;
import org.apache.tika.mime.MimeTypeException;
import org.apache.tika.mime.MimeTypes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import ru.micord.ervu.audit.service.AuditService;
import ru.micord.ervu.exception.JsonParsingException;
import ru.micord.ervu.security.esia.model.EmployeeModel;
import ru.micord.ervu.security.esia.model.PersonModel;
import ru.micord.ervu.security.esia.service.UlDataService;
import ru.micord.ervu.security.esia.EsiaAuthInfoStore;
import ru.micord.ervu.security.webbpm.jwt.UserIdsPair;
import ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil;
import ru.micord.ervu.service.InteractionService;
import ru.micord.ervu.util.DateUtils;

import static ervu.enums.FileStatusCode.FILE_CLEAN;
import static ervu.enums.FileStatusCode.FILE_INFECTED;
import static ervu.enums.FileStatusCode.FILE_NOT_CHECKED;
import static ervu.enums.FileStatusCode.FILE_UPLOADED;
import static ru.micord.ervu.util.StringUtils.convertToFio;

/**
 * @author Alexandr Shalaginov
 */
@Service
public class EmployeeInfoFileUploadService {
  private static final Logger LOGGER = LoggerFactory.getLogger(EmployeeInfoFileUploadService.class);
  private final WebDavClient webDavClient;
  private final EmployeeInfoKafkaMessageService employeeInfoKafkaMessageService;
  private final KafkaTemplate<String, String> kafkaTemplate;
  private final InteractionService interactionService;
  private final UlDataService ulDataService;
  private final AuditService auditService;

  @Value("${av.kafka.message.topic.name}")
  private String kafkaTopicName;

  public EmployeeInfoFileUploadService(
      WebDavClient webDavClient,
      EmployeeInfoKafkaMessageService employeeInfoKafkaMessageService,
      @Qualifier("avTemplate") KafkaTemplate<String, String> kafkaTemplate,
      InteractionService interactionService,
      UlDataService ulDataService,
      AuditService auditService) {
    this.webDavClient = webDavClient;
    this.employeeInfoKafkaMessageService = employeeInfoKafkaMessageService;
    this.kafkaTemplate = kafkaTemplate;
    this.interactionService = interactionService;
    this.ulDataService = ulDataService;
    this.auditService = auditService;
  }

  public boolean saveEmployeeInformationFile(MultipartFile multipartFile, String formType,
                                             String offset) {
    UserIdsPair userIdsPair = SecurityUtil.getUserIdsPair();

    if (userIdsPair == null || !isValid(multipartFile)) {
      return false;
    }
    String fileId = UUID.randomUUID().toString();
    String fileName = multipartFile.getOriginalFilename();
    EmployeeInfoFileFormType employeeInfoFileFormType = EmployeeInfoFileFormType.valueOf(
        formType);
    String esiaUserId = userIdsPair.getEsiaUserId();
    String ervuId = userIdsPair.getErvuId();
    String accessToken = EsiaAuthInfoStore.getAccessToken(esiaUserId);
    EmployeeModel employeeModel = ulDataService.getEmployeeModel(accessToken);
    PersonModel personModel = employeeModel.getPerson();

    String fileUploadUrl = this.webDavClient.uploadFile(multipartFile);
    FileStatus fileStatus = new FileStatus();
    fileStatus.setStatus(fileUploadUrl == null ? "Невозможно проверить файл ЛК РП" : "Загрузка");
    LocalDateTime now = LocalDateTime.now();
    interactionService.setStatus(fileId, fileStatus.getStatus(), fileName,
        employeeInfoFileFormType.getFilePatternCode(), Timestamp.valueOf(now),
        convertToFio(personModel.getFirstName(), personModel.getMiddleName(),
            personModel.getLastName()
        ),
        ervuId
    );

    long fileSize = multipartFile.getSize();
    String departureDateTime = DateUtils.convertToString(now);
    EmployeeInfoKafkaMessage kafkaMessage = employeeInfoKafkaMessageService.getKafkaMessage(
        fileId,
        fileUploadUrl,
        fileName,
        employeeInfoFileFormType,
        departureDateTime,
        accessToken,
        offset,
        fileStatus,
        ervuId,
        esiaUserId,
        personModel,
        fileSize
    );

    if (fileUploadUrl != null) {
      fileStatus.setCode(FILE_UPLOADED.getCode());
      fileStatus.setDescription("Файл принят до проверки на вирусы");
      String jsonMessage = getJsonKafkaMessage(kafkaMessage);
      return sendMessage(jsonMessage);
    }
    else {
      LOGGER.error("Failed to upload file: {}", fileName);
      fileStatus.setCode(FILE_NOT_CHECKED.getCode());
      fileStatus.setDescription("Невозможно проверить файл по причине недоступности или ошибки в работе антивируса");
      auditService.processUploadEvent(kafkaMessage.getOrgInfo(), kafkaMessage.getFileInfo());
      return false;
    }
  }

  private boolean isValid(MultipartFile multipartFile) {

    if (multipartFile == null || multipartFile.getOriginalFilename() == null) {
      return false;
    }
    String fileName = multipartFile.getOriginalFilename();
    try {
      String contentType = new Tika().detect(multipartFile.getBytes());
      MimeTypes defaultMimeTypes = MimeTypes.getDefaultMimeTypes();
      MimeType mimeType = defaultMimeTypes.forName(contentType);
      boolean isCsv = mimeType.getType().equals(MediaType.TEXT_PLAIN)
                      && fileName.toLowerCase(Locale.getDefault()).endsWith(".csv");

      if (!isCsv) {
        LOGGER.info("Trying to upload file={} with wrong mime type={}",
            fileName, mimeType
        );
      }
      return isCsv;
    }
    catch (MimeTypeException e) {
      LOGGER.error(
          "Couldn't get mime type from bytes for file=" + fileName, e);
      return false;
    }
    catch (IOException e) {
      LOGGER.error("Error while checking file type, file=" + fileName,
          e
      );
      return false;
    }
  }

  private boolean sendMessage(String message) {
    ProducerRecord<String, String> record = new ProducerRecord<>(this.kafkaTopicName, message);
    record.headers()
        .add("messageId", UUID.randomUUID().toString().getBytes(StandardCharsets.UTF_8));

    try {
      this.kafkaTemplate.send(record).get();
      LOGGER.debug("Successfully sent record: {}", record);
      return true;
    }
    catch (Exception exception) {
      LOGGER.error("Failed to send message", exception);
      return false;
    }
  }

  private String getJsonKafkaMessage(EmployeeInfoKafkaMessage employeeInfoKafkaMessage) {
    ObjectMapper mapper = new ObjectMapper();
    try {
      return mapper.writeValueAsString(employeeInfoKafkaMessage);
    }
    catch (JsonProcessingException e) {
      throw new JsonParsingException(
          String.format("Fail get json from: %s", employeeInfoKafkaMessage), e);
    }
  }

  @KafkaListener(id = "${av.kafka.group.id}", topics = "${av.kafka.download.response}",
      containerFactory = "avContainerFactory")
  public void listenKafka(String kafkaMessage) {
    ObjectMapper mapper = new ObjectMapper();

    try {
      DownloadResponse downloadResponse = mapper.readValue(kafkaMessage, DownloadResponse.class);
      FileInfo fileInfo = downloadResponse.fileInfo();
      String statusCode = fileInfo.getFileStatus().getCode();

      if (Arrays.asList(FILE_INFECTED.getCode(), FILE_CLEAN.getCode()).contains(statusCode)) {
        interactionService.delete(fileInfo.getFileId(), downloadResponse.orgInfo().getOrgId());
      }
      else if (statusCode.equals(FILE_NOT_CHECKED.getCode())) {
        auditService.processUploadEvent(downloadResponse.orgInfo(), downloadResponse.fileInfo());
        interactionService.updateStatus(fileInfo.getFileId(), fileInfo.getFileStatus().getStatus(),
            downloadResponse.orgInfo().getOrgId()
        );
      }
    }
    catch (JsonProcessingException e) {
      throw new JsonParsingException(String.format("Fail get json from: %s", kafkaMessage), e);
    }
  }
}
