package com.ga.api.service.settle;

import com.ga.common.annotation.ExcelColumn;
import com.ga.common.excel.ExcelService;
import com.ga.common.exception.BizException;
import com.ga.common.exception.ErrorCode;
import com.ga.common.model.PageResponse;
import com.ga.common.util.SecurityUtil;
import com.ga.core.mapper.settle.PaymentBatchItemMapper;
import com.ga.core.mapper.settle.PaymentBatchMapper;
import com.ga.core.vo.settle.PaymentBatchItemResp;
import com.ga.core.vo.settle.PaymentBatchItemUpdateReq;
import com.ga.core.vo.settle.PaymentBatchItemVO;
import com.ga.core.vo.settle.PaymentBatchResp;
import com.ga.core.vo.settle.PaymentBatchSaveReq;
import com.ga.core.vo.settle.PaymentBatchSearchParam;
import com.ga.core.vo.settle.PaymentBatchStatus;
import com.ga.core.vo.settle.PaymentBatchVO;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.apache.ibatis.cursor.Cursor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Iterator;
import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class PaymentBatchService {

    private final PaymentBatchMapper batchMapper;
    private final PaymentBatchItemMapper itemMapper;
    private final ExcelService excelService;

    /**
     * 배치 생성.
     * DRAFT 상태로 INSERT → 정산월 지급대상 items 일괄 INSERT → totals 재집계.
     * @return 생성된 batchId
     */
    @Transactional
    public Long create(PaymentBatchSaveReq req) {
        PaymentBatchVO vo = new PaymentBatchVO();
        vo.setSettleMonth(req.getSettleMonth());
        vo.setBatchName(req.getBatchName());
        vo.setStatus(PaymentBatchStatus.DRAFT.name());
        vo.setCreatedBy(SecurityUtil.getCurrentUserId());
        batchMapper.insert(vo);  // batchId 채번

        Long batchId = vo.getBatchId();
        itemMapper.insertFromSettleMaster(batchId, req.getSettleMonth());
        batchMapper.recalcTotals(batchId);
        return batchId;
    }

    public PageResponse<PaymentBatchResp> list(PaymentBatchSearchParam param) {
        param.startPage();
        return PageResponse.of(batchMapper.selectList(param));
    }

    public PaymentBatchResp detail(Long batchId) {
        PaymentBatchResp resp = batchMapper.selectById(batchId);
        if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
        return resp;
    }

    /**
     * 지급대상건 포함/제외 수정.
     * DRAFT 배치만 허용. item이 해당 배치 소속인지 검증 후 갱신 → totals 재집계.
     */
    @Transactional
    public void updateItem(Long batchId, Long itemId, PaymentBatchItemUpdateReq req) {
        PaymentBatchResp batch = batchMapper.selectById(batchId);
        if (batch == null) throw new BizException(ErrorCode.NOT_FOUND);
        if (!PaymentBatchStatus.DRAFT.name().equals(batch.getStatus())) {
            throw new BizException(ErrorCode.INVALID_STATUS, "확정/취소된 배치는 수정 불가");
        }

        PaymentBatchItemVO item = itemMapper.selectById(itemId);
        if (item == null) throw new BizException(ErrorCode.NOT_FOUND);
        if (!batchId.equals(item.getBatchId())) {
            throw new BizException(ErrorCode.INVALID_PARAMETER, "해당 배치 소속 항목이 아닙니다");
        }

        itemMapper.updateInclude(itemId, req.getIncluded(), req.getExcludeReason());
        batchMapper.recalcTotals(batchId);
    }

    /**
     * 배치 확정.
     * DRAFT → FINALIZED. 포함건 0이면 거부.
     */
    @Transactional
    public void finalize(Long batchId) {
        PaymentBatchResp batch = batchMapper.selectById(batchId);
        if (batch == null) throw new BizException(ErrorCode.NOT_FOUND);
        if (!PaymentBatchStatus.DRAFT.name().equals(batch.getStatus())) {
            throw new BizException(ErrorCode.INVALID_STATUS, "확정/취소된 배치는 수정 불가");
        }

        batchMapper.recalcTotals(batchId);
        // totals 재조회 후 포함건 수 확인
        PaymentBatchResp refreshed = batchMapper.selectById(batchId);
        if (refreshed.getTotalCount() == null || refreshed.getTotalCount() == 0) {
            throw new BizException(ErrorCode.INVALID_STATUS, "포함건 없음");
        }

        batchMapper.updateStatus(batchId, PaymentBatchStatus.FINALIZED.name(), SecurityUtil.getCurrentUserId());
    }

    /**
     * 배치 취소.
     * 이미 CANCELLED면 거부.
     */
    @Transactional
    public void cancel(Long batchId) {
        PaymentBatchResp batch = batchMapper.selectById(batchId);
        if (batch == null) throw new BizException(ErrorCode.NOT_FOUND);
        if (PaymentBatchStatus.CANCELLED.name().equals(batch.getStatus())) {
            throw new BizException(ErrorCode.INVALID_STATUS, "이미 취소된 배치입니다");
        }

        batchMapper.updateStatus(batchId, PaymentBatchStatus.CANCELLED.name(), SecurityUtil.getCurrentUserId());
    }

    /**
     * 배치 items 엑셀 다운로드.
     */
    @Transactional(readOnly = true)
    public void export(Long batchId, HttpServletResponse response) {
        PaymentBatchResp batch = batchMapper.selectById(batchId);
        if (batch == null) throw new BizException(ErrorCode.NOT_FOUND);

        List<PaymentBatchItemResp> items = itemMapper.selectByBatchId(batchId);
        List<ItemExcelRow> rows = items.stream().map(ItemExcelRow::from).toList();

        excelService.exportLargeExcel(
                "지급대상건_" + batch.getSettleMonth(),
                ItemExcelRow.class,
                () -> new ListCursor<>(rows),
                response
        );
    }

    // ── 엑셀 출력용 내부 VO ───────────────────────────────────────────────

    @Data
    public static class ItemExcelRow {

        @ExcelColumn(header = "항목ID", order = 1, width = 10)
        private Long itemId;

        @ExcelColumn(header = "설계사명", order = 2, width = 14)
        private String agentName;

        @ExcelColumn(header = "설계사ID", order = 3, width = 10)
        private Long agentId;

        @ExcelColumn(header = "지급가능금액", order = 4, width = 16)
        private BigDecimal payableAmount;

        @ExcelColumn(header = "포함여부", order = 5, width = 10)
        private Boolean included;

        @ExcelColumn(header = "제외사유", order = 6, width = 30)
        private String excludeReason;

        static ItemExcelRow from(PaymentBatchItemResp r) {
            ItemExcelRow row = new ItemExcelRow();
            row.setItemId(r.getItemId());
            row.setAgentName(r.getAgentName());
            row.setAgentId(r.getAgentId());
            row.setPayableAmount(r.getPayableAmount());
            row.setIncluded(r.getIncluded());
            row.setExcludeReason(r.getExcludeReason());
            return row;
        }
    }

    /** List를 MyBatis Cursor 인터페이스로 감싸는 어댑터. */
    private static class ListCursor<T> implements Cursor<T> {
        private final List<T> list;
        ListCursor(List<T> list) { this.list = list; }

        @Override public boolean isOpen()       { return true;  }
        @Override public boolean isConsumed()   { return false; }
        @Override public int getCurrentIndex()  { return -1;    }
        @Override public Iterator<T> iterator() { return list.iterator(); }
        @Override public void close()           { /* no-op */  }
    }
}
