package kr.itn.itnhub.process;

import kr.itn.itnhub.seed.SeedParseException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

/**
 * 기관별 권리처리 엑셀(시트명 {@code 권리처리}, 없으면 첫 시트로 대체)을 읽는다.
 *
 * 시트 배치 (1행 그룹제목, 2행 헤더, 3행부터 데이터, A열(순번)이 빈 첫 행에서 데이터 끝):
 *   A 순번  B 사이트명  C 범주  D 게시판명  E 게시물제목  F URL주소  G 설명
 *   H 첨부파일여부  I 기존 공공누리유형(+AI)  J 계약서 유무  K 제작일  L 공표일
 *   M 권리확인(대분류)  N 권리확인(세부)  O 처리 구분  P 공공누리 유형  Q AI유형
 *   R 의견  S 비고  T 기존 증빙자료  U (수정 링크 열, 건너뜀)
 *   V 공공누리유형(판정)  W AI유형(판정)  X 최종의견  Y (빈 열, 건너뜀)
 *   Z 판단근거  AA 처리상태
 *
 * ReviewParser와 마찬가지로 lastRowNum까지 돌되 A열이 비면 즉시 멈춘다.
 *
 * 제작일/공표일(K, L열)은 실제 날짜서식이 입혀진 값이라 {@code yyyy-MM-dd} 문자열로
 * 정규화한다({@link DateUtil#isCellDateFormatted}로 판별). 그 외 숫자 셀은 ReviewParser와
 * 동일하게 반올림 문자열로 바꾼다. 수식 오류(#VALUE! 등) 셀은 캐시된 결과 타입이 ERROR가
 * 되어 default 분기(빈 문자열 → null)로 처리된다.
 */
@Component
public class ProcessParser {

    public static final String PRIMARY_SHEET_NAME = "권리처리";

    private static final int DATA_START_ROW_INDEX = 2; // 3행

    private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    static final int COL_SEQ = 0;               // A
    static final int COL_SITE_NAME = 1;         // B
    static final int COL_CATEGORY = 2;          // C
    static final int COL_BOARD_NAME = 3;        // D
    static final int COL_POST_TITLE = 4;        // E
    static final int COL_URL = 5;                // F
    static final int COL_DESCRIPTION = 6;       // G
    static final int COL_HAS_ATTACHMENT = 7;    // H
    static final int COL_PRIOR_KOGL_TYPE = 8;   // I
    static final int COL_CONTRACT_DOCS = 9;     // J
    static final int COL_PRODUCED_DATE = 10;    // K
    static final int COL_PUBLISHED_DATE = 11;   // L
    static final int COL_REVIEW_MAJOR = 12;     // M
    static final int COL_REVIEW_MINOR = 13;     // N
    static final int COL_REVIEW_RESULT = 14;    // O
    static final int COL_REVIEW_KOGL_TYPE = 15; // P
    static final int COL_REVIEW_AI_TYPE = 16;   // Q
    static final int COL_REVIEW_OPINION = 17;   // R
    static final int COL_REVIEW_NOTE = 18;      // S
    // 실파일에서 헤더는 T열에 '기존 증빙자료'라고 적혀 있지만 실제 데이터는 U열에 있다
    // (T열에는 엑셀 화면용 '수정' 링크 텍스트가 들어 있음 - 데이터 기준으로 U를 읽는다)
    static final int COL_PRIOR_EVIDENCE = 20;   // U
    /** 헤더('기존 증빙자료')가 적힌 열. 이 시스템이 만드는 다운로드 파일은 여기에 값을 쓴다. */
    static final int COL_PRIOR_EVIDENCE_HEADER = 19; // T
    static final int COL_JUDGED_KOGL_TYPE = 21; // V
    static final int COL_JUDGED_AI_TYPE = 22;   // W
    static final int COL_FINAL_OPINION = 23;    // X
    // 24 = Y, 빈 열, 건너뜀
    static final int COL_JUDGMENT_BASIS = 25;   // Z
    static final int COL_PROCESS_STATUS = 26;   // AA

    public List<ProcessRow> parse(InputStream xlsx) {
        try (Workbook wb = WorkbookFactory.create(xlsx)) {
            Sheet sheet = wb.getSheet(PRIMARY_SHEET_NAME);
            if (sheet == null && wb.getNumberOfSheets() > 0) {
                sheet = wb.getSheetAt(0);
            }
            if (sheet == null) {
                throw new SeedParseException("통합문서에 읽을 수 있는 시트가 없습니다.");
            }
            return readRows(sheet);
        } catch (IOException e) {
            throw new SeedParseException("엑셀 파일을 읽지 못했습니다.", e);
        }
    }

    private List<ProcessRow> readRows(Sheet sheet) {
        List<ProcessRow> result = new ArrayList<>();

        for (int i = DATA_START_ROW_INDEX; i <= sheet.getLastRowNum(); i++) {
            Row row = sheet.getRow(i);
            if (row == null) {
                break; // 데이터 영역은 연속적이다 - 빈 행이 나오면 그게 끝이다
            }

            String seqRaw = string(row, COL_SEQ);
            if (seqRaw.isBlank()) {
                break;
            }
            // 권리확인과 동일한 이유로 파일 내 행 순서를 병합 키로 쓴다 (순번 중복 대비)
            int seq = result.size() + 1;

            result.add(new ProcessRow(
                    seq,
                    nullIfBlank(string(row, COL_SITE_NAME)),
                    nullIfBlank(string(row, COL_CATEGORY)),
                    nullIfBlank(string(row, COL_BOARD_NAME)),
                    nullIfBlank(string(row, COL_POST_TITLE)),
                    nullIfBlank(string(row, COL_URL)),
                    nullIfBlank(string(row, COL_DESCRIPTION)),
                    nullIfBlank(string(row, COL_HAS_ATTACHMENT)),
                    nullIfBlank(string(row, COL_PRIOR_KOGL_TYPE)),
                    nullIfBlank(string(row, COL_CONTRACT_DOCS)),
                    nullIfBlank(string(row, COL_PRODUCED_DATE)),
                    nullIfBlank(string(row, COL_PUBLISHED_DATE)),
                    nullIfBlank(string(row, COL_REVIEW_MAJOR)),
                    nullIfBlank(string(row, COL_REVIEW_MINOR)),
                    nullIfBlank(string(row, COL_REVIEW_RESULT)),
                    nullIfBlank(string(row, COL_REVIEW_KOGL_TYPE)),
                    nullIfBlank(string(row, COL_REVIEW_AI_TYPE)),
                    nullIfBlank(string(row, COL_REVIEW_OPINION)),
                    nullIfBlank(string(row, COL_REVIEW_NOTE)),
                    priorEvidence(row),
                    nullIfBlank(string(row, COL_JUDGED_KOGL_TYPE)),
                    nullIfBlank(string(row, COL_JUDGED_AI_TYPE)),
                    nullIfBlank(string(row, COL_FINAL_OPINION)),
                    nullIfBlank(string(row, COL_JUDGMENT_BASIS)),
                    nullIfBlank(string(row, COL_PROCESS_STATUS))));
        }
        return result;
    }

    /**
     * 기존 증빙자료는 파일마다 값이 있는 열이 다르다.
     *
     * <p>기관이 보내오는 원본은 헤더가 T열인데 데이터가 U열에 있고(T에는 엑셀 화면용 '수정'
     * 링크 텍스트가 들어 있다), 이 시스템이 만들어 주는 다운로드 파일은 헤더 자리인 T열에
     * 값을 쓴다. 한쪽만 읽으면 "내려받아 고쳐서 다시 올리는" 흐름에서 값이 조용히 사라지므로
     * U를 먼저 보고, 비어 있으면 T를 본다.</p>
     */
    private String priorEvidence(Row row) {
        String fromDataColumn = nullIfBlank(string(row, COL_PRIOR_EVIDENCE));
        return fromDataColumn != null
                ? fromDataColumn
                : nullIfBlank(string(row, COL_PRIOR_EVIDENCE_HEADER));
    }

    private String string(Row row, int columnIndex) {
        Cell cell = row.getCell(columnIndex);
        if (cell == null) {
            return "";
        }
        return cellValue(cell, cell.getCellType());
    }

    /** ReviewParser와 동일한 셀 값 변환 규칙에, 날짜서식 숫자 셀을 yyyy-MM-dd로 정규화하는 분기를 더한다. */
    private String cellValue(Cell cell, CellType cellType) {
        return switch (cellType) {
            case STRING -> cell.getStringCellValue().trim();
            case NUMERIC -> DateUtil.isCellDateFormatted(cell)
                    ? cell.getLocalDateTimeCellValue().format(DATE_FORMAT)
                    : String.valueOf(Math.round(cell.getNumericCellValue()));
            case BOOLEAN -> String.valueOf(cell.getBooleanCellValue());
            case FORMULA -> cellValue(cell, cell.getCachedFormulaResultType());
            default -> "";
        };
    }

    private String nullIfBlank(String value) {
        return (value == null || value.isBlank()) ? null : value;
    }
}
