package kr.itn.itnhub.process;

import kr.itn.itnhub.code.Code;
import kr.itn.itnhub.code.CodeService;
import kr.itn.itnhub.code.Codes;
import kr.itn.itnhub.org.OrgNotFoundException;
import kr.itn.itnhub.org.OrganizationMapper;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.List;
import java.util.Set;

/** 기관관리 상세의 권리처리 탭이 쓰는 목록/상세/수정/삭제/다운로드 서비스. */
@Service
public class ProcessService {

    /**
     * 1행 그룹 제목(A=게시물 정보(조사원), M=권리확인(변호사), U=권리처리(변호사)) + 2행 컬럼 헤더.
     * U열(수정 링크)과 Y열은 원본 시트의 건너뛰는 열이라 빈 문자열로 남겨둔다.
     */
    static final String[] COLUMN_HEADERS = {
            "순번", "사이트명", "범주", "게시판명", "게시물제목", "URL주소", "설명",
            "첨부파일여부", "기존 공공누리유형(+AI)", "계약서 유무", "제작일", "공표일",
            "권리확인(대분류)", "권리확인(세부)", "처리 구분", "공공누리 유형", "AI유형", "의견", "비고", "기존 증빙자료",
            "", "공공누리유형(판정)", "AI유형(판정)", "최종의견", "", "판단근거", "처리상태"
    };

    private final ProcessItemMapper mapper;
    private final OrganizationMapper orgMapper;
    private final CodeService codeService;

    public ProcessService(ProcessItemMapper mapper, OrganizationMapper orgMapper,
                          CodeService codeService) {
        this.mapper = mapper;
        this.orgMapper = orgMapper;
        this.codeService = codeService;
    }

    /**
     * 권리처리 완료로 셀 상태값. 코드표에서 done 속성이 붙은 코드를 찾는다.
     *
     * <p>지정이 없으면 어떤 행도 완료로 세지 않도록 매칭되지 않는 값을 준다 - 빈 문자열을
     * 주면 처리상태가 비어 있는 행이 전부 완료로 잡힌다.</p>
     */
    private String doneStatus() {
        return codeService.group(Codes.PROCESS_STATUS).stream()
                .filter(code -> code.flag(Codes.ATTR_DONE))
                .map(Code::code)
                .findFirst()
                .orElse(" ");
    }

    public ProcessPage list(Long orgId, String keyword, String site, String board, String title,
                            String status, int page, int size) {
        requireOrg(orgId);
        String kw = normalizeKeyword(keyword);
        String siteFilter = normalizeKeyword(site);
        String boardFilter = normalizeKeyword(board);
        String titleFilter = normalizeKeyword(title);
        String st = normalizeStatus(status);
        int safePage = Math.max(page, 0);
        int safeSize = size <= 0 ? 30 : size;
        int offset = safePage * safeSize;

        String done = doneStatus();
        List<ProcessItem> items = mapper.findPage(orgId, kw, siteFilter, boardFilter, titleFilter,
                st, done, offset, safeSize);
        // total/done은 검색어·상태 필터와 무관한 기관 전체 진행률이다(헤더/통계 카드용 - ProcessPage 참고).
        ProcessStats stats = mapper.stats(orgId, done);
        return new ProcessPage(items, stats.total(), stats.done(), safePage, safeSize);
    }

    /** 목록에서 고른 여러 건에 같은 값을 한 번에 반영한다. 화면에서 비워 둔 항목은 유지된다. */
    @Transactional
    public int updateBulk(Long orgId, BulkProcessingRequest request) {
        requireOrg(orgId);
        if (request.ids() == null || request.ids().isEmpty()) {
            return 0;
        }
        return mapper.updateProcessingBulk(orgId, request.ids(),
                blankToNull(request.processStatus()),
                blankToNull(request.judgedKoglType()),
                blankToNull(request.finalOpinion()),
                blankToNull(request.reviewNote()),
                doneStatus());
    }

    /** 목록에서 고른 여러 건을 지운다. */
    @Transactional
    public int deleteBulk(Long orgId, List<Long> ids) {
        requireOrg(orgId);
        if (ids == null || ids.isEmpty()) {
            return 0;
        }
        return mapper.deleteByIds(orgId, ids);
    }

    private String blankToNull(String value) {
        return value == null || value.isBlank() ? null : value;
    }

    public ProcessItem get(Long orgId, Long itemId) {
        requireOrg(orgId);
        return requireItem(orgId, itemId);
    }

    @Transactional
    public ProcessItem updateProcessing(Long orgId, Long itemId, ProcessingRequest request) {
        requireOrg(orgId);
        validateProcessStatus(request.processStatus());
        int updated = mapper.updateProcessing(orgId, itemId,
                request.contractDocs(), request.judgedKoglType(), request.judgedAiType(),
                request.finalOpinion(), request.judgmentBasis(), request.processStatus(),
                doneStatus());
        if (updated == 0) {
            throw new ProcessNotFoundException("게시물을 찾을 수 없습니다: " + itemId);
        }
        return mapper.findById(orgId, itemId);
    }

    @Transactional
    public void delete(Long orgId, Long itemId) {
        requireOrg(orgId);
        int deleted = mapper.deleteById(orgId, itemId);
        if (deleted == 0) {
            throw new ProcessNotFoundException("게시물을 찾을 수 없습니다: " + itemId);
        }
    }

    /** 원본 파일과 같은 헤더 배치(1행 그룹제목, 2행 컬럼헤더, 3행부터 데이터)로 xlsx를 만든다. */
    public byte[] downloadWorkbook(Long orgId) {
        requireOrg(orgId);
        List<ProcessItem> items = mapper.findAllByOrg(orgId);

        try (XSSFWorkbook wb = new XSSFWorkbook(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            Sheet sheet = wb.createSheet(ProcessParser.PRIMARY_SHEET_NAME);

            Row groupRow = sheet.createRow(0);
            groupRow.createCell(0).setCellValue("게시물 정보(조사원)");
            groupRow.createCell(12).setCellValue("권리확인(변호사)");
            groupRow.createCell(20).setCellValue("권리처리(변호사)");

            Row headerRow = sheet.createRow(1);
            for (int i = 0; i < COLUMN_HEADERS.length; i++) {
                headerRow.createCell(i).setCellValue(COLUMN_HEADERS[i]);
            }

            int rowNum = 2;
            for (ProcessItem item : items) {
                Row row = sheet.createRow(rowNum++);
                setCell(row, 0, String.valueOf(item.getSeq()));
                setCell(row, 1, item.getSiteName());
                setCell(row, 2, item.getCategory());
                setCell(row, 3, item.getBoardName());
                setCell(row, 4, item.getPostTitle());
                setCell(row, 5, item.getUrl());
                setCell(row, 6, item.getDescription());
                setCell(row, 7, item.getHasAttachment());
                setCell(row, 8, item.getPriorKoglType());
                setCell(row, 9, item.getContractDocs());
                setCell(row, 10, item.getProducedDate());
                setCell(row, 11, item.getPublishedDate());
                setCell(row, 12, item.getReviewMajor());
                setCell(row, 13, item.getReviewMinor());
                setCell(row, 14, item.getReviewResult());
                setCell(row, 15, item.getReviewKoglType());
                setCell(row, 16, item.getReviewAiType());
                setCell(row, 17, item.getReviewOpinion());
                setCell(row, 18, item.getReviewNote());
                setCell(row, 19, item.getPriorEvidence());
                // 20 = U, 수정 링크 열, 건너뜀
                setCell(row, 21, item.getJudgedKoglType());
                setCell(row, 22, item.getJudgedAiType());
                setCell(row, 23, item.getFinalOpinion());
                // 24 = Y, 빈 열
                setCell(row, 25, item.getJudgmentBasis());
                setCell(row, 26, item.getProcessStatus());
            }

            wb.write(out);
            return out.toByteArray();
        } catch (IOException e) {
            throw new UncheckedIOException("권리처리 엑셀을 만들지 못했습니다.", e);
        }
    }

    private void setCell(Row row, int columnIndex, String value) {
        if (value == null) {
            return;
        }
        Cell cell = row.createCell(columnIndex);
        cell.setCellValue(value);
    }

    /** 화이트리스트는 코드표가 정한다. 코드를 늘리면 여기 손대지 않아도 통과한다. */
    private void validateProcessStatus(String processStatus) {
        if (processStatus == null || processStatus.isBlank()) {
            return;
        }
        if (!codeService.isValid(Codes.PROCESS_STATUS, processStatus)) {
            String allowed = codeService.group(Codes.PROCESS_STATUS).stream()
                    .map(Code::label)
                    .collect(java.util.stream.Collectors.joining(" 또는 "));
            throw new InvalidProcessStatusException("처리상태는 " + allowed + "여야 합니다.");
        }
    }

    private String normalizeKeyword(String keyword) {
        return (keyword == null || keyword.isBlank()) ? null : keyword.trim();
    }

    private String normalizeStatus(String status) {
        return (status == null || status.isBlank()) ? null : status.trim();
    }

    private void requireOrg(Long orgId) {
        if (orgMapper.findById(orgId) == null) {
            throw new OrgNotFoundException("기관을 찾을 수 없습니다: " + orgId);
        }
    }

    private ProcessItem requireItem(Long orgId, Long itemId) {
        ProcessItem item = mapper.findById(orgId, itemId);
        if (item == null) {
            throw new ProcessNotFoundException("게시물을 찾을 수 없습니다: " + itemId);
        }
        return item;
    }
}
