package com.zcloud.personnel.positioning.command.query;

import com.zcloud.personnel.positioning.command.convertor.AreaCoConvertor;
import com.zcloud.personnel.positioning.dto.AreaPageQry;
import com.zcloud.personnel.positioning.dto.clientobject.AreaCO;
import com.zcloud.personnel.positioning.dto.clientobject.AreaPersonStatCO;
import com.zcloud.personnel.positioning.dto.clientobject.BiCompanyOnlinePersonCO;
import com.zcloud.personnel.positioning.dto.clientobject.OptionCO;
import com.zcloud.personnel.positioning.persistence.dataobject.AreaDO;
import com.zcloud.personnel.positioning.persistence.repository.AreaRepository;
import com.alibaba.cola.dto.PageResponse;
import com.alibaba.cola.exception.BizException;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jjb.saas.framework.auth.model.SSOUser;
import com.jjb.saas.framework.auth.utils.AuthContext;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * web-app
 *
 * @Author fangjiakai
 * @Date 2026-07-07
 */
@Component
@AllArgsConstructor
public class AreaQueryExe {
    private final AreaRepository areaRepository;
    private final AreaCoConvertor areaCoConvertor;
    private final BiPersonLocationProvider biPersonLocationProvider;
    private final ObjectMapper objectMapper;

    public PageResponse<AreaCO> execute(AreaPageQry qry) {
        AreaPageQry query = qry == null ? new AreaPageQry() : qry;
        Map<String, Object> params = query.toHashMap();
        fillCorpinfo(params);
        PageResponse<AreaDO> pageResponse = areaRepository.listPage(params);
        List<AreaCO> data = areaCoConvertor.converDOsToCOs(pageResponse.getData());
        return PageResponse.of(data, pageResponse.getTotalCount(), pageResponse.getPageSize(), pageResponse.getPageIndex());
    }

    public List<AreaCO> listAll() {
        QueryWrapper<AreaDO> queryWrapper = new QueryWrapper<>();
        applyCorpinfo(queryWrapper, null);
        queryWrapper.orderByDesc("create_time");
        return areaCoConvertor.converDOsToCOs(areaRepository.list(queryWrapper));
    }

    public List<OptionCO> listOptions(String corpinfoId, String keyword) {
        QueryWrapper<AreaDO> queryWrapper = new QueryWrapper<>();
        queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
        queryWrapper.and(wrapper -> wrapper.isNull("status").or().eq("status", "ENABLED"));
        queryWrapper.in("area_type", "COMPANY_AREA", "WORK_AREA", "BUFFER");
        applyCorpinfo(queryWrapper, corpinfoId);
        if (StringUtils.hasText(keyword)) {
            queryWrapper.like("area_name", keyword.trim());
        }
        queryWrapper.orderByAsc("sort_no").orderByAsc("area_name");

        List<AreaDO> areas = areaRepository.list(queryWrapper);
        List<OptionCO> options = new ArrayList<>(areas.size());
        for (AreaDO area : areas) {
            options.add(new OptionCO(area.getAreaName(), String.valueOf(area.getId())));
        }
        return options;
    }

    public List<AreaPersonStatCO> listPersonStat(Long corpinfoId) {
        if (corpinfoId == null) {
            throw new BizException("企业id不能为空");
        }
        String companyId = String.valueOf(corpinfoId);
        List<AreaStatAccumulator> areas = listCompanyAreas(companyId);
        List<BiCompanyOnlinePersonCO> personList =
                biPersonLocationProvider.listCompanyOnlinePersons(companyId, null, null);
        for (BiCompanyOnlinePersonCO person : personList) {
            countPerson(areas, person);
        }
        List<AreaPersonStatCO> result = new ArrayList<>(areas.size());
        for (AreaStatAccumulator area : areas) {
            result.add(area.toCO());
        }
        return result;
    }

    public AreaCO queryById(Long id) {
        QueryWrapper<AreaDO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id", id);
        applyCorpinfo(queryWrapper, null);
        AreaDO dataObject = areaRepository.getOne(queryWrapper, false);
        return areaCoConvertor.converDOToCO(dataObject);
    }

    private List<AreaStatAccumulator> listCompanyAreas(String corpinfoId) {
        QueryWrapper<AreaDO> queryWrapper = new QueryWrapper<>();
        queryWrapper.and(wrapper -> wrapper.isNull("delete_enum").or().eq("delete_enum", "FALSE"));
        queryWrapper.and(wrapper -> wrapper.isNull("status").or().eq("status", "ENABLED"));
        queryWrapper.eq("corpinfo_id", corpinfoId);
        queryWrapper.orderByAsc("sort_no").orderByAsc("area_name").orderByDesc("create_time");
        List<AreaDO> rows = areaRepository.list(queryWrapper);
        List<AreaStatAccumulator> result = new ArrayList<>(rows.size());
        for (AreaDO row : rows) {
            result.add(new AreaStatAccumulator(row, parsePolygon(row.getPolygonJson())));
        }
        return result;
    }

    private void countPerson(List<AreaStatAccumulator> areas, BiCompanyOnlinePersonCO person) {
        if (person == null || person.getLon() == null || person.getLat() == null) {
            return;
        }
        for (AreaStatAccumulator area : areas) {
            if (pointInPolygon(person.getLon(), person.getLat(), area.points)) {
                area.add(person);
                return;
            }
        }
    }

    private List<BigDecimal[]> parsePolygon(String polygonJson) {
        List<BigDecimal[]> points = new ArrayList<>();
        if (!StringUtils.hasText(polygonJson)) {
            return points;
        }
        try {
            collectPoints(objectMapper.readTree(polygonJson), points);
        } catch (IOException e) {
            throw new BizException("区域边界坐标JSON格式错误");
        }
        return points;
    }

    private void collectPoints(JsonNode node, List<BigDecimal[]> points) {
        if (node == null || node.isNull() || node.isMissingNode()) {
            return;
        }
        if (node.isObject()) {
            node.elements().forEachRemaining(child -> collectPoints(child, points));
            return;
        }
        if (!node.isArray()) {
            return;
        }
        if (node.size() >= 2 && node.get(0).isNumber() && node.get(1).isNumber()) {
            points.add(new BigDecimal[]{node.get(0).decimalValue(), node.get(1).decimalValue()});
            return;
        }
        for (JsonNode child : node) {
            collectPoints(child, points);
        }
    }

    private boolean pointInPolygon(BigDecimal lon, BigDecimal lat, List<BigDecimal[]> points) {
        if (lon == null || lat == null || points == null || points.size() < 3) {
            return false;
        }
        boolean inside = false;
        double x = lon.doubleValue();
        double y = lat.doubleValue();
        for (int i = 0, j = points.size() - 1; i < points.size(); j = i++) {
            double xi = points.get(i)[0].doubleValue();
            double yi = points.get(i)[1].doubleValue();
            double xj = points.get(j)[0].doubleValue();
            double yj = points.get(j)[1].doubleValue();
            if ((yi > y) != (yj > y)
                    && x < (xj - xi) * (y - yi) / (yj - yi) + xi) {
                inside = !inside;
            }
        }
        return inside;
    }

    private void fillCorpinfo(Map<String, Object> params) {
        if (params.get("eqCorpinfoId") != null || StringUtils.hasText((String) params.get("likeCorpinfoName"))) {
            return;
        }
        SSOUser ssoUser = AuthContext.getCurrentUser();
        if (ssoUser != null && ssoUser.getCompanyId() != null) {
            params.put("eqCorpinfoId", String.valueOf(ssoUser.getCompanyId()));
        }
    }

    private void applyCorpinfo(QueryWrapper<AreaDO> queryWrapper, String corpinfoId) {
        if (corpinfoId != null) {
            queryWrapper.eq("corpinfo_id", corpinfoId);
            return;
        }
        SSOUser ssoUser = AuthContext.getCurrentUser();
        if (ssoUser != null && ssoUser.getCompanyId() != null) {
            queryWrapper.eq("corpinfo_id", String.valueOf(ssoUser.getCompanyId()));
        }
    }

    private static class AreaStatAccumulator {
        private final AreaDO area;
        private final List<BigDecimal[]> points;
        private int unitCount;
        private int relatedCount;

        private AreaStatAccumulator(AreaDO area, List<BigDecimal[]> points) {
            this.area = area;
            this.points = points;
        }

        private void add(BiCompanyOnlinePersonCO person) {
            if (Boolean.TRUE.equals(person.getInternalEmployee())) {
                unitCount++;
            } else {
                relatedCount++;
            }
        }

        private AreaPersonStatCO toCO() {
            AreaPersonStatCO co = new AreaPersonStatCO();
            co.setAreaId(area.getId());
            co.setAreaCode(area.getAreaCode());
            co.setAreaName(area.getAreaName());
            co.setAreaType(area.getAreaType());
            co.setCorpinfoId(area.getCorpinfoId());
            co.setCorpinfoName(area.getCorpinfoName());
            co.setCenterLon(area.getCenterLon());
            co.setCenterLat(area.getCenterLat());
            co.setPolygonJson(area.getPolygonJson());
            co.setUnitCount(unitCount);
            co.setRelatedCount(relatedCount);
            co.setTotalCount(unitCount + relatedCount);
            return co;
        }
    }
}
