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

import com.zcloud.personnel.positioning.dto.PositionPersonPageQry;
import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelLocalPersonDO;
import com.zcloud.personnel.positioning.persistence.dataobject.PersonnelMappingSnapshotDO;
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalBindingSnapshotDO;
import com.zcloud.personnel.positioning.persistence.dataobject.TerminalLocationSnapshotDO;
import com.zcloud.personnel.positioning.persistence.repository.PersonnelMatchRepository;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Component
@RequiredArgsConstructor
class PositionPersonLocalLookupService {
    private static final int BATCH_SIZE = 500;

    private final PersonnelMatchRepository personnelMatchRepository;

    boolean hasPersonnelMappings() {
        try {
            return personnelMatchRepository.countAll() > 0;
        } catch (RuntimeException ignored) {
            // Keep the pre-migration FindS fallback available during rolling deployment.
            return false;
        }
    }

    List<PersonMapping> findMatchedPersonnel(PositionPersonPageQry query) {
        Map<String, Object> params = new HashMap<>();
        if (query != null) {
            params.put("userId", query.getUserId());
            params.put("staffName", normalize(query.getStaffName()));
            params.put("corpinfoId", query.getCorpinfoId() == null ? null : String.valueOf(query.getCorpinfoId()));
            params.put("corpinfoName", normalize(query.getCorpinfoName()));
            params.put("departmentId", query.getDepartmentId());
            params.put("departmentName", normalize(query.getDepartmentName()));
        }
        List<PersonMapping> result = new ArrayList<>();
        for (PersonnelMappingSnapshotDO row : personnelMatchRepository.listMatched(params)) {
            PersonMapping mapping = new PersonMapping();
            mapping.setFindsStaffNo(normalize(row.getFindsStaffNo()));
            mapping.setFindsStaffNoType(normalize(row.getFindsStaffNoType()));
            mapping.setFindsIdCardNo(normalize(row.getFindsIdCardNo()));
            mapping.setFindsStaffName(row.getFindsStaffName());
            mapping.setFindsCorpinfoId(row.getFindsCorpinfoId());
            mapping.setFindsCorpinfoName(row.getFindsCorpinfoName());
            mapping.setFindsDepartmentName(row.getFindsDepartmentName());
            mapping.setFindsTerminalNo(normalize(row.getFindsTerminalNo()));
            mapping.setLocationStatus(normalize(row.getLocationStatus()));
            mapping.setFindsDeviceStatus(normalize(row.getFindsDeviceStatus()));
            mapping.setLocal(toLocalPerson(row));
            result.add(mapping);
        }
        return result;
    }

    Map<String, LocalPerson> findUniquePeopleByPhones(Set<String> phones) {
        Map<String, List<LocalPerson>> grouped = new LinkedHashMap<>();
        for (List<String> batch : batches(phones)) {
            personnelMatchRepository.listPeopleByPhones(batch).stream()
                    .map(this::toLocalPerson)
                    .forEach(person -> grouped.computeIfAbsent(person.getMobileNo(), key -> new ArrayList<>()).add(person));
        }

        Map<String, LocalPerson> unique = new HashMap<>();
        grouped.forEach((phone, people) -> {
            if (StringUtils.hasText(phone) && people.size() == 1) {
                unique.put(phone, people.get(0));
            }
        });
        return unique;
    }

    List<LocalPerson> findAllPeople() {
        List<LocalPerson> result = new ArrayList<>();
        for (PersonnelLocalPersonDO row : personnelMatchRepository.listAllPeople()) {
            result.add(toLocalPerson(row));
        }
        return result;
    }

    Map<String, LocalPerson> findUniquePeopleByTerminalNos(Set<String> terminalNos) {
        Map<String, List<LocalPerson>> grouped = new LinkedHashMap<>();
        for (List<String> batch : batches(terminalNos)) {
            for (PersonnelLocalPersonDO row : personnelMatchRepository.listPeopleByTerminalNos(batch)) {
                grouped.computeIfAbsent(row.getTerminalNo(), key -> new ArrayList<>()).add(toLocalPerson(row));
            }
        }

        Map<String, LocalPerson> unique = new HashMap<>();
        grouped.forEach((terminalNo, people) -> {
            if (StringUtils.hasText(terminalNo) && people.size() == 1) {
                unique.put(terminalNo, people.get(0));
            }
        });
        return unique;
    }

    Map<String, String> findTerminalNosByIdCardNos(Set<String> idCardNos) {
        Map<String, String> result = new LinkedHashMap<>();
        for (List<String> batch : batches(idCardNos)) {
            for (TerminalBindingSnapshotDO binding : personnelMatchRepository.listTerminalBindingsByIdCardNos(batch)) {
                String idCardNo = normalize(binding.getIdCardNo());
                String terminalNo = normalize(binding.getTerminalNo());
                if (StringUtils.hasText(idCardNo) && StringUtils.hasText(terminalNo)) {
                    result.putIfAbsent(idCardNo, terminalNo);
                }
            }
        }
        return result;
    }

    Map<String, TerminalSnapshot> findTerminals(Set<String> terminalNos) {
        Map<String, TerminalSnapshot> result = new LinkedHashMap<>();
        for (List<String> batch : batches(terminalNos)) {
            for (TerminalLocationSnapshotDO row : personnelMatchRepository.listTerminals(batch)) {
                TerminalSnapshot terminal = new TerminalSnapshot();
                terminal.setTerminalNo(row.getTerminalNo());
                terminal.setDeviceType(row.getDeviceType());
                terminal.setDeviceStatus(normalize(row.getDeviceStatus()));
                terminal.setLastLocationTime(row.getLastLocationTime() == null ? null
                        : row.getLastLocationTime().atZone(java.time.ZoneId.systemDefault()).toInstant().toEpochMilli());
                terminal.setLastLocationName(row.getLastLocationName());
                result.putIfAbsent(terminal.getTerminalNo(), terminal);
            }
        }
        return result;
    }

    private LocalPerson toLocalPerson(PersonnelLocalPersonDO row) {
        LocalPerson person = new LocalPerson();
        person.setUserId(row.getUserId());
        person.setLocalStaffNo(row.getLocalStaffNo());
        person.setStaffName(row.getStaffName());
        person.setMobileNo(normalize(row.getMobileNo()));
        person.setIdCardNo(normalize(row.getIdCardNo()));
        person.setCorpinfoId(row.getCorpinfoId());
        person.setCorpinfoName(row.getCorpinfoName());
        person.setCreditCode(row.getCreditCode());
        person.setPortArea(row.getPortArea());
        person.setDepartmentId(row.getDepartmentId());
        person.setDepartmentName(row.getDepartmentName());
        return person;
    }

    private List<List<String>> batches(Set<String> values) {
        List<String> normalized = new ArrayList<>();
        if (values != null) {
            for (String value : values) {
                String item = normalize(value);
                if (StringUtils.hasText(item)) {
                    normalized.add(item);
                }
            }
        }
        List<List<String>> result = new ArrayList<>();
        for (int from = 0; from < normalized.size(); from += BATCH_SIZE) {
            result.add(normalized.subList(from, Math.min(from + BATCH_SIZE, normalized.size())));
        }
        return result;
    }

    private String normalize(String value) {
        return value == null ? null : value.trim();
    }

    @Data
    static class LocalPerson {
        private Long userId;
        private String localStaffNo;
        private String staffName;
        private String mobileNo;
        private String idCardNo;
        private String corpinfoId;
        private String corpinfoName;
        private String creditCode;
        private Integer portArea;
        private Long departmentId;
        private String departmentName;
    }

    @Data
    static class TerminalSnapshot {
        private String terminalNo;
        private String deviceType;
        private String deviceStatus;
        private Long lastLocationTime;
        private String lastLocationName;
    }

    @Data
    static class PersonMapping {
        private String findsStaffNo;
        private String findsStaffNoType;
        private String findsIdCardNo;
        private String findsStaffName;
        private String findsCorpinfoId;
        private String findsCorpinfoName;
        private String findsDepartmentName;
        private String findsTerminalNo;
        private String locationStatus;
        private String findsDeviceStatus;
        private LocalPerson local;
    }

}
