package cn.iocoder.yudao.module.system.service.biz;

import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.elderlyitemsround.*;
import cn.iocoder.yudao.module.system.controller.admin.biz.vo.BuildBedVO;
import cn.iocoder.yudao.module.system.dal.dataobject.biz.ElderlyItemsRoundDO;
import cn.iocoder.yudao.module.system.dal.dataobject.biz.ElderlyInfoDO;
import cn.iocoder.yudao.module.system.dal.mysql.biz.ElderlyItemsRoundMapper;
import cn.iocoder.yudao.module.system.dal.mysql.biz.ElderlyInfoMapper;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.converters.longconverter.LongStringConverter;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.ArrayList;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.List;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.COMMON_NOT_FOUND;

@Service
@Validated
public class ElderlyItemsRoundServiceImpl implements ElderlyItemsRoundService {

    @Resource
    private ElderlyItemsRoundMapper elderlyItemsRoundMapper;

    @Resource
    private ElderlyInfoMapper elderlyInfoMapper;

    @Resource
    private BuildService buildService;

    @Override
    public Long create(ElderlyItemsRoundSaveReqVO createReqVO) {
        ElderlyItemsRoundDO record = BeanUtils.toBean(createReqVO, ElderlyItemsRoundDO.class);
        fillDefault(record);
        elderlyItemsRoundMapper.insert(record);
        return record.getId();
    }

    @Override
    public void update(ElderlyItemsRoundSaveReqVO updateReqVO) {
        validateExists(updateReqVO.getId());
        ElderlyItemsRoundDO updateObj = BeanUtils.toBean(updateReqVO, ElderlyItemsRoundDO.class);
        fillDefault(updateObj);
        elderlyItemsRoundMapper.updateById(updateObj);
    }

    @Override
    public void delete(Long id) {
        validateExists(id);
        elderlyItemsRoundMapper.deleteById(id);
    }

    @Override
    public ElderlyItemsRoundRespVO get(ElderlyItemsRoundPageReqVO reqVO) {
        if (reqVO == null || reqVO.getElderId() == null) {
            throw invalidParamException("elderId不能为空");
        }
        ElderlyInfoDO elderlyInfo = elderlyInfoMapper.selectById(reqVO.getElderId());
        if (elderlyInfo == null) {
            throw exception(COMMON_NOT_FOUND);
        }

        ElderlyItemsRoundRespVO respVO = new ElderlyItemsRoundRespVO();
        respVO.setElderId(elderlyInfo.getId());
        respVO.setElderName(elderlyInfo.getElderName());
        if (elderlyInfo.getBedId() != null) {
            BuildBedVO bedInfo = buildService.getBedInfoById(elderlyInfo.getBedId());
            if (bedInfo != null) {
                respVO.setBuildName(bedInfo.getBuildName());
                respVO.setFloorName(bedInfo.getBuildFloorName());
                respVO.setRoomName(bedInfo.getBuildRoomName());
                respVO.setBedName(bedInfo.getBuildBedName());
            }
        }

        PageResult<ElderlyItemsRoundDO> pageResult = elderlyItemsRoundMapper.selectDetailPage(reqVO);
        respVO.setPageResult(BeanUtils.toBean(pageResult, ElderlyItemsRoundRespVO.Record.class));
        return respVO;
    }

    @Override
    public PageResult<ElderlyItemsRoundPageRespVO> getPage(ElderlyItemsRoundPageReqVO pageReqVO) {
        return elderlyItemsRoundMapper.selectPageWithElderInfo(pageReqVO);
    }

    @Override
    public Boolean batchCreate(ElderlyItemsRoundBatchSaveReqVO reqVO) {
        Long tenantId = reqVO.getTenantId() != null ? reqVO.getTenantId() : TenantContextHolder.getTenantId();

        List<ElderlyItemsRoundDO> insertList = new ArrayList<>();
        for (ElderlyItemsRoundBatchSaveReqVO.Record recordReq : reqVO.getRecords()) {
            ElderlyItemsRoundDO record = new ElderlyItemsRoundDO();
            record.setTenantId(tenantId);
            record.setRecorder(reqVO.getRecorder());
            record.setRole(reqVO.getRole());
            record.setBuildId(reqVO.getBuildId());
            record.setFloorId(reqVO.getFloorId());
            record.setRoomId(reqVO.getRoomId());
            record.setElderId(recordReq.getElderId());
            record.setItems(recordReq.getItems());
            record.setVoice(recordReq.getVoice());
            record.setRemark(recordReq.getRemark());
            record.setRoundTime(reqVO.getRoundTime());
            record.setStatus(recordReq.getStatus());
            insertList.add(record);
        }
        return elderlyItemsRoundMapper.insertBatch(insertList);
    }

    @Override
    public void exportExcel(String yearMonth, HttpServletResponse response) throws IOException {
        YearMonth ym = parseYearMonth(yearMonth);
        Long tenantId = TenantContextHolder.getTenantId();

        List<ElderlyInfoDO> elderlyList = elderlyInfoMapper.selectExportListWithBedInfo(tenantId);

        int daysInMonth = ym.lengthOfMonth();
        int colCount = 2 + daysInMonth;

        Map<Long, Map<Integer, String>> elderDayStatusMap = new HashMap<>();
        LocalDateTime start = ym.atDay(1).atStartOfDay();
        LocalDateTime endExclusive = ym.plusMonths(1).atDay(1).atStartOfDay();
        List<Map<String, Object>> dayStatusList = elderlyItemsRoundMapper.selectMonthDayMinStatus(tenantId, start, endExclusive);
        for (Map<String, Object> row : dayStatusList) {
            if (row == null) {
                continue;
            }
            Object elderIdValue = row.get("elderId");
            Object dayValue = row.get("day");
            Object minStatusValue = row.get("minStatus");
            if (!(elderIdValue instanceof Number) || !(dayValue instanceof Number) || !(minStatusValue instanceof Boolean)) {
                continue;
            }
            Long elderId = ((Number) elderIdValue).longValue();
            Integer day = ((Number) dayValue).intValue();
            Boolean minStatus = ((Boolean) minStatusValue);

            Map<Integer, String> dayMap = elderDayStatusMap.computeIfAbsent(elderId, k -> new HashMap<>());
            dayMap.put(day, !minStatus ? "△" : "√");
        }

        Map<String, List<ElderlyInfoDO>> buildGroups = new LinkedHashMap<>();
        for (ElderlyInfoDO elderly : elderlyList) {
            String buildName = StrUtil.blankToDefault(elderly.getBuildName(), "未分配楼栋");
            buildGroups.computeIfAbsent(buildName, k -> new ArrayList<>()).add(elderly);
        }

        String filename = "长者巡房项目-" + ym + ".xlsx";
        ExcelUtils.setResponseProperties(response, filename);

        Map<String, List<List<String>>> sheetRows = new LinkedHashMap<>();
        Map<String, List<Integer>> sheetMergeRows = new LinkedHashMap<>();

        if (buildGroups.isEmpty()) {
            String sheetName = uniqueSheetName("无数据", sheetRows);
            List<List<String>> rows = new ArrayList<>();
            rows.add(padRow("长者巡房项目（" + ym + "）", colCount));
            rows.add(padRow("无符合条件的长者数据", colCount));
            sheetRows.put(sheetName, rows);
            sheetMergeRows.put(sheetName, new ArrayList<>(Collections.singletonList(0)));
        } else {
            List<Map.Entry<String, List<ElderlyInfoDO>>> buildEntries = new ArrayList<>(buildGroups.entrySet());
            buildEntries.sort(Map.Entry.comparingByKey(Comparator.nullsLast(String::compareTo)));
            for (Map.Entry<String, List<ElderlyInfoDO>> buildEntry : buildEntries) {
                String rawSheetName = sanitizeSheetName(buildEntry.getKey());
                String sheetName = uniqueSheetName(rawSheetName, sheetRows);

                List<ElderlyInfoDO> buildElders = buildEntry.getValue();
                buildElders.sort(Comparator
                        .comparing((ElderlyInfoDO e) -> StrUtil.blankToDefault(e.getFloorName(), ""))
                        .thenComparing(e -> StrUtil.blankToDefault(e.getRoomName(), ""))
                        .thenComparing(e -> StrUtil.blankToDefault(e.getBedName(), ""))
                        .thenComparing(e -> StrUtil.blankToDefault(e.getElderName(), "")));

                List<List<String>> rows = new ArrayList<>();
                List<Integer> mergeRows = new ArrayList<>();

                rows.add(padRow(buildEntry.getKey() + " 长者巡房项目（" + ym + "）", colCount));
                mergeRows.add(0);
                rows.add(blankRow(colCount));

                int rowIndex = 2;
                Map<String, List<ElderlyInfoDO>> floorGroups = new LinkedHashMap<>();
                for (ElderlyInfoDO elderly : buildElders) {
                    String floorName = StrUtil.blankToDefault(elderly.getFloorName(), "未分配楼层");
                    floorGroups.computeIfAbsent(floorName, k -> new ArrayList<>()).add(elderly);
                }

                List<Map.Entry<String, List<ElderlyInfoDO>>> floorEntries = new ArrayList<>(floorGroups.entrySet());
                floorEntries.sort(Map.Entry.comparingByKey(Comparator.nullsLast(String::compareTo)));
                for (Map.Entry<String, List<ElderlyInfoDO>> floorEntry : floorEntries) {
                    rows.add(padRow(floorEntry.getKey(), colCount));
                    mergeRows.add(rowIndex);
                    rowIndex++;

                    rows.add(headerRow(daysInMonth));
                    rowIndex++;

                    for (ElderlyInfoDO elderly : floorEntry.getValue()) {
                        rows.add(dataRow(elderly, elderDayStatusMap.get(elderly.getId()), daysInMonth));
                        rowIndex++;
                    }

                    rows.add(blankRow(colCount));
                    rowIndex++;
                }

                sheetRows.put(sheetName, rows);
                sheetMergeRows.put(sheetName, mergeRows);
            }
        }

        ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
                .autoCloseStream(false)
                .registerConverter(new LongStringConverter())
                .registerWriteHandler(new SheetLayoutWriteHandler(sheetMergeRows, colCount - 1, 10, 32, 4))
                .build();
        try {
            int sheetNo = 0;
            for (Map.Entry<String, List<List<String>>> entry : sheetRows.entrySet()) {
                WriteSheet sheet = EasyExcel.writerSheet(sheetNo++, entry.getKey()).build();
                excelWriter.write(entry.getValue(), sheet);
            }
        } finally {
            excelWriter.finish();
        }
    }

    private void fillDefault(ElderlyItemsRoundDO record) {
        if (record.getTenantId() == null) {
            record.setTenantId(TenantContextHolder.getTenantId());
        }
        if (StrUtil.isBlank(record.getRecorder())) {
            record.setRecorder(trimRecorder(SecurityFrameworkUtils.getLoginUserNickname()));
        } else {
            record.setRecorder(trimRecorder(record.getRecorder()));
        }
    }

    private String trimRecorder(String recorder) {
        if (recorder == null) {
            return null;
        }
        String value = recorder.trim();
        if (value.length() <= 20) {
            return value;
        }
        return value.substring(0, 20);
    }

    private void validateExists(Long id) {
        if (id == null || elderlyItemsRoundMapper.selectById(id) == null) {
            throw exception(COMMON_NOT_FOUND);
        }
    }

    private static YearMonth parseYearMonth(String value) {
        if (StrUtil.isBlank(value)) {
            throw invalidParamException("yearMonth不能为空");
        }
        String v = value.trim();
        Pattern p1 = Pattern.compile("^(\\d{4})-(0[1-9]|1[0-2])$");
        Matcher m1 = p1.matcher(v);
        if (m1.matches()) {
            return YearMonth.of(Integer.parseInt(m1.group(1)), Integer.parseInt(m1.group(2)));
        }
        Pattern p2 = Pattern.compile("^(\\d{4})(0[1-9]|1[0-2])$");
        Matcher m2 = p2.matcher(v);
        if (m2.matches()) {
            return YearMonth.of(Integer.parseInt(m2.group(1)), Integer.parseInt(m2.group(2)));
        }
        throw invalidParamException("yearMonth格式错误，示例：2026-05 或 202605");
    }

    private static String sanitizeSheetName(String name) {
        String v = StrUtil.blankToDefault(name, "Sheet");
        v = v.replaceAll("[\\\\/?*\\[\\]:]", " ");
        v = v.trim();
        if (v.length() > 31) {
            v = v.substring(0, 31);
        }
        return StrUtil.blankToDefault(v, "Sheet");
    }

    private static String uniqueSheetName(String baseName, Map<String, ?> existing) {
        String base = sanitizeSheetName(baseName);
        if (!existing.containsKey(base)) {
            return base;
        }
        for (int i = 2; i < 1000; i++) {
            String suffix = "(" + i + ")";
            int maxLen = 31 - suffix.length();
            String candidateBase = base.length() > maxLen ? base.substring(0, maxLen) : base;
            String candidate = candidateBase + suffix;
            if (!existing.containsKey(candidate)) {
                return candidate;
            }
        }
        return String.valueOf(System.currentTimeMillis());
    }

    private static List<String> padRow(String firstCell, int colCount) {
        List<String> row = new ArrayList<>(colCount);
        row.add(firstCell);
        for (int i = 1; i < colCount; i++) {
            row.add("");
        }
        return row;
    }

    private static List<String> blankRow(int colCount) {
        List<String> row = new ArrayList<>(colCount);
        for (int i = 0; i < colCount; i++) {
            row.add("");
        }
        return row;
    }

    private static List<String> headerRow(int daysInMonth) {
        int colCount = 2 + daysInMonth;
        List<String> row = new ArrayList<>(colCount);
        row.add("房号");
        row.add("日期\\姓名");
        for (int d = 1; d <= daysInMonth; d++) {
            row.add(String.valueOf(d));
        }
        return row;
    }

    private static List<String> dataRow(ElderlyInfoDO elderly, Map<Integer, String> dayStatusMap, int daysInMonth) {
        int colCount = 2 + daysInMonth;
        List<String> row = new ArrayList<>(colCount);
        row.add(StrUtil.blankToDefault(elderly.getRoomName(), ""));
        String bed = StrUtil.blankToDefault(elderly.getBedName(), "");
        String elderName = StrUtil.blankToDefault(elderly.getElderName(), "");
        if (StrUtil.isBlank(bed)) {
            row.add(elderName);
        } else if (StrUtil.isBlank(elderName)) {
            row.add(bed);
        } else {
            row.add(bed + "/" + elderName);
        }
        for (int d = 1; d <= daysInMonth; d++) {
            String v = dayStatusMap != null ? dayStatusMap.get(d) : null;
            row.add(StrUtil.blankToDefault(v, ""));
        }
        return row;
    }

    private static class SheetLayoutWriteHandler implements SheetWriteHandler {
        private final Map<String, List<Integer>> sheetMergeRows;
        private final int lastColIndex;
        private final int roomColWidthChars;
        private final int nameColWidthChars;
        private final int dayColWidthChars;

        private SheetLayoutWriteHandler(Map<String, List<Integer>> sheetMergeRows, int lastColIndex,
                                        int roomColWidthChars, int nameColWidthChars, int dayColWidthChars) {
            this.sheetMergeRows = sheetMergeRows;
            this.lastColIndex = lastColIndex;
            this.roomColWidthChars = roomColWidthChars;
            this.nameColWidthChars = nameColWidthChars;
            this.dayColWidthChars = dayColWidthChars;
        }

        @Override
        public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        }

        @Override
        public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
            Sheet sheet = writeSheetHolder.getSheet();
            for (int col = 0; col <= lastColIndex; col++) {
                if (col == 0) {
                    sheet.setColumnWidth(col, roomColWidthChars * 256);
                } else if (col == 1) {
                    sheet.setColumnWidth(col, nameColWidthChars * 256);
                } else {
                    sheet.setColumnWidth(col, dayColWidthChars * 256);
                }
            }
            List<Integer> rows = sheetMergeRows.get(writeSheetHolder.getSheetName());
            if (rows == null) {
                return;
            }
            for (Integer row : rows) {
                if (row == null) {
                    continue;
                }
                sheet.addMergedRegion(new CellRangeAddress(row, row, 0, lastColIndex));
            }
        }
    }
}
