package incheon.ags.dss.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.math.BigDecimal;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.MalformedInputException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * .txt 통계 파일(ana_stats_data_dtl) 로더.
 * AbstractFileLoader의 구현체.
 */
@Profile("data-load-dss")
@Component
@Order(2)
public class FileDataLoader extends AbstractFileLoader {

    private static final Logger log = LoggerFactory.getLogger(FileDataLoader.class);

    private static final String INSERT_SQL =
            "INSERT INTO icdss.ana_stats_data_dtl (baye, scp_cd, stats_artcl, stats_vl, frst_reg_id, last_mdfcn_id) " +
            "VALUES (?, ?, ?, ?, 'BATCH', 'BATCH') " +
            "ON CONFLICT (baye, scp_cd, stats_artcl) " +
            "DO UPDATE SET stats_vl = EXCLUDED.stats_vl";

    private static final int CHUNK_SIZE = 1000;

    @Value("${dss.loader.txt.src:data/dss/txt/src}")
    private String dssLoaderTxtSrc;

    @Value("${dss.loader.txt.done:data/dss/txt/tmp}")
    private String dssLoaderTxtTmp;

    public FileDataLoader() {
        super(log); // 부모 클래스에 로거 전달
    }

    // --- AbstractFileLoader 구현 ---

    @Override
    protected String getLoaderName() {
        return "텍스트 파일 (ana_stats_data_dtl)";
    }

    @Override
    protected Path getSourceDirectory() {
        return Paths.get(dssLoaderTxtSrc);
    }

    @Override
    protected Path getProcessedDirectory() {
        return Paths.get(dssLoaderTxtTmp);
    }

    @Override
    protected boolean isTargetFile(Path path) {
        return path.toString().endsWith(".txt");
    }

    @Override
    protected String getInsertSql() {
        return INSERT_SQL;
    }

    @Override
    protected int getChunkSize() {
        return CHUNK_SIZE;
    }

    @Override
    protected void moveProcessedFileGroup(Path sourceFile, Path processedDir) throws IOException {
        // .txt 파일은 단일 파일이므로 기본 이동 로직 사용
        super.moveSingleFile(sourceFile, processedDir);
    }

    /**
     * .txt 파일 파싱 로직
     */
    @Override
    protected List<Object[]> parseFile(Path path) throws Exception {
        String filename = path.getFileName().toString();
        List<Object[]> fileData = new ArrayList<>();

        // 복잡한 인코딩 처리를 거쳐 라인 스트림 획득
        try (Stream<String> lines = getLinesStream(path)) {
            if (lines == null) {
                log.error("[{}] 모든 인코딩 처리 실패. 파일을 스킵합니다.", filename);
                return fileData; // 빈 리스트 반환 (오류지만 트랜잭션 롤백은 아님)
            }

            // 라인별 파싱
            lines.forEach(line -> {
                if (line == null || line.trim().isEmpty()) {
                    return;
                }
                try {
                    String[] parts = line.split("\\^");
                    if (parts.length == 4) {
                        Short baye = Short.parseShort(parts[0]);
                        String scpCd = parts[1];
                        String statIem = parts[2];
                        BigDecimal statValue = parseStatValue(parts[3]);
                        fileData.add(new Object[] { baye, scpCd, statIem, statValue });
                    } else {
                        log.warn("라인 형식이 맞지 않습니다 (스킵): {} | 내용: {}", filename, line);
                    }
                } catch (Exception e) {
                	// 파싱 중 에러 발생 시 입력 데이터(line)가 너무 길거나 민감 정보일 수 있으므로 로그 레벨 분리
                    log.error("[{}] 데이터 라인 파싱 실패 (건너뜀)", filename);
                    log.debug("Parsing Failed - Line: {}, Error: {}", line, e);
                }
            });

        } catch (IOException e) {
            log.error("파일 스트림을 여는 중 오류 발생(파일 스킵): {}", filename, e);
            // 이 예외는 상위로 전달되어 트랜잭션 롤백을 유발할 수 있음 (설계에 따라 결정)
            // 여기서는 스킵으로 처리.
        }
        return fileData;
    }

    // --- FileDataLoader 고유 헬퍼 메서드 ---

    /**
     * stats_vl 파싱 (N/A 처리)
     */
    private BigDecimal parseStatValue(String rawValue) {
        if ("N/A".equalsIgnoreCase(rawValue)) {
            return null;
        }
        try {
            return new BigDecimal(rawValue);
        } catch (NumberFormatException e) {
            return null; // 숫자 변환 실패 시 null
        }
    }

    /**
     * 복잡한 인코딩(UTF-8 -> MS949 -> UTF-8-ignore)을 처리하며 파일 라인 스트림을 반환합니다.
     */
    private Stream<String> getLinesStream(Path path) throws IOException {
        String filename = path.getFileName().toString();

        // 1. UTF-8 시도
        try {
            // Stream을 반환하지 않고 List로 미리 읽어야 catch 블록에서 재시도 가능
            return Files.lines(path, StandardCharsets.UTF_8).collect(Collectors.toList()).stream();
        } catch (UncheckedIOException e) {
            if (!(e.getCause() instanceof MalformedInputException)) {
            	log.error("[{}] UTF-8 디코딩 중 시스템 오류 발생 (파일 스킵)", filename);
                log.debug("UTF-8 Decoding Error", e);
                throw e; // 재시도 불가 오류
            }
        }

        // 2. MS949 시도
        log.warn("[{}] UTF-8 디코딩 실패. MS949로 재시도합니다.", filename);
        try {
            return Files.lines(path, java.nio.charset.Charset.forName("MS949")).collect(Collectors.toList()).stream();
        } catch (UncheckedIOException e) {
            if (!(e.getCause() instanceof MalformedInputException)) {
            	log.error("[{}] MS949 디코딩 중 시스템 오류 발생 (파일 스킵)", filename);
                log.debug("MS949 Decoding Error", e);
                throw e; // 재시도 불가 오류
            }
        }

        // 3. UTF-8 (손상 문자 무시)
        log.error("[{}] MS949 디코딩도 실패. 손상 문자를 무시하며 UTF-8 재시도.", filename);
        CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
        decoder.onMalformedInput(CodingErrorAction.IGNORE);
        decoder.onUnmappableCharacter(CodingErrorAction.IGNORE);

        try (InputStream in = Files.newInputStream(path);
             Reader reader = new InputStreamReader(in, decoder);
             BufferedReader br = new BufferedReader(reader)) {
            // 이 시점에서는 Stream을 반환해야 하므로 List로 변환
            return br.lines().collect(Collectors.toList()).stream();
        } catch (IOException finalEx) {
            log.error("손상 문자를 무시하는 UTF-8 재시도도 실패(파일 스킵): {}", filename, finalEx);
            return null; // 모든 시도 실패
        }
    }
}