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

import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
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 JdbcTemplate jdbcTemplate;

    Map<String, LocalPerson> findUniquePeopleByPhones(Set<String> phones) {
        Map<String, List<LocalPerson>> grouped = new LinkedHashMap<>();
        for (List<String> batch : batches(phones)) {
            String placeholders = String.join(",", Collections.nCopies(batch.size(), "?"));
            String sql = "SELECT u.id AS user_id, u.username AS local_staff_no, u.name AS staff_name, "
                    + "TRIM(u.phone) AS mobile_no, u.user_id_card AS id_card_no, u.corpinfo_id, "
                    + "ci.corp_name AS corpinfo_name, u.department_id, d.name AS department_name "
                    + "FROM user_scope_v u "
                    + "LEFT JOIN corp_info ci ON CAST(ci.corpinfo_id AS CHAR) = CAST(u.corpinfo_id AS CHAR) "
                    + "LEFT JOIN department d ON d.id = u.department_id "
                    + "AND (d.delete_enum IS NULL OR d.delete_enum = 'FALSE') "
                    + "WHERE (u.delete_enum IS NULL OR u.delete_enum = 'FALSE') "
                    + "AND TRIM(u.phone) IN (" + placeholders + ") "
                    + "ORDER BY u.id DESC";
            jdbcTemplate.query(sql, batch.toArray(), (rs, rowNum) -> {
                LocalPerson person = new LocalPerson();
                person.setUserId(nullableLong(rs.getLong("user_id"), rs.wasNull()));
                person.setLocalStaffNo(rs.getString("local_staff_no"));
                person.setStaffName(rs.getString("staff_name"));
                person.setMobileNo(normalize(rs.getString("mobile_no")));
                person.setIdCardNo(rs.getString("id_card_no"));
                person.setCorpinfoId(nullableLong(rs.getLong("corpinfo_id"), rs.wasNull()));
                person.setCorpinfoName(rs.getString("corpinfo_name"));
                person.setDepartmentId(nullableLong(rs.getLong("department_id"), rs.wasNull()));
                person.setDepartmentName(rs.getString("department_name"));
                return person;
            }).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;
    }

    Map<String, LocalPerson> findUniquePeopleByTerminalNos(Set<String> terminalNos) {
        Map<String, List<LocalPerson>> grouped = new LinkedHashMap<>();
        for (List<String> batch : batches(terminalNos)) {
            String placeholders = String.join(",", Collections.nCopies(batch.size(), "?"));
            String sql = "SELECT DISTINCT t.terminal_no, u.id AS user_id, u.username AS local_staff_no, "
                    + "u.name AS staff_name, TRIM(u.phone) AS mobile_no, u.user_id_card AS id_card_no, "
                    + "u.corpinfo_id, ci.corp_name AS corpinfo_name, u.department_id, d.name AS department_name "
                    + "FROM terminal t LEFT JOIN terminal_bind tb ON tb.terminal_id = t.id "
                    + "AND tb.bind_status = 'BOUND' "
                    + "AND (tb.delete_enum IS NULL OR tb.delete_enum = 'FALSE') "
                    + "JOIN user_scope_v u ON u.user_id_card = COALESCE(NULLIF(tb.id_card_no, ''), "
                    + "NULLIF(t.bind_id_card_no, '')) "
                    + "LEFT JOIN corp_info ci ON CAST(ci.corpinfo_id AS CHAR) = CAST(u.corpinfo_id AS CHAR) "
                    + "LEFT JOIN department d ON d.id = u.department_id "
                    + "AND (d.delete_enum IS NULL OR d.delete_enum = 'FALSE') "
                    + "WHERE (t.delete_enum IS NULL OR t.delete_enum = 'FALSE') "
                    + "AND (u.delete_enum IS NULL OR u.delete_enum = 'FALSE') "
                    + "AND t.terminal_no IN (" + placeholders + ") ORDER BY t.terminal_no, u.id DESC";
            jdbcTemplate.query(sql, batch.toArray(), (rs, rowNum) -> {
                LocalPerson person = new LocalPerson();
                person.setUserId(nullableLong(rs.getLong("user_id"), rs.wasNull()));
                person.setLocalStaffNo(rs.getString("local_staff_no"));
                person.setStaffName(rs.getString("staff_name"));
                person.setMobileNo(normalize(rs.getString("mobile_no")));
                person.setIdCardNo(rs.getString("id_card_no"));
                person.setCorpinfoId(nullableLong(rs.getLong("corpinfo_id"), rs.wasNull()));
                person.setCorpinfoName(rs.getString("corpinfo_name"));
                person.setDepartmentId(nullableLong(rs.getLong("department_id"), rs.wasNull()));
                person.setDepartmentName(rs.getString("department_name"));
                return new TerminalPerson(rs.getString("terminal_no"), person);
            }).forEach(row -> grouped.computeIfAbsent(row.terminalNo, key -> new ArrayList<>()).add(row.person));
        }

        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, TerminalSnapshot> findTerminals(Set<String> terminalNos) {
        Map<String, TerminalSnapshot> result = new LinkedHashMap<>();
        for (List<String> batch : batches(terminalNos)) {
            String placeholders = String.join(",", Collections.nCopies(batch.size(), "?"));
            String sql = "SELECT terminal_no, device_type, last_location_time, last_location_name "
                    + "FROM terminal WHERE (delete_enum IS NULL OR delete_enum = 'FALSE') "
                    + "AND terminal_no IN (" + placeholders + ") ORDER BY update_time DESC, id DESC";
            jdbcTemplate.query(sql, batch.toArray(), (rs, rowNum) -> {
                TerminalSnapshot terminal = new TerminalSnapshot();
                terminal.setTerminalNo(rs.getString("terminal_no"));
                terminal.setDeviceType(rs.getString("device_type"));
                Timestamp time = rs.getTimestamp("last_location_time");
                terminal.setLastLocationTime(time == null ? null : time.getTime());
                terminal.setLastLocationName(rs.getString("last_location_name"));
                return terminal;
            }).forEach(terminal -> result.putIfAbsent(terminal.getTerminalNo(), terminal));
        }
        return result;
    }

    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 Long nullableLong(long value, boolean wasNull) {
        return wasNull ? null : value;
    }

    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 Long corpinfoId;
        private String corpinfoName;
        private Long departmentId;
        private String departmentName;
    }

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

    private static class TerminalPerson {
        private final String terminalNo;
        private final LocalPerson person;

        private TerminalPerson(String terminalNo, LocalPerson person) {
            this.terminalNo = terminalNo;
            this.person = person;
        }
    }
}
