package com.ga.common.excel;

import com.ga.common.annotation.ExcelColumn;
import com.ga.common.code.CommonCodeService;
import lombok.Getter;
import lombok.Setter;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * 엑셀 업로드(보험사 실적 수신 ① 단계) 라운드트립 테스트.
 * 실제 .xlsx 바이트를 생성 → ExcelService.importLargeExcel 으로 파싱 →
 * VO 매핑/타입변환/필수검증/배치 consumer 호출/에러행 수집을 검증한다.
 *
 * 시나리오 ① "보험이 들어오는" 입구: 보험사 실적 엑셀이 시스템으로 인입되는 경로.
 */
@ExtendWith(MockitoExtension.class)
class ExcelServiceImportTest {

    @Mock CommonCodeService codeService;  // import 경로에서는 미사용 (export 전용)

    /** 보험사 실적 1행(raw)을 모사하는 업로드 VO. */
    @Getter
    @Setter
    public static class CarrierResultRow {
        @ExcelColumn(header = "증권번호", order = 1, required = true)
        private String policyNo;
        @ExcelColumn(header = "설계사코드", order = 2)
        private String agentCode;
        @ExcelColumn(header = "보험료", order = 3)
        private BigDecimal premium;
        @ExcelColumn(header = "계약일", order = 4)
        private LocalDate contractDate;
        @ExcelColumn(header = "회차", order = 5)
        private Integer commissionYear;
    }

    @Test
    @DisplayName("정상 행은 VO로 정확히 파싱되고, 필수(증권번호) 누락 행은 에러로 수집된다")
    void 보험사실적_엑셀업로드_정상행파싱_및_필수누락행_에러수집() throws Exception {
        // given: 헤더 + 정상 2행 + 증권번호 누락 1행
        byte[] xlsx = buildXlsx(sheet -> {
            header(sheet);
            dataRow(sheet, 1, "POL-1001", "AG-10", 1000000, "2024-01-15", 1);
            dataRow(sheet, 2, "POL-1002", "AG-20", 250000.50, "2024-02-20", 2);
            dataRow(sheet, 3, null, "AG-30", 500000, "2024-03-01", 1); // 증권번호 누락 → 에러
        });
        MultipartFile file = multipart(xlsx);
        ExcelService excelService = new ExcelService(codeService);

        // when
        List<CarrierResultRow> saved = new ArrayList<>();
        ExcelImportResult result = excelService.importLargeExcel(
                file, CarrierResultRow.class, 100, saved::addAll);

        // then: 합계/성공/에러 카운트
        assertThat(result.getTotalCount()).isEqualTo(3);
        assertThat(result.getSuccessCount()).isEqualTo(2);
        assertThat(result.getErrorCount()).isEqualTo(1);
        assertThat(result.getErrors()).hasSize(1);
        assertThat(result.getErrors().get(0).getRowNumber()).isEqualTo(4); // 엑셀 4번째 행(1-base, 헤더 포함)
        assertThat(result.getErrors().get(0).getErrorMessage()).contains("증권번호");

        // then: 파싱 값/타입 변환 정확성
        assertThat(saved).hasSize(2);
        CarrierResultRow r1 = saved.get(0);
        assertThat(r1.getPolicyNo()).isEqualTo("POL-1001");
        assertThat(r1.getAgentCode()).isEqualTo("AG-10");
        assertThat(r1.getPremium()).isEqualByComparingTo("1000000");      // numeric → BigDecimal
        assertThat(r1.getContractDate()).isEqualTo(LocalDate.of(2024, 1, 15)); // string → LocalDate
        assertThat(r1.getCommissionYear()).isEqualTo(1);                   // numeric → Integer
        assertThat(saved.get(1).getPremium()).isEqualByComparingTo("250000.50");
    }

    @Test
    @DisplayName("배치 크기 단위로 consumer가 나누어 호출되고 전 행이 누적 저장된다")
    void 배치사이즈_단위로_consumer_누적호출() throws Exception {
        byte[] xlsx = buildXlsx(sheet -> {
            header(sheet);
            for (int i = 1; i <= 5; i++) {
                dataRow(sheet, i, "POL-" + i, "AG-" + i, 100000 * i, "2024-01-15", 1);
            }
        });
        ExcelService excelService = new ExcelService(codeService);

        List<Integer> batchSizes = new ArrayList<>();
        List<CarrierResultRow> saved = new ArrayList<>();
        ExcelImportResult result = excelService.importLargeExcel(
                multipart(xlsx), CarrierResultRow.class, 2, batch -> {
                    batchSizes.add(batch.size());
                    saved.addAll(batch);
                });

        // 5행 / 배치2 → [2, 2, 1] 로 consumer 3회 호출
        assertThat(batchSizes).containsExactly(2, 2, 1);
        assertThat(result.getSuccessCount()).isEqualTo(5);
        assertThat(result.getErrorCount()).isZero();
        assertThat(saved).hasSize(5);
    }

    // ── .xlsx 빌더 헬퍼 ──

    private interface SheetWriter { void write(Sheet sheet); }

    private static byte[] buildXlsx(SheetWriter writer) throws Exception {
        try (XSSFWorkbook wb = new XSSFWorkbook();
             ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            Sheet sheet = wb.createSheet("Sheet1");
            writer.write(sheet);
            wb.write(out);
            return out.toByteArray();
        }
    }

    private static void header(Sheet sheet) {
        Row h = sheet.createRow(0);
        String[] cols = {"증권번호", "설계사코드", "보험료", "계약일", "회차"};
        for (int i = 0; i < cols.length; i++) h.createCell(i).setCellValue(cols[i]);
    }

    private static void dataRow(Sheet sheet, int rowIdx, String policyNo, String agentCode,
                                double premium, String contractDate, int year) {
        Row r = sheet.createRow(rowIdx);
        if (policyNo != null) r.createCell(0).setCellValue(policyNo); // null → 셀 미생성(필수 누락)
        r.createCell(1).setCellValue(agentCode);
        r.createCell(2).setCellValue(premium);
        r.createCell(3).setCellValue(contractDate);
        r.createCell(4).setCellValue(year);
    }

    private static MultipartFile multipart(byte[] xlsx) {
        return new MockMultipartFile(
                "file", "carrier.xlsx",
                "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", xlsx);
    }
}
