package com.eactive.eai.rms.common.cm;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.eactive.eai.rms.bat.loader.service.TransactionLoaderServiceImpl;
import com.eactive.eai.rms.common.cm.mapping.CmLogDetailUIMapper;
import com.eactive.eai.rms.common.cm.mapping.CmLogUIMapper;
import com.eactive.eai.rms.common.cm.ui.CmLogDetailUI;
import com.eactive.eai.rms.common.cm.ui.CmLogUI;
import com.eactive.eai.rms.common.cm.ui.CmLogUISearch;
import com.eactive.eai.rms.common.context.MonitoringContext;
import com.eactive.eai.rms.common.vo.GridResponse;
import com.eactive.eai.rms.data.entity.man.cm.CmLogDetailEntity;
import com.eactive.eai.rms.data.entity.man.cm.CmLogDetailEntityId;
import com.eactive.eai.rms.data.entity.man.cm.CmLogDetailEntityService;
import com.eactive.eai.rms.data.entity.man.cm.CmLogEntity;
import com.eactive.eai.rms.data.entity.man.cm.CmLogEntityService;
import com.eactive.eai.rms.onl.common.util.FileWriteUtil;

import software.amazon.awssdk.core.ResponseBytes;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.ListObjectsV2Request;
import software.amazon.awssdk.services.s3.model.ListObjectsV2Response;
import software.amazon.awssdk.services.s3.model.S3Object;

@Service
@Transactional(transactionManager = "transactionManagerForEMS")
public class CmLogService {
   
   private static final Logger logger = Logger.getLogger("DYNAMIC_LOGGER");
   
   public static final String DEPOLY_SUCCESS    = "Y";
   public static final String DEPOLY_FAIL       = "N";
   
   /** The monitoring context. */
    @Autowired
    @Qualifier("monitoringContext")
    private transient MonitoringContext monitoringContext;
    
    @Autowired
    @Qualifier("batLoaderService")
    private TransactionLoaderServiceImpl loaderService;
   
//   @Autowired private CmLogDao dao;
   
   @Autowired private S3Client s3Client;

   @Autowired
   private CmLogEntityService cmLogEntityService;

   @Autowired
   private CmLogDetailEntityService cmLogDetailEntityService;

   @Autowired
   private CmLogUIMapper cmLogUIMapper;

   @Autowired
   private CmLogDetailUIMapper cmLogDetailUIMapper;

   public void insertLog(CmLogEntity cmLogEntity, String path) throws Exception {

      logger.debug("CmLogService] Insert CM log to RM37");

      cmLogEntityService.save(cmLogEntity);

      if ("ALL".equals(cmLogEntity.getAppl_cd())) {
         
         String deployIdPath = path + cmLogEntity.getService_type() + File.separator + cmLogEntity.getDeploy_id();
         List<String> appCodesFromDirectory = getAppCodesFromDirectory(Paths.get(deployIdPath), cmLogEntity.getRollback_yn());
         
         for(String appCode : appCodesFromDirectory) {
            String fullPath = path + cmLogEntity.getService_type() + File.separator + cmLogEntity.getDeploy_id() + File.separator + appCode;
            
            String layoutPath =    fullPath + File.separator + FileWriteUtil.LAYOUT    + File.separator;
            String transformPath =    fullPath + File.separator + FileWriteUtil.TRANSFORM + File.separator;
            String interfacePath =    fullPath + File.separator + FileWriteUtil.INTERFACE + File.separator;
            String bapPath =      fullPath + File.separator + FileWriteUtil.BAP_CM_DIR+ File.separator; 
            
            logger.debug("CmLogService] Insert CM detail log to RM38");
            if("MCT".equals(cmLogEntity.getService_type())) {
               detailFilesLogging(cmLogEntity.getGuid(), layoutPath, path, "^.{4}T");
            } else if("BAP".equals(cmLogEntity.getService_type())) {
               detailFilesLogging(cmLogEntity.getGuid(), bapPath,       path);
            } else {
               detailFilesLogging(cmLogEntity.getGuid(), layoutPath,    path);
               detailFilesLogging(cmLogEntity.getGuid(), transformPath, path);
               detailFilesLogging(cmLogEntity.getGuid(), interfacePath, path);
            }
         }

      } else {
         String fullPath = path + cmLogEntity.getService_type() + File.separator + cmLogEntity.getDeploy_id() + File.separator + cmLogEntity.getAppl_cd();
         
         String layoutPath =    fullPath + File.separator + FileWriteUtil.LAYOUT    + File.separator;
         String transformPath =    fullPath + File.separator + FileWriteUtil.TRANSFORM + File.separator;
         String interfacePath =    fullPath + File.separator + FileWriteUtil.INTERFACE + File.separator;
         String bapPath =      fullPath + File.separator + FileWriteUtil.BAP_CM_DIR+ File.separator; 
         
         logger.debug("CmLogService] Insert CM detail log to RM38");
         if("MCT".equals(cmLogEntity.getService_type())) {
            detailFilesLogging(cmLogEntity.getGuid(), layoutPath,    path, "^.{4}T");
         } else if("BAP".equals(cmLogEntity.getService_type())) {
            detailFilesLogging(cmLogEntity.getGuid(), bapPath,       path);
         } else {
            detailFilesLogging(cmLogEntity.getGuid(), layoutPath,    path);
            detailFilesLogging(cmLogEntity.getGuid(), transformPath, path);
            detailFilesLogging(cmLogEntity.getGuid(), interfacePath, path);
         }
      }
      
   }
   
   private void detailFilesLogging(String guid, String path, String shouldBeDeletedOnPath) throws Exception {
      
      File files = new File(path);
      
      String[] fileNames = files.list();
      if(fileNames == null) {return;}
      
      for(String fileName : fileNames) {
         
         Path resourcePath = Paths.get(path+fileName);
         byte[] fileContentByte = Files.readAllBytes(resourcePath);
         String fileContent = new String(fileContentByte, StandardCharsets.UTF_8);

         CmLogDetailEntity cmLogDetailEntity = new CmLogDetailEntity();
         CmLogDetailEntityId cmLogDetailEntityId = new CmLogDetailEntityId();

         cmLogDetailEntityId.setGuid(guid);
         cmLogDetailEntityId.setResource_path(StringUtils.removeStart(path, shouldBeDeletedOnPath));
         cmLogDetailEntityId.setResource_name(fileName);

         cmLogDetailEntity.setId(cmLogDetailEntityId);
         cmLogDetailEntity.setResource_content(fileContent);

         cmLogDetailEntityService.save(cmLogDetailEntity);

//         dao.insertRM38(new HashMap<String, Object>() {{
//            put("guid", guid);
//            put("path", StringUtils.removeStart(path, shouldBeDeletedOnPath));
//            put("name", fileName);
//            put("content", fileContent);
//         }});
         
      }
      
   }
   
   /**
    * regExp에 match되는 fileName만 logging 합니다.
    */
   private void detailFilesLogging(String guid, String path, String shouldBeDeletedOnPath, String regExp) throws Exception {
      
      File files = new File(path);
      String[] fileNames = files.list();
      if(fileNames == null) {return;}
      
      Pattern pattern = Pattern.compile(regExp);
      
      for(String fileName : fileNames) {
         
         Matcher matcher = pattern.matcher(fileName);
         
         if(!matcher.find(0)) {
            continue;
         }
         
         Path resourcePath = Paths.get(path+fileName);
         byte[] fileContentByte = Files.readAllBytes(resourcePath);
         String fileContent = new String(fileContentByte, "EUC-KR");

         CmLogDetailEntity cmLogDetailEntity = new CmLogDetailEntity();
         CmLogDetailEntityId cmLogDetailEntityId = new CmLogDetailEntityId();

         cmLogDetailEntityId.setGuid(guid);
         cmLogDetailEntityId.setResource_path(StringUtils.removeStart(path, shouldBeDeletedOnPath));
         cmLogDetailEntityId.setResource_name(fileName);

         cmLogDetailEntity.setId(cmLogDetailEntityId);
         cmLogDetailEntity.setResource_content(fileContent);

         cmLogDetailEntityService.save(cmLogDetailEntity);
         
//         dao.insertRM38(new HashMap<String, Object>() {{
//            put("guid", guid);
//            put("path", StringUtils.removeStart(path, shouldBeDeletedOnPath));
//            put("name", fileName);
//            put("content", fileContent);
//         }});
         
      }
   }

//   @Transactional(readOnly = false, transactionManager = "transactionManager")
   public Page<CmLogUI> selectList(CmLogUISearch cmLogUISearch, Pageable pageable) throws Exception {

      Page<CmLogEntity> tuples = cmLogEntityService.selectList(cmLogUISearch, pageable);

      return tuples.map( tuple -> {
//         CmLog2Entity cmLog2Entity = tuple.get(QCmLog2Entity.cmLog2Entity);

//         CmLogUI cmLogUI = cmLogUIMapper.toVoWithoutChildren(cmLog2Entity);
         CmLogUI cmLogUI = cmLogUIMapper.toVo(tuple);

         return cmLogUI;
      });
   }
   
   public Page<CmLogDetailUI> selectDetailList(CmLogUISearch cmLogUISearch, Pageable pageable) throws Exception {

      Page<CmLogDetailEntity> tuples = cmLogDetailEntityService.selectList(cmLogUISearch, pageable);


      return tuples.map( tuple -> {
         CmLogDetailUI cmLogDetailUI = cmLogDetailUIMapper.toVo(tuple);

//         cmLogDetailUI.setResource_data(cmLogDetailUI.getResource_content());

         // DB에서 받아온 파일 데이터의 내용(RESOURCE_CONTENT)을 parseToString()으로 파싱하여 추가한다.
         String fileContent = cmLogDetailUI.getResource_content();

         if(fileContent == null || fileContent.equals("null")) {
            fileContent = "";
         }

         byte[] content = fileContent.getBytes();

         InputStream is = null;
         BufferedReader br = null;
         InputStreamReader isr = null;
         try {
            is = new ByteArrayInputStream(content);
            isr = new InputStreamReader(is);
            br = new BufferedReader(isr);

            cmLogDetailUI.setResource_data(loaderService.parseToString(br));
         } catch(Exception e) {
            logger.error("parse error : ", e);
         } finally{
            if (br != null) try{br.close();}catch(Exception e){}
            if (isr != null) try{isr.close();}catch(Exception e){}
            if (is != null) try{is.close();}catch(Exception e){}
         }

         return cmLogDetailUI;
      });
   }
   
//   public int setNullCmLogDetail(Period remain) {
//
//      String dateTime = LocalDateTime.now()
//            .minusDays(remain.getDays())
//            .format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
//
//      return cmLogDetailEntityService.setNullCmLogDetail(dateTime);
//   }

//   public int deleteCmLogAndDetail(Period remain) {
//
//      String dateTime = LocalDateTime.now()
//            .minusDays(remain.getDays())
//            .format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
//
//      return dao.deleteCmLogAndDetail(new HashMap<String, Object>() {{
//         put("dateTime", dateTime);
//      }});
//   }
   
   private List<String> getAppCodesFromDirectory(Path directory, String rollbackYN) {
      
      List<String> appCodes = new ArrayList<>();
      
      try (DirectoryStream<Path> onlyDirectory = Files.newDirectoryStream(directory, entry -> Files.isDirectory(entry));) {
         
         for(Path path : onlyDirectory) {
            appCodes.add(path.getFileName().toString());
         }
         
         if (appCodes.size() == 0 && rollbackYN.equals("N")) {
            throw new RuntimeException("No AppCode Directory In Folder:" + directory.toString());
         }
         
         return appCodes;
      } catch (IOException e) {
         logger.error(e);
         throw new RuntimeException(e);
      }

   }

   public GridResponse<Map<String, Object>> bucketFileList(Map<String, String> paramMap) {
      
      final String prefix = paramMap.get("pop_prefix");
      
      GridResponse<Map<String, Object>> result = new GridResponse<>();
      List<Map<String, Object>> resultContent = new ArrayList<>();
      
      try {
         
         String bucketName = monitoringContext.getStringProperty(MonitoringContext.AWS_S3_CM_BUCKET_NAME);
         
         ListObjectsV2Request listObjectsReqManual = ListObjectsV2Request.builder()
                   .bucket(bucketName)
                   .maxKeys(1000)
                   .prefix(prefix)
                   .build();
         
         boolean done = false;
           while (!done) {
               ListObjectsV2Response listObjResponse = s3Client.listObjectsV2(listObjectsReqManual);
               
               for (S3Object content : listObjResponse.contents()) {
                  Map<String, Object> row = new HashMap<>();
                  row.put("eTag", content.eTag());
                  row.put("key", content.key());
                  row.put("size", content.size());
                  row.put("storageClassAsString", content.storageClassAsString());
                  row.put("lastModifiedLong", content.lastModified().toEpochMilli());
                  resultContent.add(row);
               }

               if (listObjResponse.nextContinuationToken() == null) {
                   done = true;
               }

               listObjectsReqManual = ListObjectsV2Request.builder()
                       .bucket(bucketName)
                       .continuationToken(listObjResponse.nextContinuationToken())
                       .build();
           }
           
      } catch (Exception e) {
         logger.error(e);
         throw new RuntimeException(e);
      }
      
      result.setPage(1);
      result.setRecords(resultContent.size());
      result.setRows(resultContent);
      
      return result;
   }

   public byte[] getFileContent(String objectKey) {
      String bucketName = monitoringContext.getStringProperty(MonitoringContext.AWS_S3_CM_BUCKET_NAME);

      GetObjectRequest getObjectRequest = GetObjectRequest.builder().bucket(bucketName).key(objectKey).build();

      ResponseBytes<GetObjectResponse> objectAsBytes = s3Client.getObjectAsBytes(getObjectRequest);
      
      return objectAsBytes.asByteArray();
   }
   
}
