package com.eactive.eai.rms.onl.dashboard.modern.backup;

import com.eactive.eai.common.monitor.MonitorVO;
import com.eactive.eai.rms.common.context.MonitoringContext;
import com.eactive.eai.rms.onl.dashboard.modern.backup.mapper.Dashboard2RepositoryVoMapper;
import com.eactive.eai.rms.onl.dashboard.modern.backup.mapper.RepositoryVoMapper;
import com.eactive.eai.rms.onl.dashboard.modern.backup.mapper.ThroughputVoMapper;
import com.eactive.eai.rms.onl.dashboard.repository.Dashboard2Repository;
import com.eactive.eai.rms.onl.server.repository.Repository;
import com.eactive.eai.rms.onl.tracking.vo.MemStaticsVo;
import com.eactive.eai.rms.onl.vo.MonitorDataVo;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
@RequiredArgsConstructor
public class RepositoryBackupService implements ApplicationListener<ContextRefreshedEvent> {

    @Value("${inst.Type:ems}")
    private String instType;

    @Value("${inst.Name:ems-0}")
    private String instName;

    private static final DateTimeFormatter BACKUP_TIMESTAMP_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

    private final Repository repository;
    private final Dashboard2Repository dashboard2Repository;
//    private final ThroughputCalculationService throughputCalculationService;

    @Autowired
    @Qualifier("monitoringContext")
    private final MonitoringContext monitoringContext;

    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
    private final ObjectMapper objectMapper =
            new ObjectMapper()
                    .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
                    .addMixIn(MonitorDataVo.class, MonitorDataVoMixIn.class)
                    .addMixIn(MonitorVO.class, MonitorVOMixin.class)
                    .addMixIn(MemStaticsVo.class, MemStaticsVoMixIn.class);

    private final RepositoryVoMapper rMapper = new RepositoryVoMapper();
    private final Dashboard2RepositoryVoMapper d2rMapper = new Dashboard2RepositoryVoMapper();
    private final ThroughputVoMapper tMapper = new ThroughputVoMapper();

    private boolean readFlag = false;
    private boolean backupFlag = true;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {

        String repositoryBackupEnable = monitoringContext.getProperty("repository.backup.enable", "false");

        backupFlag = BooleanUtils.toBoolean(repositoryBackupEnable);

        if (!backupFlag) {
            return;
        }

        try {

            // 백업 디렉토리와 경로 생성
            String backupDirectory = monitoringContext.getProperty("repository.backup.path", "/log/elink/");
            final Path backupFilePath = Paths.get(backupDirectory, instType, instName, "repository", "backup.json");

            // 파일이 존재하고 아직 읽지 않은 경우에 진행
            synchronized (this) {
                if (Files.exists(backupFilePath) && !readFlag) {
                    String json = new String(Files.readAllBytes(backupFilePath), StandardCharsets.UTF_8);
                    if (StringUtils.isNotBlank(json)) {
                        BackupContentVO backupContentVO = objectMapper.readValue(json, BackupContentVO.class);
                        LocalDateTime fileTime = LocalDateTime.parse(backupContentVO.getBackupTimestamp(), BACKUP_TIMESTAMP_FORMATTER);

                        // 백업 파일의 날짜가 오늘보다 이전이면 삭제, 그렇지 않으면 복원 수행
                        if (fileTime.toLocalDate().isBefore(LocalDate.now())) {
                            synchronized (this) {
                                Files.delete(backupFilePath);
                            }
                        } else {
                            rMapper.setVoValuesToObject(backupContentVO.getRepositoryFields(), repository);
                            d2rMapper.setVoValuesToObject(backupContentVO.getDashboardRepository2Fields(), dashboard2Repository);
                            // tMapper.setVoValuesToObject(backupContentVO.getThroughputFields(), throughputCalculationService);
                            readFlag = true;
                        }
                    }
                }
            }

            // 10분마다 BackupContentVO 생성해서 저장함
            scheduler.scheduleAtFixedRate(() -> {

                if (!backupFlag)
                    return;

                log.info("Repository Data Backup");

                try {
                    // VO
                    BackupContentVO backupContentVO = new BackupContentVO();
                    backupContentVO.setRepositoryFields(rMapper.toVo(repository));
                    backupContentVO.setDashboardRepository2Fields(d2rMapper.toVo(dashboard2Repository));
//                    backupContentVO.setThroughputFields(tMapper.toVo(throughputCalculationService));

                    synchronized (this) {
                        // File Write
                        if (!Files.exists(backupFilePath)) {
                            Files.createDirectories(backupFilePath.getParent());
                            Files.createFile(backupFilePath);
                        }

                        Files.write(backupFilePath, objectMapper.writerWithDefaultPrettyPrinter().writeValueAsBytes(backupContentVO));
                    }

                } catch (Exception e) {
                    log.error("Repository Backup 데이터 생성 중 오류가 발생했습니다.", e);
                }

            }, 3, 10, TimeUnit.MINUTES);

        } catch (Exception e) {
            log.error("Repository Backup 시작 설정 중 오류가 발생했습니다.", e);
        }

    }

    @PreDestroy
    public void destroy() {
        this.backupFlag = false;
    }

    // 매일 0시에 실행
    @Scheduled(cron = "0 0 0 * * ?")
    private void dailyCleanUp() {
        removeBackupFile();
    }

    public synchronized void removeBackupFile() {
        String backupFilePathStr = monitoringContext.getProperty("repository.backup.path", "/log/elink/repository/");
        backupFilePathStr = backupFilePathStr.endsWith("/") ? backupFilePathStr : backupFilePathStr + "/";
        backupFilePathStr += instType + "/" + instName + "/backup.json";
        final Path backupFilePath = Paths.get(backupFilePathStr);
        if (Files.exists(backupFilePath)) {
            try {
                Files.delete(backupFilePath);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

}
