package com.ga.api.service.commission;

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.core.mapper.commission.ClawbackReceivableMapper;
import com.ga.core.vo.commission.ClawbackReceivableResp;
import com.ga.core.vo.commission.ClawbackReceivableSaveReq;
import com.ga.core.vo.commission.ClawbackReceivableSearchParam;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.apache.ibatis.cursor.Cursor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

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

    private final ClawbackReceivableMapper mapper;
    private final ExcelService excelService;

    public PageResponse<ClawbackReceivableResp> list(ClawbackReceivableSearchParam param) {
        param.startPage();
        return PageResponse.of(mapper.selectList(param));
    }

    public ClawbackReceivableResp detail(Long id) {
        ClawbackReceivableResp resp = mapper.selectById(id);
        if (resp == null) throw new BizException(ErrorCode.NOT_FOUND);
        return resp;
    }

    public List<Map<String, Object>> summary(ClawbackReceivableSearchParam param) {
        return mapper.summaryByAgent(param);
    }

    /**
     * 대손/직접상환 처리.
     * OUTSTANDING 또는 RECOVERING 채권만 허용, 전이 상태는 CLEARED/WRITTEN_OFF 만 가능.
     */
    @Transactional
    public void writeOff(Long id, ClawbackReceivableSaveReq req, Long userId) {
        ClawbackReceivableResp target = mapper.selectById(id);
        if (target == null) throw new BizException(ErrorCode.NOT_FOUND);

        String currentStatus = target.getStatus();
        if (!"OUTSTANDING".equals(currentStatus) && !"RECOVERING".equals(currentStatus)) {
            throw new BizException(ErrorCode.INVALID_PARAMETER, "이미 종결된 채권입니다");
        }

        String newStatus = req.getStatus();
        if (!"CLEARED".equals(newStatus) && !"WRITTEN_OFF".equals(newStatus)) {
            throw new BizException(ErrorCode.INVALID_PARAMETER, "전이 가능한 상태는 CLEARED 또는 WRITTEN_OFF 입니다");
        }

        mapper.writeOff(id, newStatus, req.getWriteoffReason(), userId);
    }

    /**
     * 엑셀 export.
     * NOTE(TODO): ClawbackReceivableMapper 에 selectCursorForExcel 이 없어 List → 인메모리 Cursor 로 대체.
     * ga-core 에 Cursor 메서드 추가 후 mapper.selectCursorForExcel(param) 으로 교체 권장.
     */
    @Transactional
    public void export(ClawbackReceivableSearchParam param, HttpServletResponse response) {
        List<ClawbackReceivableResp> rows = mapper.selectList(param);
        List<ClawbackReceivableExcelRow> excelRows = rows.stream().map(r -> {
            ClawbackReceivableExcelRow row = new ClawbackReceivableExcelRow();
            row.setAgentName(r.getAgentName());
            row.setOriginSettleMonth(r.getOriginSettleMonth());
            row.setOriginalAmount(r.getOriginalAmount());
            row.setRecoveredAmount(r.getRecoveredAmount());
            row.setBalance(r.getBalance());
            row.setStatus(r.getStatus());
            return row;
        }).toList();
        excelService.exportLargeExcel("환수채권목록", ClawbackReceivableExcelRow.class,
                () -> listCursor(excelRows), response);
    }

    /** List 를 MyBatis Cursor 인터페이스로 감싸는 인메모리 어댑터 */
    private static <T> Cursor<T> listCursor(List<T> list) {
        return new Cursor<T>() {
            private final Iterator<T> it = list.iterator();
            private int index = -1;

            @Override public boolean isOpen()    { return true; }
            @Override public boolean isConsumed() { return !it.hasNext(); }
            @Override public int getCurrentIndex() { return index; }
            @Override public void close() {}

            @Override
            public Iterator<T> iterator() {
                return new Iterator<T>() {
                    @Override public boolean hasNext() { return it.hasNext(); }
                    @Override public T next() { index++; return it.next(); }
                };
            }
        };
    }
}
