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

import com.zcloud.personnel.positioning.dto.PositionPersonPageQry;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.springframework.dao.DataAccessException;
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;

    boolean hasPersonnelMappings() {
        try {
            Integer count = jdbcTemplate.queryForObject(
                    "SELECT COUNT(1) FROM personnel_match "
                            + "WHERE (delete_enum IS NULL OR delete_enum = 'FALSE')",
                    Integer.class);
            return count != null && count > 0;
        } catch (DataAccessException ignored) {
            // Keep the pre-migration FindS fallback available during rolling deployment.
            return false;
        }
    }

    List<PersonMapping> findMatchedPersonnel(PositionPersonPageQry query) {
        StringBuilder sql = new StringBuilder(
                "SELECT pm.finds_staff_no, pm.finds_staff_no_type, pm.finds_id_card_no, "
                        + "pm.finds_staff_name, pm.finds_corpinfo_id, pm.finds_corpinfo_name, "
                        + "pm.finds_department_name, pm.finds_terminal_no, pm.location_status, "
                        + "pm.finds_device_status, "
                        + "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, ci.code AS credit_code, ci.port_area, "
                        + "u.department_id, d.name AS department_name "
                        + "FROM personnel_match pm JOIN user_scope_v u ON u.id = pm.local_user_id "
                        + "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 pm.match_status = 'MATCHED' "
                        + "AND (pm.delete_enum IS NULL OR pm.delete_enum = 'FALSE') "
                        + "AND (u.delete_enum IS NULL OR u.delete_enum = 'FALSE')");
        List<Object> args = new ArrayList<>();
        if (query != null && query.getUserId() != null) {
            sql.append(" AND u.id = ?");
            args.add(query.getUserId());
        }
        if (query != null && StringUtils.hasText(query.getStaffName())) {
            sql.append(" AND u.name LIKE ?");
            args.add("%" + query.getStaffName().trim() + "%");
        }
        if (query != null && query.getCorpinfoId() != null) {
            sql.append(" AND CAST(u.corpinfo_id AS CHAR) = ?");
            args.add(String.valueOf(query.getCorpinfoId()));
        }
        if (query != null && StringUtils.hasText(query.getCorpinfoName())) {
            sql.append(" AND ci.corp_name LIKE ?");
            args.add("%" + query.getCorpinfoName().trim() + "%");
        }
        if (query != null && query.getDepartmentId() != null) {
            sql.append(" AND u.department_id = ?");
            args.add(query.getDepartmentId());
        }
        if (query != null && StringUtils.hasText(query.getDepartmentName())) {
            sql.append(" AND d.name LIKE ?");
            args.add("%" + query.getDepartmentName().trim() + "%");
        }
        sql.append(" ORDER BY u.id DESC, pm.finds_staff_no");
        return jdbcTemplate.query(sql.toString(), args.toArray(), (rs, rowNum) -> {
            LocalPerson local = new LocalPerson();
            local.setUserId(nullableLong(rs.getLong("user_id"), rs.wasNull()));
            local.setLocalStaffNo(rs.getString("local_staff_no"));
            local.setStaffName(rs.getString("staff_name"));
            local.setMobileNo(normalize(rs.getString("mobile_no")));
            local.setIdCardNo(normalize(rs.getString("id_card_no")));
            local.setCorpinfoId(rs.getString("corpinfo_id"));
            local.setCorpinfoName(rs.getString("corpinfo_name"));
            local.setCreditCode(rs.getString("credit_code"));
            int portArea = rs.getInt("port_area");
            local.setPortArea(rs.wasNull() ? null : portArea);
            local.setDepartmentId(nullableLong(rs.getLong("department_id"), rs.wasNull()));
            local.setDepartmentName(rs.getString("department_name"));

            PersonMapping mapping = new PersonMapping();
            mapping.setFindsStaffNo(normalize(rs.getString("finds_staff_no")));
            mapping.setFindsStaffNoType(normalize(rs.getString("finds_staff_no_type")));
            mapping.setFindsIdCardNo(normalize(rs.getString("finds_id_card_no")));
            mapping.setFindsStaffName(rs.getString("finds_staff_name"));
            mapping.setFindsCorpinfoId(rs.getString("finds_corpinfo_id"));
            mapping.setFindsCorpinfoName(rs.getString("finds_corpinfo_name"));
            mapping.setFindsDepartmentName(rs.getString("finds_department_name"));
            mapping.setFindsTerminalNo(normalize(rs.getString("finds_terminal_no")));
            mapping.setLocationStatus(normalize(rs.getString("location_status")));
            mapping.setFindsDeviceStatus(normalize(rs.getString("finds_device_status")));
            mapping.setLocal(local);
            return mapping;
        });
    }

    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, ci.code AS credit_code, ci.port_area, "
                    + "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(rs.getString("corpinfo_id"));
                person.setCorpinfoName(rs.getString("corpinfo_name"));
                person.setCreditCode(rs.getString("credit_code"));
                int portArea = rs.getInt("port_area");
                person.setPortArea(rs.wasNull() ? null : portArea);
                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, ci.code AS credit_code, ci.port_area, "
                    + "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(rs.getString("corpinfo_id"));
                person.setCorpinfoName(rs.getString("corpinfo_name"));
                person.setCreditCode(rs.getString("credit_code"));
                int portArea = rs.getInt("port_area");
                person.setPortArea(rs.wasNull() ? null : portArea);
                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, String> findTerminalNosByIdCardNos(Set<String> idCardNos) {
        Map<String, String> result = new LinkedHashMap<>();
        for (List<String> batch : batches(idCardNos)) {
            String placeholders = String.join(",", Collections.nCopies(batch.size(), "?"));
            String sql = "SELECT COALESCE(NULLIF(TRIM(tb.id_card_no), ''), "
                    + "NULLIF(TRIM(t.bind_id_card_no), '')) AS id_card_no, t.terminal_no "
                    + "FROM terminal t LEFT JOIN terminal_bind tb ON tb.id = ("
                    + "SELECT tb2.id FROM terminal_bind tb2 WHERE tb2.terminal_no = t.terminal_no "
                    + "AND tb2.bind_status = 'BOUND' "
                    + "AND (tb2.delete_enum IS NULL OR tb2.delete_enum = 'FALSE') "
                    + "ORDER BY tb2.bind_time DESC, tb2.id DESC LIMIT 1) "
                    + "WHERE (t.delete_enum IS NULL OR t.delete_enum = 'FALSE') "
                    + "AND COALESCE(NULLIF(TRIM(tb.id_card_no), ''), "
                    + "NULLIF(TRIM(t.bind_id_card_no), '')) IN (" + placeholders + ") "
                    + "ORDER BY id_card_no, t.update_time DESC, t.id DESC";
            jdbcTemplate.query(sql, batch.toArray(), (rs, rowNum) -> {
                String idCardNo = normalize(rs.getString("id_card_no"));
                String terminalNo = normalize(rs.getString("terminal_no"));
                return new TerminalBinding(idCardNo, terminalNo);
            }).forEach(binding -> {
                if (StringUtils.hasText(binding.idCardNo) && StringUtils.hasText(binding.terminalNo)) {
                    result.putIfAbsent(binding.idCardNo, binding.terminalNo);
                }
            });
        }
        return result;
    }

    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, device_status, 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"));
                terminal.setDeviceStatus(normalize(rs.getString("device_status")));
                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 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;
    }

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

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

    private static class TerminalBinding {
        private final String idCardNo;
        private final String terminalNo;

        private TerminalBinding(String idCardNo, String terminalNo) {
            this.idCardNo = idCardNo;
            this.terminalNo = terminalNo;
        }
    }
}
