package com.usky.sas.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.usky.common.core.bean.CommonPage;
import com.usky.sas.domain.SasBuilding;
import com.usky.sas.domain.SasHouse;
import com.usky.sas.domain.SasPerson;
import com.usky.sas.domain.SasPoliceStation;
import com.usky.sas.domain.SasSysArea;
import com.usky.sas.domain.SasVillage;
import com.usky.sas.domain.SasEquip;
import com.usky.sas.domain.SasEquipCode;
import com.usky.sas.domain.SasEntrance;
import com.usky.sas.domain.SasCredentialTypeCode;
import com.usky.sas.domain.SasSystemConfig;
import com.usky.sas.mapper.SasBuildingMapper;
import com.usky.sas.mapper.SasHouseMapper;
import com.usky.sas.mapper.SasPersonMapper;
import com.usky.sas.mapper.SasPoliceStationMapper;
import com.usky.sas.mapper.SasSysAreaMapper;
import com.usky.sas.mapper.SasVillageMapper;
import com.usky.sas.mapper.SasEquipMapper;
import com.usky.sas.mapper.SasEquipCodeMapper;
import com.usky.sas.mapper.SasEntranceMapper;
import com.usky.sas.mapper.SasGisMapper;
import com.usky.sas.mapper.SasCredentialTypeCodeMapper;
import com.usky.sas.mapper.SasSystemConfigMapper;
import com.usky.sas.domain.SasGis;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.usky.sas.service.SasOneStandardSixRealitiesService;
import com.usky.sas.service.SasConfigService;
import com.usky.sas.service.dto.agbox.JsonRpcRequest;
import com.usky.sas.service.dto.agbox.VillageInfoVO;
import com.usky.sas.service.dto.agbox.VillageListVO;
import com.usky.sas.service.dto.agbox.PowerVo;
import com.usky.sas.service.dto.agbox.EquipVo;
import com.usky.sas.service.dto.agbox.EntranceVo;
import com.usky.sas.service.dto.agbox.BuildingVO;
import com.usky.sas.service.dto.agbox.HouseVo;
import com.usky.sas.service.vo.*;
import cn.hutool.http.HttpRequest;
import cn.hutool.core.util.IdcardUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class SasOneStandardSixRealitiesServiceImpl implements SasOneStandardSixRealitiesService {

    private static final String AGBOX_VILLAGE_PATH = "/agbox/village";
    private static final int PAGE_SIZE = 100;
    /** AG 接口请求超时（毫秒） */
    private static final int HTTP_TIMEOUT_MS = 60000;
    /** 请求失败时最大重试次数 */
    private static final int MAX_RETRIES = 3;

    @Autowired
    private SasPersonMapper sasPersonMapper;

    @Autowired
    private SasConfigService sasConfigService;

    @Autowired
    private SasBuildingMapper sasBuildingMapper;

    @Autowired
    private SasHouseMapper sasHouseMapper;

    @Autowired
    private SasVillageMapper sasVillageMapper;

    @Autowired
    private SasSysAreaMapper sasSysAreaMapper;

    @Autowired
    private SasPoliceStationMapper sasPoliceStationMapper;

    @Autowired
    private SasGisMapper sasGisMapper;

    @Autowired
    private SasEquipMapper sasEquipMapper;

    @Autowired
    private SasEquipCodeMapper sasEquipCodeMapper;

    @Autowired
    private SasEntranceMapper sasEntranceMapper;

    @Autowired
    private SasCredentialTypeCodeMapper sasCredentialTypeCodeMapper;

    @Autowired
    private SasSystemConfigMapper sasSystemConfigMapper;

    @Override
    public CommonPage<PowerListItem> powerPage(OneStandardSixRealitiesPageRequest request) {
        // 先尝试从 AG 获取实有力量分页，失败或未配置时回退到本地人员表
        com.usky.sas.domain.SasConfig config = sasConfigService.getConfig();
        if (config == null || config.getKeyds() == null || config.getHost() == null) {
            log.debug("实有力量分页：未配置 AG，走本地人员表");
            return localPowerPage(request);
        }

        String villageCode = request.getVillageCode();
        if (villageCode == null || villageCode.isEmpty()) {
            villageCode = getVillageCodeForSync();
        }
        if (villageCode == null || villageCode.isEmpty()) {
            log.debug("实有力量分页：无可用地块编码，走本地人员表");
            return localPowerPage(request);
        }

        int pageNo = request.getCurrent() != null ? request.getCurrent() : 1;
        int pageSize = request.getSize() != null ? request.getSize() : 10;

        try {
            String baseUrl = buildAgboxBaseUrl(config.getHost(), null);
            String villageUrl = baseUrl + AGBOX_VILLAGE_PATH;
            Map<String, Object> params = new HashMap<>();
            params.put("page", pageNo);
            params.put("pageSize", pageSize);
            params.put("code", villageCode);
            JsonRpcRequest getPowerJson = new JsonRpcRequest("getPower", params, null);
            Map<String, Object> requestBody = new HashMap<>();
            requestBody.put("key", config.getKeyds());
            requestBody.put("json", JSONUtil.toJsonStr(getPowerJson));
            log.info("请求AG实有力量分页(getPower)：page={}, pageSize={}, code={}", pageNo, pageSize, villageCode);

            ObjectMapper mapper = new ObjectMapper();
            String resultStr = postWithRetry(villageUrl, requestBody);
            PowerVo powerVo = mapper.readValue(resultStr, PowerVo.class);
            PowerVo.PowerResult result = powerVo != null ? powerVo.getResult() : null;
            List<PowerVo.Power> powerList = (result != null && result.getPowerList() != null)
                    ? result.getPowerList() : Collections.emptyList();

            SasSystemConfig systemConfig = sasSystemConfigMapper.selectOne(new LambdaQueryWrapper<SasSystemConfig>().last("limit 1"));
            boolean maskIdCard = systemConfig != null && Boolean.TRUE.equals(systemConfig.getMaskIdCardNo());

            List<PowerListItem> list = powerList.stream()
                    .map(p -> fromPowerToListItem(p, maskIdCard))
                    .collect(Collectors.toList());

            long total = (result != null && result.getCount() != null) ? result.getCount() : 0L;
            return new CommonPage<>(list, total, (long) pageNo, (long) pageSize);
        } catch (Exception e) {
            log.warn("实有力量分页请求 AG 失败，回退本地人员表：{}", e.getMessage());
            return localPowerPage(request);
        }
    }

    @Override
    public int syncPower() {
        com.usky.sas.domain.SasConfig config = sasConfigService.getConfig();
        if (config == null || config.getKeyds() == null || config.getHost() == null) {
            log.warn("同步实有力量失败：未配置 AG 接口（key/host）");
            return 0;
        }
        String villageCode = getVillageCodeForSync();
        if (villageCode == null || villageCode.isEmpty()) {
            log.warn("同步实有力量失败：地块信息表中无可用地块（请先同步地块或设置默认地块）");
            return 0;
        }
        String baseUrl = buildAgboxBaseUrl(config.getHost(), null);
        String villageUrl = baseUrl + AGBOX_VILLAGE_PATH;
        String key = config.getKeyds();
        ObjectMapper mapper = new ObjectMapper();
        int totalSynced = 0;
        int page = 1;

        try {
            while (true) {
                Map<String, Object> params = new HashMap<>();
                params.put("page", page);
                params.put("pageSize", PAGE_SIZE);
                params.put("code", villageCode);
                JsonRpcRequest getPowerJson = new JsonRpcRequest("getPower", params, null);
                Map<String, Object> requestBody = new HashMap<>();
                requestBody.put("key", key);
                requestBody.put("json", JSONUtil.toJsonStr(getPowerJson));
                log.info("请求AG实有力量(getPower)，page={}：{}", page, JSONUtil.toJsonStr(getPowerJson));

                String resultStr = postWithRetry(villageUrl, requestBody);
                log.info("AG实有力量响应 page={}：{}", page, resultStr);

                PowerVo powerVo = mapper.readValue(resultStr, PowerVo.class);
                PowerVo.PowerResult result = powerVo != null ? powerVo.getResult() : null;
                List<PowerVo.Power> powerList = (result != null && result.getPowerList() != null) ? result.getPowerList() : Collections.emptyList();

                if (powerList.isEmpty()) {
                    break;
                }

                for (PowerVo.Power power : powerList) {
                    if (power.getPersonCode() == null || power.getPersonCode().isEmpty()) {
                        continue;
                    }
                    try {
                        SasPerson entity = powerToSasPerson(power);
                        SasPerson exist = sasPersonMapper.selectById(power.getPersonCode());
                        LocalDateTime now = LocalDateTime.now();
                        if (exist != null) {
                            entity.setCreateTime(exist.getCreateTime());
                            entity.setUpdateTime(now);
                            sasPersonMapper.updateById(entity);
                        } else {
                            entity.setCreateTime(now);
                            entity.setUpdateTime(now);
                            sasPersonMapper.insert(entity);
                        }
                        totalSynced++;
                    } catch (Exception e) {
                        log.error("同步实有力量 {} 失败：{}", power.getPersonCode(), e.getMessage(), e);
                    }
                }

                if (powerList.size() < PAGE_SIZE) {
                    break;
                }
                page++;
            }
        } catch (Exception e) {
            log.error("同步实有力量失败：{}", e.getMessage(), e);
        }
        log.info("实有力量同步完成，共同步 {} 条", totalSynced);
        return totalSynced;
    }

    private SasPerson powerToSasPerson(PowerVo.Power power) {
        SasPerson entity = new SasPerson();
        entity.setPersonCode(power.getPersonCode());
        entity.setName(power.getName());
        entity.setCredentialType(power.getCredentialType());
        entity.setCredentialNo(power.getCredentialNo());
        List<String> phone = power.getPhone();
        if (phone != null && !phone.isEmpty()) {
            if (phone.size() >= 1) entity.setPhone1(phone.get(0));
            if (phone.size() >= 2) entity.setPhone2(phone.get(1));
            if (phone.size() >= 3) entity.setPhone3(phone.get(2));
        }
        entity.setPowerCode(power.getPowerCode() != null && !power.getPowerCode().isEmpty() ? power.getPowerCode() : "1");
        entity.setSecurityCardNo(power.getSecurityCardNo());
        entity.setSecurityDutiesCode(power.getSecurityDutiesCode());
        entity.setPicId(power.getPicId());
        entity.setCompanyCode(power.getCompanyCode());
        entity.setOrigin(power.getOrigin());
        entity.setPlaceOfBirth(power.getPlaceOfBirth());
        entity.setEducationCode(power.getEducationCode());
        entity.setMaritalStatusCode(power.getMaritalStatusCode());
        entity.setNationalityCode(power.getNationalityCode());
        if (power.getUpdateTime() != null && !power.getUpdateTime().isEmpty()) {
            try {
                entity.setUpdateTime(LocalDateTime.parse(power.getUpdateTime(), java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            } catch (Exception ignored) {
                entity.setUpdateTime(LocalDateTime.now());
            }
        }
        return entity;
    }

    @Override
    public CommonPage<FacilityListItem> facilityPage(OneStandardSixRealitiesPageRequest request) {
        IPage<SasEquip> page = new Page<>(request.getCurrent(), request.getSize());
        LambdaQueryWrapper<SasEquip> wrapper = new LambdaQueryWrapper<>();
        if (request.getVillageCode() != null && !request.getVillageCode().isEmpty()) {
            wrapper.eq(SasEquip::getVillageCode, request.getVillageCode());
        }
        wrapper.orderByDesc(SasEquip::getUpdateTime);
        page = sasEquipMapper.selectPage(page, wrapper);
        List<SasEquip> records = page.getRecords();
        if (records == null) {
            records = Collections.emptyList();
        }
        Set<Integer> equipTypeCodes = new HashSet<>();
        for (SasEquip e : records) {
            if (e.getEquipType() != null) equipTypeCodes.add(e.getEquipType());
        }
        Map<Integer, String> equipTypeNameMap = new HashMap<>();
        if (!equipTypeCodes.isEmpty()) {
            for (Integer code : equipTypeCodes) {
                SasEquipCode ec = sasEquipCodeMapper.selectById(code);
                if (ec != null) equipTypeNameMap.put(code, ec.getName());
            }
        }
        List<FacilityListItem> list = records.stream()
                .map(e -> fromEquipToFacilityItem(e, equipTypeNameMap))
                .collect(Collectors.toList());
        return new CommonPage<>(list, page.getTotal(), page.getCurrent(), page.getSize());
    }

    private FacilityListItem fromEquipToFacilityItem(SasEquip e, Map<Integer, String> equipTypeNameMap) {
        FacilityListItem item = new FacilityListItem();
        item.setCode(e.getCode());
        item.setEquipName(e.getEquipName());
        item.setEquipType(e.getEquipType());
        item.setEquipTypeName(e.getEquipType() != null ? equipTypeNameMap.get(e.getEquipType()) : null);
        item.setVillageCode(e.getVillageCode());
        item.setFloor(e.getFloor());
        item.setUrlId(e.getUrlId());
        item.setPicUrl(e.getUrlId());
        item.setGisId(e.getGisId());
        item.setRemark(e.getRemark());
        item.setCreateTime(e.getCreateTime());
        item.setUpdateTime(e.getUpdateTime());
        return item;
    }

    @Override
    public int syncFacility() {
        com.usky.sas.domain.SasConfig config = sasConfigService.getConfig();
        if (config == null || config.getKeyds() == null || config.getHost() == null) {
            log.warn("同步实有设施失败：未配置 AG 接口（key/host）");
            return 0;
        }
        String villageCode = getVillageCodeForSync();
        if (villageCode == null || villageCode.isEmpty()) {
            log.warn("同步实有设施失败：地块信息表中无可用地块（请先同步地块或设置默认地块）");
            return 0;
        }
        String baseUrl = buildAgboxBaseUrl(config.getHost(), null);
        String villageUrl = baseUrl + AGBOX_VILLAGE_PATH;
        String key = config.getKeyds();
        ObjectMapper mapper = new ObjectMapper();
        int totalSynced = 0;
        int page = 1;

        try {
            while (true) {
                Map<String, Object> params = new HashMap<>();
                params.put("page", page);
                params.put("pageSize", PAGE_SIZE);
                params.put("code", villageCode);
                JsonRpcRequest getEquipJson = new JsonRpcRequest("getEquip", params, null);
                Map<String, Object> requestBody = new HashMap<>();
                requestBody.put("key", key);
                requestBody.put("json", JSONUtil.toJsonStr(getEquipJson));
                log.info("请求AG实有设施(getEquip)，page={}：{}", page, JSONUtil.toJsonStr(getEquipJson));

                String resultStr = postWithRetry(villageUrl, requestBody);
                log.info("AG实有设施响应 page={}：{}", page, resultStr);

                EquipVo equipVo = mapper.readValue(resultStr, EquipVo.class);
                EquipVo.EquipResult result = equipVo != null ? equipVo.getResult() : null;
                List<EquipVo.EquipMsg> equipList = (result != null && result.getEquipList() != null) ? result.getEquipList() : Collections.emptyList();

                if (equipList.isEmpty()) {
                    break;
                }

                for (EquipVo.EquipMsg msg : equipList) {
                    if (msg.getCode() == null || msg.getCode().isEmpty()) {
                        continue;
                    }
                    try {
                        SasEquip entity = equipMsgToSasEquip(msg, villageCode);
                        SasEquip exist = sasEquipMapper.selectById(msg.getCode());
                        LocalDateTime now = LocalDateTime.now();
                        if (exist != null) {
                            entity.setCreateTime(exist.getCreateTime());
                            entity.setUpdateTime(now);
                            sasEquipMapper.updateById(entity);
                        } else {
                            entity.setCreateTime(now);
                            entity.setUpdateTime(now);
                            sasEquipMapper.insert(entity);
                        }
                        totalSynced++;
                    } catch (Exception e) {
                        log.error("同步实有设施 {} 失败：{}", msg.getCode(), e.getMessage(), e);
                    }
                }

                if (equipList.size() < PAGE_SIZE) {
                    break;
                }
                page++;
            }
        } catch (Exception e) {
            log.error("同步实有设施失败：{}", e.getMessage(), e);
        }
        log.info("实有设施同步完成，共同步 {} 条", totalSynced);
        return totalSynced;
    }

    private SasEquip equipMsgToSasEquip(EquipVo.EquipMsg msg, String villageCodeForSync) {
        SasEquip entity = new SasEquip();
        entity.setCode(msg.getCode());
        entity.setEquipName(msg.getEquipName());
        entity.setEquipType(msg.getEquipType());
        entity.setVillageCode(msg.getVillageCode() != null ? msg.getVillageCode() : villageCodeForSync);
        entity.setFloor(msg.getFloor());
        entity.setGisId(msg.getGisId());
        entity.setRemark(msg.getRemark());
        if (msg.getUrlId() != null && !msg.getUrlId().isEmpty()) {
            entity.setUrlId(msg.getUrlId());
        } else if (msg.getEquipPicUrl() != null) {
            String url = msg.getEquipPicUrl().getUrl();
            String path = msg.getEquipPicUrl().getPath();
            if (url != null && path != null) {
                entity.setUrlId(url + path);
            } else if (path != null) {
                entity.setUrlId(path);
            } else if (url != null) {
                entity.setUrlId(url);
            }
        }
        if (msg.getUpdateTime() != null && !msg.getUpdateTime().isEmpty()) {
            try {
                entity.setUpdateTime(LocalDateTime.parse(msg.getUpdateTime(), java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            } catch (Exception ignored) {
                entity.setUpdateTime(LocalDateTime.now());
            }
        }
        return entity;
    }

    @Override
    public CommonPage<EntranceListItem> entrancePage(OneStandardSixRealitiesPageRequest request) {
        IPage<SasEntrance> page = new Page<>(request.getCurrent(), request.getSize());
        LambdaQueryWrapper<SasEntrance> wrapper = new LambdaQueryWrapper<>();
        if (request.getVillageCode() != null && !request.getVillageCode().isEmpty()) {
            wrapper.eq(SasEntrance::getVillageCode, request.getVillageCode());
        }
        wrapper.orderByDesc(SasEntrance::getUpdateTime);
        page = sasEntranceMapper.selectPage(page, wrapper);
        List<SasEntrance> records = page.getRecords();
        if (records == null) {
            records = Collections.emptyList();
        }
        List<EntranceListItem> list = records.stream()
                .map(this::fromEntranceToListItem)
                .collect(Collectors.toList());
        return new CommonPage<>(list, page.getTotal(), page.getCurrent(), page.getSize());
    }

    private EntranceListItem fromEntranceToListItem(SasEntrance e) {
        EntranceListItem item = new EntranceListItem();
        item.setEntranceCode(e.getEntranceCode());
        item.setName(e.getName());
        item.setVillageCode(e.getVillageCode());
        item.setAddress(e.getAddress());
        if (e.getDeviceIds() != null && !e.getDeviceIds().isEmpty()) {
            item.setDeviceIds(Arrays.stream(e.getDeviceIds().split(","))
                    .map(String::trim)
                    .filter(s -> !s.isEmpty())
                    .collect(Collectors.toList()));
        }
        item.setCreateTime(e.getCreateTime());
        item.setUpdateTime(e.getUpdateTime());
        return item;
    }

    @Override
    public int syncEntrance() {
        com.usky.sas.domain.SasConfig config = sasConfigService.getConfig();
        if (config == null || config.getKeyds() == null || config.getHost() == null) {
            log.warn("同步出入口失败：未配置 AG 接口（key/host）");
            return 0;
        }
        String villageCode = getVillageCodeForSync();
        if (villageCode == null || villageCode.isEmpty()) {
            log.warn("同步出入口失败：地块信息表中无可用地块（请先同步地块或设置默认地块）");
            return 0;
        }
        String baseUrl = buildAgboxBaseUrl(config.getHost(), null);
        String villageUrl = baseUrl + AGBOX_VILLAGE_PATH;
        String key = config.getKeyds();
        ObjectMapper mapper = new ObjectMapper();
        int totalSynced = 0;
        int page = 1;

        try {
            while (true) {
                Map<String, Object> params = new HashMap<>();
                params.put("page", page);
                params.put("pageSize", PAGE_SIZE);
                params.put("villageCode", villageCode);
                JsonRpcRequest getEntranceJson = new JsonRpcRequest("getEntrance", params, null);
                Map<String, Object> requestBody = new HashMap<>();
                requestBody.put("key", key);
                requestBody.put("json", JSONUtil.toJsonStr(getEntranceJson));
                log.info("请求AG出入口(getEntrance)，page={}：{}", page, JSONUtil.toJsonStr(getEntranceJson));

                String resultStr = postWithRetry(villageUrl, requestBody);
                log.info("AG出入口响应 page={}：{}", page, resultStr);

                EntranceVo entranceVo = mapper.readValue(resultStr, EntranceVo.class);
                EntranceVo.EntranceResult result = entranceVo != null ? entranceVo.getResult() : null;
                List<EntranceVo.EntranceList> entranceList = (result != null && result.getEntranceList() != null) ? result.getEntranceList() : Collections.emptyList();

                if (entranceList.isEmpty()) {
                    break;
                }

                for (EntranceVo.EntranceList msg : entranceList) {
                    if (msg.getEntranceCode() == null || msg.getEntranceCode().isEmpty()) {
                        continue;
                    }
                    try {
                        SasEntrance entity = entranceListToSasEntrance(msg, villageCode);
                        SasEntrance exist = sasEntranceMapper.selectById(msg.getEntranceCode());
                        LocalDateTime now = LocalDateTime.now();
                        if (exist != null) {
                            entity.setCreateTime(exist.getCreateTime());
                            entity.setUpdateTime(now);
                            sasEntranceMapper.updateById(entity);
                        } else {
                            entity.setCreateTime(now);
                            entity.setUpdateTime(now);
                            sasEntranceMapper.insert(entity);
                        }
                        totalSynced++;
                    } catch (Exception e) {
                        log.error("同步出入口 {} 失败：{}", msg.getEntranceCode(), e.getMessage(), e);
                    }
                }

                if (entranceList.size() < PAGE_SIZE) {
                    break;
                }
                page++;
            }
        } catch (Exception e) {
            log.error("同步出入口失败：{}", e.getMessage(), e);
        }
        log.info("出入口同步完成，共同步 {} 条", totalSynced);
        return totalSynced;
    }

    private SasEntrance entranceListToSasEntrance(EntranceVo.EntranceList msg, String villageCodeForSync) {
        SasEntrance entity = new SasEntrance();
        entity.setEntranceCode(msg.getEntranceCode());
        entity.setName(msg.getName());
        entity.setVillageCode(msg.getVillageCode() != null ? msg.getVillageCode() : villageCodeForSync);
        entity.setAddress(msg.getAddress());
        if (msg.getDeviceIds() != null && !msg.getDeviceIds().isEmpty()) {
            entity.setDeviceIds(String.join(",", msg.getDeviceIds()));
        }
        if (msg.getPicId() != null && !msg.getPicId().isEmpty()) {
            entity.setPicId(msg.getPicId());
        } else if (msg.getEntrancePicUrl() != null) {
            String url = msg.getEntrancePicUrl().getUrl();
            String path = msg.getEntrancePicUrl().getPath();
            if (url != null && path != null) {
                entity.setPicId(url + path);
            } else if (path != null) {
                entity.setPicId(path);
            } else if (url != null) {
                entity.setPicId(url);
            }
        }
        if (msg.getUpdateTime() != null && !msg.getUpdateTime().isEmpty()) {
            try {
                entity.setUpdateTime(LocalDateTime.parse(msg.getUpdateTime(), java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            } catch (Exception ignored) {
                entity.setUpdateTime(LocalDateTime.now());
            }
        }
        return entity;
    }

    @Override
    public CommonPage<AreaListItem> areaPage(OneStandardSixRealitiesPageRequest request) {
        IPage<SasBuilding> page = new Page<>(request.getCurrent(), request.getSize());
        LambdaQueryWrapper<SasBuilding> wrapper = new LambdaQueryWrapper<>();
        if (request.getVillageCode() != null && !request.getVillageCode().isEmpty()) {
            wrapper.eq(SasBuilding::getVillageCode, request.getVillageCode());
        }
        page = sasBuildingMapper.selectPage(page, wrapper);
        List<SasBuilding> records = page.getRecords();
        if (records == null) {
            records = Collections.emptyList();
        }
        List<AreaListItem> list = records.stream()
                .map(this::fromBuildingToArea)
                .collect(Collectors.toList());
        return new CommonPage<>(list, page.getTotal(), page.getCurrent(), page.getSize());
    }

    @Override
    public int syncArea() {
        com.usky.sas.domain.SasConfig config = sasConfigService.getConfig();
        if (config == null || config.getKeyds() == null || config.getHost() == null) {
            log.warn("同步区域信息失败：未配置 AG 接口（key/host）");
            return 0;
        }
        String villageCode = getVillageCodeForSync();
        if (villageCode == null || villageCode.isEmpty()) {
            log.warn("同步区域信息失败：地块信息表中无可用地块（请先同步地块或设置默认地块）");
            return 0;
        }
        String baseUrl = buildAgboxBaseUrl(config.getHost(), null);
        String villageUrl = baseUrl + AGBOX_VILLAGE_PATH;
        String key = config.getKeyds();
        ObjectMapper mapper = new ObjectMapper();
        int totalSynced = 0;
        int page = 1;

        try {
            while (true) {
                Map<String, Object> params = new HashMap<>();
                params.put("page", page);
                params.put("pageSize", PAGE_SIZE);
                params.put("villageCode", villageCode);
                JsonRpcRequest getBuildingJson = new JsonRpcRequest("getBuilding", params, null);
                Map<String, Object> requestBody = new HashMap<>();
                requestBody.put("key", key);
                requestBody.put("json", JSONUtil.toJsonStr(getBuildingJson));
                log.info("请求AG区域信息(getBuilding)，page={}：{}", page, JSONUtil.toJsonStr(getBuildingJson));

                String resultStr = postWithRetry(villageUrl, requestBody);
                log.info("AG区域信息响应 page={}：{}", page, resultStr);

                BuildingVO buildingVO = mapper.readValue(resultStr, BuildingVO.class);
                BuildingVO.BuildingListResult result = buildingVO != null ? buildingVO.getResult() : null;
                List<BuildingVO.BuildingList> buildingList = (result != null && result.getBuildingList() != null) ? result.getBuildingList() : Collections.emptyList();

                if (buildingList.isEmpty()) {
                    break;
                }

                for (BuildingVO.BuildingList msg : buildingList) {
                    if (msg.getBuildingCode() == null || msg.getBuildingCode().isEmpty()) {
                        continue;
                    }
                    try {
                        SasBuilding entity = buildingListToSasBuilding(msg, villageCode);
                        SasBuilding exist = sasBuildingMapper.selectById(msg.getBuildingCode());
                        LocalDateTime now = LocalDateTime.now();
                        if (exist != null) {
                            entity.setCreateTime(exist.getCreateTime());
                            entity.setUpdateTime(now);
                            sasBuildingMapper.updateById(entity);
                        } else {
                            entity.setCreateTime(now);
                            entity.setUpdateTime(now);
                            sasBuildingMapper.insert(entity);
                        }
                        totalSynced++;
                    } catch (Exception e) {
                        log.error("同步区域信息 {} 失败：{}", msg.getBuildingCode(), e.getMessage(), e);
                    }
                }

                if (buildingList.size() < PAGE_SIZE) {
                    break;
                }
                page++;
            }
        } catch (Exception e) {
            log.error("同步区域信息失败：{}", e.getMessage(), e);
        }
        log.info("区域信息同步完成，共同步 {} 条", totalSynced);
        return totalSynced;
    }

    private SasBuilding buildingListToSasBuilding(BuildingVO.BuildingList msg, String villageCodeForSync) {
        SasBuilding entity = new SasBuilding();
        entity.setBuildingCode(msg.getBuildingCode());
        entity.setBuildingNo(msg.getBuildingNo());
        entity.setFloorTotal(msg.getFloorTotal());
        entity.setHouseTotal(msg.getHouseTotal());
        entity.setNote(msg.getNote());
        entity.setGisId(msg.getGisId());
        entity.setVillageCode(msg.getVillageCode() != null ? msg.getVillageCode() : villageCodeForSync);
        entity.setPlaceType(msg.getPlaceType());
        if (msg.getUpdateTime() != null && !msg.getUpdateTime().isEmpty()) {
            try {
                entity.setUpdateTime(LocalDateTime.parse(msg.getUpdateTime(), java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            } catch (Exception ignored) {
                entity.setUpdateTime(LocalDateTime.now());
            }
        }
        return entity;
    }

    @Override
    public CommonPage<HouseListItem> housePage(OneStandardSixRealitiesPageRequest request) {
        IPage<SasHouse> page = new Page<>(request.getCurrent(), request.getSize());
        LambdaQueryWrapper<SasHouse> wrapper = new LambdaQueryWrapper<>();
        if (request.getBuildingCode() != null && !request.getBuildingCode().isEmpty()) {
            wrapper.eq(SasHouse::getBuildingCode, request.getBuildingCode());
        }
        if (request.getVillageCode() != null && !request.getVillageCode().isEmpty()) {
            wrapper.eq(SasHouse::getVillageCode, request.getVillageCode());
        }
        if (request.getHouseNo() != null && !request.getHouseNo().isEmpty()) {
            wrapper.eq(SasHouse::getHouseNo, request.getHouseNo());
        }
        page = sasHouseMapper.selectPage(page, wrapper);
        List<SasHouse> records = page.getRecords();
        if (records == null) {
            records = Collections.emptyList();
        }
        List<HouseListItem> list = records.stream()
                .map(this::fromHouseToHouseItem)
                .collect(Collectors.toList());
        return new CommonPage<>(list, page.getTotal(), page.getCurrent(), page.getSize());
    }

    @Override
    public int syncHouse(String buildingCode) {
        com.usky.sas.domain.SasConfig config = sasConfigService.getConfig();
        if (config == null || config.getKeyds() == null || config.getHost() == null) {
            log.warn("同步房屋信息失败：未配置 AG 接口（key/host）");
            return 0;
        }
        String baseUrl = buildAgboxBaseUrl(config.getHost(), null);
        String villageUrl = baseUrl + AGBOX_VILLAGE_PATH;
        String key = config.getKeyds();
        ObjectMapper mapper = new ObjectMapper();
        int totalSynced = 0;
        int page = 1;

        try {
            while (true) {
                Map<String, Object> params = new HashMap<>();
                params.put("page", page);
                params.put("pageSize", PAGE_SIZE);
                if (buildingCode != null && !buildingCode.isEmpty()) {
                    params.put("buildingCode", buildingCode);
                }
                JsonRpcRequest getHouseJson = new JsonRpcRequest("getHouse", params, null);
                Map<String, Object> requestBody = new HashMap<>();
                requestBody.put("key", key);
                requestBody.put("json", JSONUtil.toJsonStr(getHouseJson));
                log.info("请求AG房屋信息(getHouse)，page={}：{}", page, JSONUtil.toJsonStr(getHouseJson));

                String resultStr = postWithRetry(villageUrl, requestBody);
                log.info("AG房屋信息响应 page={}：{}", page, resultStr);

                HouseVo houseVo = mapper.readValue(resultStr, HouseVo.class);
                HouseVo.HouseResult result = houseVo != null ? houseVo.getResult() : null;
                List<HouseVo.HouseList> houseList = (result != null && result.getHouseList() != null) ? result.getHouseList() : Collections.emptyList();

                if (houseList.isEmpty()) {
                    break;
                }

                for (HouseVo.HouseList msg : houseList) {
                    if (msg.getHouseCode() == null || msg.getHouseCode().isEmpty()) {
                        continue;
                    }
                    try {
                        SasHouse entity = houseListToSasHouse(msg);
                        SasHouse exist = sasHouseMapper.selectById(msg.getHouseCode());
                        LocalDateTime now = LocalDateTime.now();
                        if (exist != null) {
                            entity.setCreateTime(exist.getCreateTime());
                            entity.setUpdateTime(now);
                            sasHouseMapper.updateById(entity);
                        } else {
                            entity.setCreateTime(now);
                            entity.setUpdateTime(now);
                            sasHouseMapper.insert(entity);
                        }
                        totalSynced++;
                    } catch (Exception e) {
                        log.error("同步房屋信息 {} 失败：{}", msg.getHouseCode(), e.getMessage(), e);
                    }
                }

                if (houseList.size() < PAGE_SIZE) {
                    break;
                }
                page++;
            }
        } catch (Exception e) {
            log.error("同步房屋信息失败：{}", e.getMessage(), e);
        }
        log.info("房屋信息同步完成，共同步 {} 条", totalSynced);
        return totalSynced;
    }

    private SasHouse houseListToSasHouse(HouseVo.HouseList msg) {
        SasHouse entity = new SasHouse();
        entity.setHouseCode(msg.getHouseCode());
        entity.setFloor(msg.getFloor());
        entity.setHouseNo(msg.getHouseNo());
        entity.setHouseLabelCode(msg.getHouseLabelCode());
        entity.setHousePurposeCode(msg.getHousePurposeCode());
        entity.setNote(msg.getNote());
        entity.setIsPublic(msg.getIsPublic());
        entity.setIsAction(msg.getIsAction());
        entity.setSystesasTypeCode(msg.getSystesasTypeCode());
        entity.setBuildingCode(msg.getBuildingCode());
        entity.setGisId(msg.getGisId());
        entity.setPersonNumber(msg.getPersonNumber());
        entity.setHouseArea(msg.getHouseArea());
        entity.setVillageCode(msg.getVillageCode());
        entity.setTags(msg.getTagsAsString());
        entity.setPlaceTypeId(msg.getPlaceTypeId());
        entity.setHouseName(msg.getHouseName());
        if (msg.getUpdateTime() != null && !msg.getUpdateTime().isEmpty()) {
            try {
                entity.setUpdateTime(LocalDateTime.parse(msg.getUpdateTime(), java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            } catch (Exception ignored) {
                entity.setUpdateTime(LocalDateTime.now());
            }
        }
        return entity;
    }

    @Override
    public CommonPage<VillageListItem> villagePage(OneStandardSixRealitiesPageRequest request) {
        IPage<SasVillage> page = new Page<>(request.getCurrent(), request.getSize());
        LambdaQueryWrapper<SasVillage> wrapper = new LambdaQueryWrapper<>();
        if (request.getVillageName() != null && !request.getVillageName().isEmpty()) {
            wrapper.like(SasVillage::getVillageName, request.getVillageName());
        }
        if (request.getIsDefault() != null) {
            wrapper.eq(SasVillage::getIsDefault, request.getIsDefault());
        }
        page = sasVillageMapper.selectPage(page, wrapper);
        List<SasVillage> records = page.getRecords();
        if (records == null) {
            records = Collections.emptyList();
        }
        // 批量查询行政区域、派出所名称、经纬度信息，避免 N+1 查询
        Map<String, String> areaNameMap = buildAreaNameMap(records);
        Map<String, String> policeStationNameMap = buildPoliceStationNameMap(records);
        Map<String, SasGis> gisMap = buildGisMap(records);
        List<VillageListItem> list = records.stream()
                .map(v -> fromVillageToVillageItem(v, areaNameMap, policeStationNameMap, gisMap))
                .collect(Collectors.toList());
        return new CommonPage<>(list, page.getTotal(), page.getCurrent(), page.getSize());
    }

    /**
     * 批量构建行政区域编码 -> 名称映射，避免 N+1 查询
     */
    private Map<String, String> buildAreaNameMap(List<SasVillage> records) {
        Set<String> codes = new HashSet<>();
        for (SasVillage v : records) {
            if (v.getProvinceCode() != null) codes.add(String.valueOf(v.getProvinceCode()));
            if (v.getCityCode() != null) codes.add(String.valueOf(v.getCityCode()));
            if (v.getDistrictCode() != null) codes.add(String.valueOf(v.getDistrictCode()));
            if (v.getStreetCode() != null) codes.add(String.valueOf(v.getStreetCode()));
            if (v.getRoadCode() != null && !v.getRoadCode().isEmpty()) codes.add(v.getRoadCode());
        }
        if (codes.isEmpty()) return Collections.emptyMap();
        List<SasSysArea> areas = sasSysAreaMapper.selectByCodes(new ArrayList<>(codes));
        Map<String, String> map = new HashMap<>(areas.size());
        for (SasSysArea a : areas) {
            map.put(a.getCode(), a.getName());
        }
        return map;
    }

    /**
     * 批量构建 gis_id -> SasGis 映射，用于获取经纬度、高度
     */
    private Map<String, SasGis> buildGisMap(List<SasVillage> records) {
        Set<String> gisIds = new HashSet<>();
        for (SasVillage v : records) {
            if (v.getGisId() != null && !v.getGisId().isEmpty()) gisIds.add(v.getGisId());
        }
        if (gisIds.isEmpty()) return Collections.emptyMap();
        List<SasGis> gisList = sasGisMapper.selectByIds(new ArrayList<>(gisIds));
        Map<String, SasGis> map = new HashMap<>(gisList.size());
        for (SasGis g : gisList) {
            map.put(g.getId(), g);
        }
        return map;
    }

    /**
     * 批量构建派出所编码 -> 名称映射，避免 N+1 查询
     */
    private Map<String, String> buildPoliceStationNameMap(List<SasVillage> records) {
        List<String> codes = records.stream()
                .map(SasVillage::getPoliceStationCode)
                .filter(Objects::nonNull)
                .filter(c -> !c.isEmpty())
                .distinct()
                .collect(Collectors.toList());
        if (codes.isEmpty()) return Collections.emptyMap();
        List<SasPoliceStation> stations = sasPoliceStationMapper.selectByCodes(codes);
        Map<String, String> map = new HashMap<>(stations.size());
        for (SasPoliceStation s : stations) {
            map.put(s.getCode(), s.getName());
        }
        return map;
    }

    @Override
    public int syncVillage() {
        com.usky.sas.domain.SasConfig config = sasConfigService.getConfig();
        if (config == null || config.getKeyds() == null || config.getHost() == null) {
            log.warn("同步地块信息失败：未配置 AG 接口（key/host）");
            return 0;
        }
        String baseUrl = buildAgboxBaseUrl(config.getHost(),null);
        String villageUrl = baseUrl + AGBOX_VILLAGE_PATH;
        String key = config.getKeyds();

        ObjectMapper mapper = new ObjectMapper();
        int totalSynced = 0;
        int page = 1;

        try {
            while (true) {
                Map<String, Object> params = new HashMap<>();
                params.put("page", page);
                params.put("pageSize", PAGE_SIZE);
                JsonRpcRequest getListJson = new JsonRpcRequest("getList", params, null);

                Map<String, Object> requestBody = new HashMap<>();
                requestBody.put("key", key);
                requestBody.put("json", JSONUtil.toJsonStr(getListJson));
                log.info("请求AG地块列表，page={}：{}", villageUrl, JSONUtil.toJsonStr(requestBody));

                String resultStr = postWithRetry(villageUrl, requestBody);
                log.info("AG地块列表响应：{}", resultStr);

                JsonNode jsonNode = mapper.readTree(resultStr);
                VillageListVO villageListVO = mapper.treeToValue(jsonNode, VillageListVO.class);

                if (villageListVO == null || villageListVO.getResult() == null
                        || villageListVO.getResult().getVillageList() == null
                        || villageListVO.getResult().getVillageList().isEmpty()) {
                    break;
                }

                List<VillageListVO.VillageListItem> villageList = villageListVO.getResult().getVillageList();
                for (VillageListVO.VillageListItem item : villageList) {
                    String villageCode = item.getVillageCode();
                    if (villageCode == null || villageCode.isEmpty()) {
                        continue;
                    }
                    try {
                        SasVillage saved = syncSingleVillage(villageUrl, key, villageCode, mapper);
                        if (saved != null) {
                            totalSynced++;
                        }
                    } catch (Exception e) {
                        log.error("同步地块 {} 失败：{}", villageCode, e.getMessage(), e);
                    }
                }

                if (villageList.size() < PAGE_SIZE) {
                    break;
                }
                page++;
            }
        } catch (Exception e) {
            log.error("同步地块信息失败：{}", e.getMessage(), e);
        }
        log.info("地块信息同步完成，共同步 {} 条", totalSynced);
        return totalSynced;
    }

    /**
     * 同步单个地块详情（调用 getInfo）
     */
    private SasVillage syncSingleVillage(String villageUrl, String key, String villageCode, ObjectMapper mapper)
            throws Exception {
        Map<String, Object> params = new HashMap<>();
        params.put("villageCode", villageCode);
        JsonRpcRequest getInfoJson = new JsonRpcRequest("getInfo", params, null);

        Map<String, Object> requestBody = new HashMap<>();
        requestBody.put("key", key);
        requestBody.put("json", JSONUtil.toJsonStr(getInfoJson));
        log.debug("请求AG地块详情：{}", JSONUtil.toJsonStr(requestBody));

        String responseStr = postWithRetry(villageUrl, requestBody);
        JsonNode jsonNode = mapper.readTree(responseStr);
        VillageInfoVO villageInfoVO = mapper.treeToValue(jsonNode, VillageInfoVO.class);

        if (villageInfoVO == null || villageInfoVO.getResult() == null
                || villageInfoVO.getResult().getVillage() == null) {
            log.warn("地块 {} 详情为空", villageCode);
            return null;
        }

        VillageInfoVO.VillageResultInfo village = villageInfoVO.getResult().getVillage();
        VillageInfoVO.CompanyResultInfo company = villageInfoVO.getResult().getCompany();

        SasVillage entity = new SasVillage();
        entity.setVillageCode(villageCode);
        entity.setVillageName(village.getVillageName());
        entity.setAddress(village.getAddress());
        entity.setProvinceCode(village.getProvinceCode());
        entity.setCityCode(village.getCityCode());
        entity.setDistrictCode(village.getDistrictCode());
        entity.setStreetCode(village.getStreetCode());
        entity.setRoadCode(village.getRoadCode());
        entity.setPoliceStationCode(village.getPoliceStationCode());
        entity.setGisId(village.getGisAsString());
        entity.setAreaGis(village.getGisAsString());

        if (company != null) {
            entity.setCompanyCode(company.getCompanyCode());
            entity.setCompanyName(company.getCompanyName());
        }

        LocalDateTime now = LocalDateTime.now();
        SasVillage exist = sasVillageMapper.selectById(villageCode);
        if (exist != null) {
            entity.setCreateTime(exist.getCreateTime());
            entity.setUpdateTime(now);
            entity.setPicUrlId(exist.getPicUrlId());
            entity.setGisAreaId(exist.getGisAreaId());
            entity.setIsDefault(exist.getIsDefault());
            sasVillageMapper.updateById(entity);
        } else {
            entity.setCreateTime(now);
            entity.setUpdateTime(now);
            entity.setIsDefault(false);
            sasVillageMapper.insert(entity);
        }
        return entity;
    }

    private String buildAgboxBaseUrl(String host, String port) {
        if (host == null || host.isEmpty()) {
            return "";
        }
        if (host.startsWith("http://") || host.startsWith("https://")) {
            return host;
        }
        return "http://" + host;
    }

    /**
     * 从地块信息表动态获取用于同步的 villageCode：优先取默认地块（is_default=true），若无则取第一条。
     */
    private String getVillageCodeForSync() {
        LambdaQueryWrapper<SasVillage> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SasVillage::getIsDefault, true).last("limit 1");
        List<SasVillage> list = sasVillageMapper.selectList(wrapper);
        if (list != null && !list.isEmpty()) {
            return list.get(0).getVillageCode();
        }
        wrapper = new LambdaQueryWrapper<>();
        wrapper.last("limit 1");
        list = sasVillageMapper.selectList(wrapper);
        return (list != null && !list.isEmpty()) ? list.get(0).getVillageCode() : null;
    }

    /**
     * 带超时和重试的 POST 请求，用于应对 AG 接口偶发的连接中断（Unexpected end of file）
     */
    private String postWithRetry(String url, Map<String, Object> requestBody) {
        Exception lastEx = null;
        for (int i = 0; i < MAX_RETRIES; i++) {
            try {
                return HttpRequest.post(url)
                        .form(requestBody)
                        .timeout(HTTP_TIMEOUT_MS)
                        .execute()
                        .body();
            } catch (Exception e) {
                lastEx = e;
                if (i < MAX_RETRIES - 1) {
                    log.warn("AG 接口请求失败，第 {} 次重试：{}", i + 1, e.getMessage());
                    try {
                        Thread.sleep(1000L * (i + 1));
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        throw new RuntimeException("重试被中断", ie);
                    }
                }
            }
        }
        throw new RuntimeException("AG 接口请求失败，重试 " + MAX_RETRIES + " 次后仍失败", lastEx);
    }

    @Override
    public void setVillageDefault(String villageCode) {
        SasVillage village = sasVillageMapper.selectById(villageCode);
        if (village == null) {
            return;
        }
        // 清除原默认
        LambdaQueryWrapper<SasVillage> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SasVillage::getIsDefault, true);
        List<SasVillage> list = sasVillageMapper.selectList(wrapper);
        for (SasVillage v : list) {
            v.setIsDefault(false);
            sasVillageMapper.updateById(v);
        }
        // 设置新的默认
        village.setIsDefault(true);
        sasVillageMapper.updateById(village);
    }

    private PowerListItem fromPersonToPower(SasPerson p) {
        PowerListItem item = new PowerListItem();
        item.setPersonCode(p.getPersonCode());
        item.setName(p.getName());
        item.setCredentialType(p.getCredentialType());
        item.setCredentialNo(p.getCredentialNo());
        item.setPhone1(p.getPhone1());
        item.setPhone2(p.getPhone2());
        item.setPhone3(p.getPhone3());
        item.setPowerCode(p.getPowerCode());
        item.setPowerName(null);
        item.setSecurityCardNo(p.getSecurityCardNo());
        item.setSecurityDutiesCode(p.getSecurityDutiesCode() == null ? null : String.valueOf(p.getSecurityDutiesCode()));
        item.setUpdateTime(p.getUpdateTime());
        return item;
    }

    /**
     * AG Power 数据转列表项
     */
    private PowerListItem fromPowerToListItem(PowerVo.Power power, boolean maskIdCard) {
        PowerListItem item = new PowerListItem();
        item.setPersonCode(power.getPersonCode());
        item.setName(power.getName());
        item.setCredentialType(power.getCredentialType());
        if (power.getCredentialType() != null) {
            SasCredentialTypeCode ct = sasCredentialTypeCodeMapper.selectById(power.getCredentialType());
            if (ct != null) {
                item.setCredentialTypeName(ct.getName());
            }
        }
        if (power.getCredentialNo() != null) {
            String no = power.getCredentialNo();
            if (maskIdCard && no.length() > 8) {
                item.setCredentialNo(IdcardUtil.hide(no, 4, no.length() - 4));
            } else {
                item.setCredentialNo(no);
            }
        }
        // 电话列表拆分到 phone1/2/3
        if (power.getPhone() != null && !power.getPhone().isEmpty()) {
            if (power.getPhone().size() >= 1) item.setPhone1(power.getPhone().get(0));
            if (power.getPhone().size() >= 2) item.setPhone2(power.getPhone().get(1));
            if (power.getPhone().size() >= 3) item.setPhone3(power.getPhone().get(2));
        }
        item.setPowerCode(power.getPowerCode());
        item.setPowerName(null);
        item.setSecurityCardNo(power.getSecurityCardNo());
        item.setSecurityDutiesCode(power.getSecurityDutiesCode() == null ? null : String.valueOf(power.getSecurityDutiesCode()));
        if (power.getUpdateTime() != null && !power.getUpdateTime().isEmpty()) {
            try {
                item.setUpdateTime(LocalDateTime.parse(
                        power.getUpdateTime(),
                        java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            } catch (Exception ignored) {
                // ignore parse error, keep null
            }
        }
        return item;
    }

    /**
     * 本地人员表分页作为回退策略
     */
    private CommonPage<PowerListItem> localPowerPage(OneStandardSixRealitiesPageRequest request) {
        IPage<SasPerson> page = new Page<>(request.getCurrent(), request.getSize());
        LambdaQueryWrapper<SasPerson> wrapper = new LambdaQueryWrapper<>();
        // 这里简单按 power_code 是否非空作为实有力量筛选
        wrapper.isNotNull(SasPerson::getPowerCode);
        page = sasPersonMapper.selectPage(page, wrapper);
        List<SasPerson> records = page.getRecords();
        if (records == null) {
            records = Collections.emptyList();
        }
        List<PowerListItem> list = records.stream()
                .map(this::fromPersonToPower)
                .collect(Collectors.toList());
        return new CommonPage<>(list, page.getTotal(), page.getCurrent(), page.getSize());
    }

    private AreaListItem fromBuildingToArea(SasBuilding b) {
        AreaListItem item = new AreaListItem();
        item.setBuildingCode(b.getBuildingCode());
        item.setBuildingNo(b.getBuildingNo());
        item.setFloorTotal(b.getFloorTotal());
        item.setHouseTotal(b.getHouseTotal());
        item.setNote(b.getNote());
        item.setGisId(b.getGisId());
        item.setVillageCode(b.getVillageCode());
        item.setPlaceType(b.getPlaceType());
        item.setCreateTime(b.getCreateTime());
        item.setUpdateTime(b.getUpdateTime());
        return item;
    }

    private HouseListItem fromHouseToHouseItem(SasHouse h) {
        HouseListItem item = new HouseListItem();
        item.setHouseCode(h.getHouseCode());
        item.setHouseNo(h.getHouseNo());
        item.setHouseName(h.getHouseName());
        item.setFloor(h.getFloor());
        item.setHouseLabelCode(h.getHouseLabelCode());
        item.setHouseLabel(h.getHouseLabel());
        item.setHousePurposeCode(h.getHousePurposeCode());
        item.setHousePurpose(h.getHousePurpose());
        item.setNote(h.getNote());
        item.setIsPublic(h.getIsPublic());
        item.setIsAction(h.getIsAction());
        item.setSystemTypeCode(h.getSystesasTypeCode());
        item.setBuildingCode(h.getBuildingCode());
        item.setGisId(h.getGisId());
        item.setPersonNumber(h.getPersonNumber());
        item.setHouseArea(h.getHouseArea());
        item.setVillageCode(h.getVillageCode());
        item.setTags(h.getTags());
        item.setCreateTime(h.getCreateTime());
        item.setUpdateTime(h.getUpdateTime());
        return item;
    }

    private VillageListItem fromVillageToVillageItem(SasVillage v,
                                                     Map<String, String> areaNameMap,
                                                     Map<String, String> policeStationNameMap,
                                                     Map<String, SasGis> gisMap) {
        VillageListItem item = new VillageListItem();
        item.setVillageCode(v.getVillageCode());
        item.setVillageName(v.getVillageName());
        item.setAddress(v.getAddress());
        item.setProvinceCode(v.getProvinceCode());
        item.setCityCode(v.getCityCode());
        item.setDistrictCode(v.getDistrictCode());
        item.setStreetCode(v.getStreetCode());
        item.setRoadCode(v.getRoadCode());
        item.setPoliceStationCode(v.getPoliceStationCode());
        item.setProvinceName(v.getProvinceCode() != null ? areaNameMap.get(String.valueOf(v.getProvinceCode())) : null);
        item.setCityName(v.getCityCode() != null ? areaNameMap.get(String.valueOf(v.getCityCode())) : null);
        item.setDistrictName(v.getDistrictCode() != null ? areaNameMap.get(String.valueOf(v.getDistrictCode())) : null);
        item.setStreetName(v.getStreetCode() != null ? areaNameMap.get(String.valueOf(v.getStreetCode())) : null);
        item.setRoadName(v.getRoadCode() != null ? areaNameMap.get(v.getRoadCode()) : null);
        item.setPoliceStationName(v.getPoliceStationCode() != null ? policeStationNameMap.get(v.getPoliceStationCode()) : null);
        // 经纬度、高度：从 sas_gis 获取，无数据时默认 0
        SasGis gis = v.getGisId() != null ? gisMap.get(v.getGisId()) : null;
        item.setLatitude(gis != null && gis.getLat() != null ? gis.getLat().doubleValue() : 0.0);
        item.setLongitude(gis != null && gis.getLon() != null ? gis.getLon().doubleValue() : 0.0);
        item.setAltitude(gis != null && gis.getAlt() != null ? gis.getAlt().doubleValue() : 0.0);
        item.setCompanyCode(v.getCompanyCode());
        item.setCompanyName(v.getCompanyName());
        item.setPicUrlId(v.getPicUrlId());
        item.setGisId(v.getGisId());
        item.setGisAreaId(v.getGisAreaId());
        item.setIsDefault(v.getIsDefault());
        item.setCreateTime(v.getCreateTime());
        item.setUpdateTime(v.getUpdateTime());
        return item;
    }
}

