package com.eactive.ext.kjb.eai;

import com.eactive.ext.kjb.common.KjbEaiBatchException;
import com.eactive.ext.kjb.common.KjbProperty;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.net.URIBuilder;
import org.apache.hc.core5.util.Timeout;

import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Comparator;
import java.util.concurrent.*;
import java.util.stream.Stream;

@Slf4j
public class KjbEaiBatchModule {
    private final KjbProperty property;
    private final CloseableHttpClient httpClient;
    private final RequestConfig requestConfig;
    private LocalDateTime lastFileRetentionCheckTime;



    public KjbEaiBatchModule(@NotNull KjbProperty property) {
        if (property == null) {
            throw new IllegalArgumentException("KjbProperty cannot be null");
        }

        this.property = property;
        requestConfig = RequestConfig.custom()
                .setConnectTimeout(Timeout.ofSeconds(property.getEaiBatchConnectionTimeout()))
                .setResponseTimeout(Timeout.ofSeconds(property.getEaiBatchResponseTimeout()))
                .build();

        if (StringUtils.isNotEmpty(property.getEaiBatchUrl())) {
            this.httpClient = HttpClients.createDefault();
        } else {
            this.httpClient = null;
            log.warn("EAI_BATCH_URL 가 설정되지 않음. 실제 EAI 배치 연동 없이 동작함");
        }
    }

    public EaiBatchMessage call(@Valid EaiBatchInterface intf, String filename) throws KjbEaiBatchException {
        EaiBatchMessage reqMessage = EaiBatchMessage.createRequest(intf.getId(), filename, property.getEaiBatchCharset());
        log.debug("EAI 배치 호출: {}", reqMessage);

        // 송신 파일 체크
        File sendFile = Paths.get(intf.getSendDir(), filename).toFile();
        if (!sendFile.exists() || !sendFile.isFile()) {
            throw new KjbEaiBatchException("Not found send file - " + sendFile.toPath());
        }

        if (this.httpClient != null) {
            URI uri = null;
            try {
                uri = new URIBuilder(property.getEaiBatchUrl())
                        .addParameter("eaibatMsg", reqMessage.toString())
                        .build();
            } catch (URISyntaxException e) {
                throw new RuntimeException(e);
            }
            log.debug("EAI Batch call - {}", uri.toASCIIString());

            final HttpGet httpGet = new HttpGet(uri);
            httpGet.setConfig(requestConfig);

            ExecutorService executor = Executors.newSingleThreadExecutor();
            Future<String> future = executor.submit(() ->
                    httpClient.execute(httpGet, response -> EntityUtils.toString(response.getEntity(), "EUC-KR")));

            EaiBatchMessage resMessage = null;
            try {
                String message = future.get(property.getEaiBatchTimeout(), TimeUnit.SECONDS);
                resMessage = EaiBatchMessage.fromResponse(message);
                log.debug("EAI 배치 응답: {}", resMessage);

                return resMessage;
            } catch (InterruptedException | ExecutionException | TimeoutException e) {
                log.error("EAI Batch call exception", e);
                throw new KjbEaiBatchException("EAI Batch call failed", e);
            } finally {
                executor.shutdown();

                // EAI 송신 실패일 경우 에러 경로로 이동
                if (resMessage != null && "1".equals(resMessage.getResultCode())) {
                    intf.moveBackup(filename);
                } else {
                    intf.moveError(filename);
                }
            }
        } else {
            log.info("EAI_BATCH_URL 이 설정되지 않아 실제 호출하지 않음. 요청 메시지: {}", reqMessage);

            // 정상응답 리턴
            EaiBatchMessage resMessage = EaiBatchMessage.fromResponse(String.format("%-19s%-8s%-10s%-1s%s@@", intf.getId(), "", "0000000000", "1", "S:" + filename));
            log.debug("EAI 배치 응답(모의): {}", resMessage);
            intf.moveBackup(filename);
            return resMessage;
        }
    }

    /**
     * 1시간 주기로 파일 유지 및 삭제 처리
     */
    public void processFileRetentionDays() {
        if (lastFileRetentionCheckTime == null) {
            lastFileRetentionCheckTime = LocalDateTime.now();
        }

        // 현재 시간과 1시간 이상 차이 날때 동작
        LocalDateTime now = LocalDateTime.now();
        boolean isOver = Duration.between(lastFileRetentionCheckTime, now).abs().toHours() == 1;

        if (!isOver) return;

        this.walkDeletePath(property.getUmsEaiBatchSendDir());
        this.walkDeletePath(property.getUmsEaiBatchBackupDir());
        this.walkDeletePath(property.getUmsEaiBatchErrorDir());

        lastFileRetentionCheckTime = now;
    }

    private void walkDeletePath(String path) {
        final int retentionDays = property.getUmsEaiBatchRetentionDate();
        log.info("WalkDelete - {} / Retention days : {}", path, retentionDays);

        Path dir = Paths.get(path);
        LocalDateTime now = LocalDateTime.now();

        try (Stream<Path> paths = Files.walk(dir)) {
            paths.sorted(Comparator.reverseOrder()).forEach(p -> {
                try {
                    BasicFileAttributes attrs = Files.readAttributes(p, BasicFileAttributes.class);
                    LocalDateTime created = attrs.creationTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();

                    long over = Math.abs(Duration.between(created, now).toDays());
                    if (over >= retentionDays) {
                        Files.deleteIfExists(p);
                        log.info("Delete over {} days - {}", retentionDays, p);
                    }
                } catch (Exception e) {
                    log.warn("Delete fail - {}", p.getFileName().toString(), e);
                }
            });
        } catch (IOException e) {
            log.warn("Not found path : {}", path);
        }
    }
}
