package com.eactive.eai.rms.ext.kakaocard.cm;

import com.eactive.eai.common.util.DatetimeUtil;
import com.eactive.eai.common.util.UUIDGenerator;
import com.eactive.eai.rms.common.cm.CmLogService;
import com.eactive.eai.rms.common.context.MonitoringContext;
import com.eactive.eai.rms.common.datasource.DataSourceContextHolder;
import com.eactive.eai.rms.common.datasource.DataSourceTypeManager;
import com.eactive.eai.rms.data.entity.man.cm.CmLogEntity;
import com.eactive.eai.rms.ext.kakaocard.cm.except.CmMoveDirectoryException;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import software.amazon.awssdk.core.ResponseInputStream;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;

import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

public abstract class KakaoCardCmAbstractService implements KakaoCardCmService {
   
   protected static final Logger logger = Logger.getLogger("DYNAMIC_LOGGER");
   
   @Autowired @Qualifier("monitoringContext")
    protected MonitoringContext monitoringContext;
   
   @Autowired
   protected S3Client s3Client;
   
   @Autowired
   protected CmLogService cmLogService;
   
   protected void storeResourceFilesFromAWS_S3(String serviceType, String commitId) throws IOException {
      
      if(System.getProperty("eai.datasource.type").equals("LOCAL")) {
         return;
      }
      
      String bucketName = monitoringContext.getStringProperty(MonitoringContext.AWS_S3_CM_BUCKET_NAME);
      
      String objectKey = "dat" + File.separator + serviceType + File.separator + serviceType + "-" + commitId + ".zip";
      
      logger.debug("Try to download from bucket: ["+bucketName+"], objectKey: ["+objectKey+"]");
      
      GetObjectRequest getObjectRequest = 
            GetObjectRequest.builder()
            .bucket(bucketName)
            .key(objectKey)
            .build();
      
      ResponseInputStream<GetObjectResponse> s3FileStream = s3Client.getObject(getObjectRequest);
      
      String uploadPath = 
            monitoringContext.getStringProperty(MonitoringContext.CM_DEVELOPER_UPLOAD_PATH, CM_DEVELOPER_UPLOAD_PATH_DEFAULT) + serviceType + File.separator + commitId + File.separator;
      
      if (new File(uploadPath).exists()) {
         deleteDirectoryRecursively(Paths.get(uploadPath));
      }
      
      try (ZipInputStream zis = new ZipInputStream(s3FileStream)) {
          ZipEntry zipEntry;
          while ((zipEntry = zis.getNextEntry()) != null) {
              File destFile = new File(uploadPath, zipEntry.getName());
              // 디렉토리인 경우 디렉토리 생성
              if (zipEntry.isDirectory()) {
                  destFile.mkdirs();
              } else {
                  // 파일인 경우 파일 복사
                  File parentDir = destFile.getParentFile();
                  if (!parentDir.exists()) {
                      parentDir.mkdirs();
                  }
                  Files.copy(zis, destFile.toPath());
              }
          }
      } catch (Exception e) {
          throw new RuntimeException(e);
      }
      
   }
   
   protected List<String> getAppCodesFromDirectory(String serviceType, String commitId) {
      
      List<String> appCodes = new ArrayList<>();
      
      Path uploadPath = Paths.get(
            monitoringContext.getStringProperty(MonitoringContext.CM_DEVELOPER_UPLOAD_PATH, CM_DEVELOPER_UPLOAD_PATH_DEFAULT)
            + serviceType + File.separator + commitId);
      
      try (DirectoryStream<Path> onlyDirectory = Files.newDirectoryStream(uploadPath, entry -> Files.isDirectory(entry));) {
         
         for(Path path : onlyDirectory) {
            appCodes.add(path.getFileName().toString());
         }
         
         if (appCodes.size() == 0) {
            throw new RuntimeException("No AppCode Directory In Folder:" + uploadPath.toString());
         }
         
         return appCodes;
      } catch (IOException e) {
         logger.error(e);
         throw new RuntimeException(e);
      }

   }
   
   private void deleteDirectoryRecursively(Path path) throws IOException {
        Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                Files.delete(file);
                return FileVisitResult.CONTINUE;
            }
            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                Files.delete(dir);
                return FileVisitResult.CONTINUE;
            }
        });
    }

   protected void cmLogging(String afterPath, String serviceType, String appCode, String deployId, String successYn, String rollbackYn, String errorMsg, String deployStartTime, String currentDateTime) throws Exception {

      logger.debug("CM작업내용 Logging.. deployId:"+ deployId);
      DataSourceContextHolder.setDataSourceType(DataSourceTypeManager.getDataSourceType(DataSourceTypeManager.MONITORING));

      CmLogEntity log = new CmLogEntity();
      log.setGuid(UUIDGenerator.getUUID());
      log.setService_type(serviceType);
      log.setAppl_cd(appCode);
      log.setDeploy_id(deployId);
      log.setSuccess_yn(successYn);
      log.setRollback_yn(rollbackYn);
      log.setError_msg(errorMsg);
      log.setDeploy_time(deployStartTime);
      log.setDeploy_end_time(DatetimeUtil.getCurrentDateTime());

      cmLogService.insertLog(log, afterPath);
   }
   
   /*
    * 폴더 이동
    * after folder check & move
    * 이동순서 uploadFolder => afterFolder => backupFolder
    * uploadFolder는 원본데이터, afterFolder는 적용된 데이터(최근꺼만 존재), backupFolder 은 적용된게 여러개일경우(히스토리성)
    */
   protected void moveFiles(String uploadPath, String afterPath, String backupPath, String serviceType, String deployId) throws IOException  {
      
      /*
       * If after exist, after -> backup
       */
      File afterFolder = new File(afterPath + File.separator + serviceType + File.separator + deployId);
      Path afterFolderPath = Paths.get(afterPath + File.separator + serviceType + File.separator + deployId + File.separator);
       logger.info("afterFolder : " + afterFolder.getAbsolutePath() + " , " + afterFolder.exists());
      if (afterFolder.exists()) {
          
          File backupFolder = new File(backupPath+ File.separator + serviceType + File.separator+ deployId + "_" + DatetimeUtil.getCurrentTimeMsNoMark(afterFolder.lastModified()));
          Path backupFolderPath = Paths.get(backupPath+ File.separator + serviceType + File.separator+ deployId + "_" + DatetimeUtil.getCurrentTimeMsNoMark(afterFolder.lastModified()));
          
          logger.info("backupFolder : " + backupFolder.getAbsolutePath());
          
          try {
             if (backupFolder.exists() && backupFolder.isDirectory()){
                 deleteDirectory(backupFolderPath);
              }
             Files.createDirectories(backupFolderPath);
             Files.move(afterFolderPath, backupFolderPath, StandardCopyOption.REPLACE_EXISTING);
             logger.info("move folder success : " + afterFolder.getAbsolutePath() + " to " + backupFolder.getAbsolutePath());
          } catch (Exception e) {
             logger.error("move folder error : " + afterFolder.getAbsolutePath() + " to " + backupFolder.getAbsolutePath());
             throw new CmMoveDirectoryException(e);
          }
          
       }
      
      /*
       * upload -> after
       */
      File uploadFolder = new File(uploadPath + File.separator + serviceType + File.separator + deployId);
      Path uploadFolderPath = Paths.get(uploadPath + File.separator + serviceType + File.separator + deployId + File.separator);
       logger.info("uploadFolder : " + uploadFolder.getAbsolutePath() + " , "+uploadFolder.exists() );
       if (uploadFolder.exists()){
          
         try {
            deleteDirectory(afterFolderPath);
            Files.createDirectories(afterFolderPath);
            Files.move(uploadFolderPath, afterFolderPath, StandardCopyOption.REPLACE_EXISTING);
            logger.info("move folder success : " + uploadFolder.getAbsolutePath() + " to " + afterFolder.getAbsolutePath());
         } catch (Exception e) {
            logger.error("move folder error : " + uploadFolder.getAbsolutePath() + " to " + afterFolder.getAbsolutePath());
            throw new CmMoveDirectoryException(e);
         }
          
       } else {
          logger.error("upload folder does not exist : " + uploadFolder.getAbsolutePath());
          throw new CmMoveDirectoryException("upload folder does not exist : " + uploadFolder.getAbsolutePath());
       }
       
   }
   
   private void deleteDirectory(Path directory) throws IOException {
       if (Files.isDirectory(directory)) {
           try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) {
               for (Path path : stream) {
                   deleteDirectory(path);
               }
           }
       }
       Files.delete(directory);
   }

}