package com.njcn.gather.aireport.clientunit.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.aireport.clientunit.component.ClientUnitUserNameResolver;
import com.njcn.gather.aireport.clientunit.mapper.ClientUnitMapper;
import com.njcn.gather.aireport.clientunit.pojo.constant.ClientUnitConst;
import com.njcn.gather.aireport.clientunit.pojo.param.ClientUnitParam;
import com.njcn.gather.aireport.clientunit.pojo.po.ClientUnitPO;
import com.njcn.gather.aireport.clientunit.pojo.vo.ClientUnitExcelVO;
import com.njcn.gather.aireport.clientunit.pojo.vo.ClientUnitImportResultVO;
import com.njcn.gather.aireport.clientunit.pojo.vo.ClientUnitVO;
import com.njcn.gather.aireport.clientunit.service.IClientUnitService;
import com.njcn.gather.system.util.ExportFileNameUtil;
import com.njcn.web.factory.PageFactory;
import com.njcn.web.utils.ExcelUtil;
import com.njcn.web.utils.HttpServletUtil;
import com.njcn.web.utils.RequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * 委托单位业务实现。
 */
@Slf4j
@Service
public class ClientUnitServiceImpl extends ServiceImpl<ClientUnitMapper, ClientUnitPO> implements IClientUnitService {

    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final String[] TEMPLATE_HEADERS = new String[]{"单位名称", "联系人", "联系方式", "地址"};

    private final ClientUnitUserNameResolver clientUnitUserNameResolver;

    public ClientUnitServiceImpl(ClientUnitUserNameResolver clientUnitUserNameResolver) {
        this.clientUnitUserNameResolver = clientUnitUserNameResolver;
    }

    @Override
    public Page<ClientUnitVO> listClientUnits(ClientUnitParam.QueryParam param) {
        ClientUnitParam.QueryParam query = param == null ? new ClientUnitParam.QueryParam() : param;
        Page<ClientUnitPO> page = this.page(
                new Page<ClientUnitPO>(PageFactory.getPageNum(query), PageFactory.getPageSize(query)),
                buildQueryWrapper(query));
        Page<ClientUnitVO> result = new Page<ClientUnitVO>(page.getCurrent(), page.getSize(), page.getTotal());
        Map<String, String> userNameMap = resolveUserNameMap(page.getRecords());
        result.setRecords(page.getRecords().stream()
                .map(clientUnit -> toVO(clientUnit, userNameMap))
                .collect(Collectors.toList()));
        return result;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean addClientUnit(ClientUnitParam.AddParam param) {
        NormalizedClientUnit data = normalizeParam(param);
        LocalDateTime now = LocalDateTime.now();
        String userId = resolveCurrentUserId();
        ClientUnitPO clientUnit = new ClientUnitPO();
        clientUnit.setId(UUID.randomUUID().toString());
        clientUnit.setName(data.getName());
        clientUnit.setContactName(data.getContactName());
        clientUnit.setPhonenumber(data.getPhonenumber());
        clientUnit.setAddress(data.getAddress());
        clientUnit.setStatus(ClientUnitConst.STATUS_NORMAL);
        clientUnit.setCreateBy(userId);
        clientUnit.setCreateTime(now);
        clientUnit.setUpdateBy(userId);
        clientUnit.setUpdateTime(now);
        return this.save(clientUnit);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean updateClientUnit(ClientUnitParam.UpdateParam param) {
        if (param == null || StrUtil.isBlank(param.getId())) {
            throw new BusinessException(CommonResponseEnum.FAIL, "委托单位ID不能为空");
        }
        ClientUnitPO clientUnit = requireNormal(param.getId());
        NormalizedClientUnit data = normalizeParam(param);
        checkDuplicate(data.getName(), clientUnit.getId());
        clientUnit.setName(data.getName());
        clientUnit.setContactName(data.getContactName());
        clientUnit.setPhonenumber(data.getPhonenumber());
        clientUnit.setAddress(data.getAddress());
        clientUnit.setUpdateBy(resolveCurrentUserId());
        clientUnit.setUpdateTime(LocalDateTime.now());
        return this.updateById(clientUnit);
    }

    @Override
    public ClientUnitVO getClientUnit(String id) {
        ClientUnitPO clientUnit = requireNormal(id);
        return toVO(clientUnit, resolveUserNameMap(Arrays.asList(clientUnit)));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deleteClientUnits(List<String> ids) {
        List<String> validIds = normalizeIds(ids);
        if (validIds.isEmpty()) {
            throw new BusinessException(CommonResponseEnum.FAIL, "委托单位ID不能为空");
        }
        return this.lambdaUpdate()
                .set(ClientUnitPO::getStatus, ClientUnitConst.STATUS_DELETED)
                .set(ClientUnitPO::getUpdateBy, resolveCurrentUserId())
                .set(ClientUnitPO::getUpdateTime, LocalDateTime.now())
                .in(ClientUnitPO::getId, validIds)
                .eq(ClientUnitPO::getStatus, ClientUnitConst.STATUS_NORMAL)
                .update();
    }

    @Override
    public void exportClientUnits(ClientUnitParam.QueryParam param) {
        List<ClientUnitExcelVO> exportRows = this.list(buildQueryWrapper(param == null ? new ClientUnitParam.QueryParam() : param))
                .stream()
                .map(this::toExcelVO)
                .collect(Collectors.toList());
        ExcelUtil.exportExcel(ExportFileNameUtil.appendToday("委托单位列表.xlsx"), "委托单位列表", ClientUnitExcelVO.class, exportRows);
    }

    @Override
    public void downloadImportTemplate() {
        HttpServletResponse response = HttpServletUtil.getResponse();
        try (XSSFWorkbook workbook = new XSSFWorkbook()) {
            XSSFSheet sheet = workbook.createSheet("委托单位导入模板");
            writeTemplateHeader(workbook, sheet);
            String fileName = URLEncoder.encode(ExportFileNameUtil.appendToday("委托单位导入模板.xlsx"), "UTF-8");
            response.reset();
            response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
            try (ServletOutputStream outputStream = response.getOutputStream()) {
                workbook.write(outputStream);
                outputStream.flush();
            }
        } catch (IOException exception) {
            log.error("下载委托单位导入模板失败", exception);
            throw new BusinessException(CommonResponseEnum.FAIL, "下载委托单位导入模板失败");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ClientUnitImportResultVO importClientUnits(MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new BusinessException(CommonResponseEnum.FAIL, "导入文件不能为空");
        }
        List<ClientUnitExcelVO> rows = readImportRows(file);
        ClientUnitImportResultVO result = new ClientUnitImportResultVO();
        if (rows.isEmpty()) {
            result.setFailCount(0);
            result.setSuccessCount(0);
            return result;
        }
        LocalDateTime now = LocalDateTime.now();
        String userId = resolveCurrentUserId();
        Set<String> fileKeys = new HashSet<String>();
        List<ClientUnitPO> saveRows = new ArrayList<ClientUnitPO>();
        for (int i = 0; i < rows.size(); i++) {
            int rowNumber = i + 2;
            try {
                NormalizedClientUnit data = normalizeExcelRow(rows.get(i));
                String duplicateKey = data.getName();
                if (!fileKeys.add(duplicateKey)) {
                    throw new BusinessException(CommonResponseEnum.FAIL, "文件内存在重复委托单位名称");
                }
                checkDuplicate(data.getName(), null);
                ClientUnitPO clientUnit = new ClientUnitPO();
                clientUnit.setId(UUID.randomUUID().toString());
                clientUnit.setName(data.getName());
                clientUnit.setContactName(data.getContactName());
                clientUnit.setPhonenumber(data.getPhonenumber());
                clientUnit.setAddress(data.getAddress());
                clientUnit.setStatus(ClientUnitConst.STATUS_NORMAL);
                clientUnit.setCreateBy(userId);
                clientUnit.setCreateTime(now);
                clientUnit.setUpdateBy(userId);
                clientUnit.setUpdateTime(now);
                saveRows.add(clientUnit);
                result.setSuccessCount(result.getSuccessCount() + 1);
            } catch (BusinessException exception) {
                result.setFailCount(result.getFailCount() + 1);
                result.getFailReasons().add("第" + rowNumber + "行：" + exception.getMessage());
            }
        }
        if (!saveRows.isEmpty()) {
            this.saveBatch(saveRows);
        }
        return result;
    }

    private LambdaQueryWrapper<ClientUnitPO> buildQueryWrapper(ClientUnitParam.QueryParam query) {
        LambdaQueryWrapper<ClientUnitPO> wrapper = new LambdaQueryWrapper<ClientUnitPO>();
        wrapper.eq(ClientUnitPO::getStatus, ClientUnitConst.STATUS_NORMAL)
                .like(StrUtil.isNotBlank(query.getName()), ClientUnitPO::getName, query.getName());
        if (StrUtil.isNotBlank(query.getSearchBeginTime())) {
            wrapper.ge(ClientUnitPO::getCreateTime, parseDateTime(query.getSearchBeginTime(), "开始时间格式不正确"));
        }
        if (StrUtil.isNotBlank(query.getSearchEndTime())) {
            wrapper.le(ClientUnitPO::getCreateTime, parseDateTime(query.getSearchEndTime(), "结束时间格式不正确"));
        }
        wrapper.orderByDesc(ClientUnitPO::getCreateTime);
        return wrapper;
    }

    private ClientUnitPO requireNormal(String id) {
        String normalizedId = trimToNull(id);
        if (normalizedId == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, "委托单位ID不能为空");
        }
        ClientUnitPO clientUnit = this.lambdaQuery()
                .eq(ClientUnitPO::getId, normalizedId)
                .eq(ClientUnitPO::getStatus, ClientUnitConst.STATUS_NORMAL)
                .one();
        if (clientUnit == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, "委托单位不存在或已删除");
        }
        return clientUnit;
    }

    private void checkDuplicate(String name, String excludeId) {
        LambdaQueryWrapper<ClientUnitPO> wrapper = new LambdaQueryWrapper<ClientUnitPO>();
        wrapper.eq(ClientUnitPO::getName, name)
                .eq(ClientUnitPO::getStatus, ClientUnitConst.STATUS_NORMAL)
                .ne(StrUtil.isNotBlank(excludeId), ClientUnitPO::getId, excludeId);
        if (this.count(wrapper) > 0) {
            throw new BusinessException(CommonResponseEnum.FAIL, "委托单位名称已存在");
        }
    }

    private NormalizedClientUnit normalizeParam(ClientUnitParam.AddParam param) {
        if (param == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, "委托单位参数不能为空");
        }
        return normalizeFields(param.getName(), param.getContactName(), param.getPhonenumber(), param.getAddress());
    }

    private NormalizedClientUnit normalizeExcelRow(ClientUnitExcelVO row) {
        if (row == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, "空行");
        }
        return normalizeFields(row.getName(), row.getContactName(), row.getPhonenumber(), row.getAddress());
    }

    private NormalizedClientUnit normalizeFields(String name, String contactName, String phonenumber, String address) {
        NormalizedClientUnit data = new NormalizedClientUnit();
        data.setName(requireText(name, "单位名称不能为空", 32, "单位名称不能超过32个字符"));
        data.setContactName(optionalText(contactName, 200, "联系人不能超过200个字符"));
        data.setPhonenumber(optionalText(phonenumber, 32, "联系方式不能超过32个字符"));
        data.setAddress(optionalText(address, 128, "地址不能超过128个字符"));
        return data;
    }

    private String requireText(String value, String blankMessage, int maxLength, String lengthMessage) {
        String text = trimToNull(value);
        if (text == null) {
            throw new BusinessException(CommonResponseEnum.FAIL, blankMessage);
        }
        if (text.length() > maxLength) {
            throw new BusinessException(CommonResponseEnum.FAIL, lengthMessage);
        }
        return text;
    }

    private String optionalText(String value, int maxLength, String lengthMessage) {
        String text = trimToNull(value);
        if (text != null && text.length() > maxLength) {
            throw new BusinessException(CommonResponseEnum.FAIL, lengthMessage);
        }
        return text;
    }

    private List<ClientUnitExcelVO> readImportRows(MultipartFile file) {
        try (InputStream inputStream = file.getInputStream();
             XSSFWorkbook workbook = new XSSFWorkbook(inputStream)) {
            XSSFSheet sheet = workbook.getNumberOfSheets() == 0 ? null : workbook.getSheetAt(0);
            if (sheet == null || sheet.getLastRowNum() < 1) {
                return Collections.emptyList();
            }
            List<ClientUnitExcelVO> rows = new ArrayList<ClientUnitExcelVO>();
            for (int i = 1; i <= sheet.getLastRowNum(); i++) {
                Row row = sheet.getRow(i);
                if (isEmptyRow(row)) {
                    continue;
                }
                ClientUnitExcelVO excelVO = new ClientUnitExcelVO();
                excelVO.setName(readCell(row, 0));
                excelVO.setContactName(readCell(row, 1));
                excelVO.setPhonenumber(readCell(row, 2));
                excelVO.setAddress(readCell(row, 3));
                rows.add(excelVO);
            }
            return rows;
        } catch (IOException exception) {
            log.error("读取委托单位导入文件失败", exception);
            throw new BusinessException(CommonResponseEnum.FAIL, "读取导入文件失败");
        }
    }

    private void writeTemplateHeader(XSSFWorkbook workbook, XSSFSheet sheet) {
        CellStyle style = workbook.createCellStyle();
        Font font = workbook.createFont();
        font.setBold(true);
        style.setFont(font);
        Row row = sheet.createRow(0);
        for (int i = 0; i < TEMPLATE_HEADERS.length; i++) {
            Cell cell = row.createCell(i);
            cell.setCellValue(TEMPLATE_HEADERS[i]);
            cell.setCellStyle(style);
            sheet.setColumnWidth(i, 25 * 256);
        }
    }

    private boolean isEmptyRow(Row row) {
        if (row == null) {
            return true;
        }
        for (int i = 0; i < TEMPLATE_HEADERS.length; i++) {
            if (trimToNull(readCell(row, i)) != null) {
                return false;
            }
        }
        return true;
    }

    private String readCell(Row row, int index) {
        Cell cell = row.getCell(index);
        if (cell == null) {
            return null;
        }
        return trimToNull(new DataFormatter().formatCellValue(cell));
    }

    private List<String> normalizeIds(List<String> ids) {
        if (ids == null || ids.isEmpty()) {
            return Collections.emptyList();
        }
        List<String> result = new ArrayList<String>();
        for (String id : ids) {
            String normalizedId = trimToNull(id);
            if (normalizedId != null && !result.contains(normalizedId)) {
                result.add(normalizedId);
            }
        }
        return result;
    }

    private LocalDateTime parseDateTime(String value, String errorMessage) {
        try {
            return LocalDateTime.parse(value.trim(), DATE_TIME_FORMATTER);
        } catch (DateTimeParseException exception) {
            throw new BusinessException(CommonResponseEnum.FAIL, errorMessage);
        }
    }

    private ClientUnitVO toVO(ClientUnitPO clientUnit, Map<String, String> userNameMap) {
        ClientUnitVO vo = new ClientUnitVO();
        BeanUtil.copyProperties(clientUnit, vo);
        vo.setCreateBy(resolveUserName(clientUnit.getCreateBy(), userNameMap));
        vo.setUpdateBy(resolveUserName(clientUnit.getUpdateBy(), userNameMap));
        return vo;
    }

    private ClientUnitExcelVO toExcelVO(ClientUnitPO clientUnit) {
        ClientUnitExcelVO vo = new ClientUnitExcelVO();
        BeanUtil.copyProperties(clientUnit, vo);
        return vo;
    }

    private Map<String, String> resolveUserNameMap(List<ClientUnitPO> clientUnits) {
        List<String> userIds = clientUnits.stream()
                .flatMap(clientUnit -> Arrays.asList(clientUnit.getCreateBy(), clientUnit.getUpdateBy()).stream())
                .filter(StrUtil::isNotBlank)
                .collect(Collectors.toList());
        return clientUnitUserNameResolver.resolveUserNames(userIds);
    }

    private String resolveUserName(String userId, Map<String, String> userNameMap) {
        if (StrUtil.isBlank(userId)) {
            return null;
        }
        String userName = userNameMap == null ? null : userNameMap.get(userId);
        return StrUtil.isNotBlank(userName) ? userName : userId;
    }

    private String resolveCurrentUserId() {
        String userId = RequestUtil.getUserId();
        return StrUtil.isBlank(userId) ? null : userId;
    }

    private String trimToNull(String value) {
        if (value == null) {
            return null;
        }
        String text = value.trim();
        return text.isEmpty() ? null : text;
    }

    @lombok.Data
    private static class NormalizedClientUnit {
        private String name;
        private String contactName;
        private String phonenumber;
        private String address;
    }
}
