import { useState, useEffect, useRef } from 'react';
import api from './lib/api.js';
import CommunityAutocompleteInput from './components/CommunityAutocompleteInput.jsx';
import CommunityBadge from './components/CommunityBadge.jsx';
import {
  IconPlus,
  IconSearch,
  IconEdit,
  IconTrash,
  IconCheck,
  IconX,
  IconDatabase,
  IconRefresh,
  IconUpload,
  IconDownload,
  IconDeviceFloppy,
  IconHash,
  IconClock,
  IconFileText,
  IconFilter,
  IconArrowUp,
  IconArrowDown,
  IconNetwork,
  IconCopy,
  IconCopyCheck
} from '@tabler/icons-react';
import TableSkeleton, { TableEmpty } from './components/TableSkeleton.jsx';
import PageHeaderActions from './components/PageHeaderActions.jsx';
import PageHeader from './components/PageHeader.jsx';
import EmptyState from './components/EmptyState.jsx';
import ConfirmDialog from './components/ConfirmDialog.jsx';
import WsUpdateModal from './components/WsUpdateModal.jsx';
import ConfirmDiffModal from './components/ConfirmDiffModal.jsx';
import HistoryModal from './components/HistoryModal.jsx';
import ErrorAlert from './components/ErrorAlert.jsx';
import { notifyMutationSuccess } from './components/NotifyProvider.jsx';
import ImportModal from './components/ImportModal.jsx';
import QuickAddBar from './components/QuickAddBar.jsx';
import AccordionCard from './components/AccordionCard.jsx';
import SavedFilters from './components/SavedFilters.jsx';
import BulkActionsBar from './components/BulkActionsBar.jsx';
import Tooltip from './components/Tooltip.jsx';
import Pagination from './components/Pagination.jsx';
import AddItemModal from './components/AddItemModal.jsx';
import LastSaved from './components/LastSaved.jsx';
import FormModal from './components/FormModal.jsx';

const API_URL = '/api';

function IPRangesManager() {
  const [items, setItems] = useState([]);
  const [originalItems, setOriginalItems] = useState([]);
  const [etag, setEtag] = useState('');
  const [lastModified, setLastModified] = useState('');
  const [contentLength, setContentLength] = useState(null);
  const [newItem, setNewItem] = useState({ ipRange: '', community: '' });
  const [newInvalid, setNewInvalid] = useState({ ipRange: false, community: false });
  const [error, setError] = useState('');
  const [success, setSuccess] = useState('');
  const [searchTerm, setSearchTerm] = useState('');
  const searchTimer = useRef(null);
  const didInit = useRef(false);
  const [editingValue, setEditingValue] = useState('');
  const [editModalOpen, setEditModalOpen] = useState(false);
  const [itemBeingEdited, setItemBeingEdited] = useState(null);
  const [loading, setLoading] = useState(false);
  const [showDeleteModal, setShowDeleteModal] = useState(false);
  const [itemToDelete, setItemToDelete] = useState(null);
  const [addModalOpen, setAddModalOpen] = useState(false);
  const [currentPage, setCurrentPage] = useState(1);
  const [totalItems, setTotalItems] = useState(0);
  const [sortField, setSortField] = useState('ipRange');
  const [sortOrder, setSortOrder] = useState('asc');
  const [filterCommunity, setFilterCommunity] = useState('');
  const pageSize = 10;
  const [selectedItems, setSelectedItems] = useState(new Set());

  // Справочник community для подсказок
  const [communities, setCommunities] = useState([]);
  useEffect(() => {
    (async () => {
      try {
        const res = await api.get(`/communities`);
        setCommunities(Array.isArray(res.data) ? res.data : []);
      } catch (e) {
        // тихо игнорируем
      }
    })();
  }, []);

  const renderCommunityBadge = (value) => (<CommunityBadge value={value} communities={communities} />);

  const isValidIPv4 = (ip) => {
    const octets = ip.split('.');
    if (octets.length !== 4) return false;
    return octets.every(o => /^\d{1,3}$/.test(o) && Number(o) >= 0 && Number(o) <= 255);
  };

  const isValidCidr = (value) => {
    const v = String(value).trim();
    const parts = v.split('/');
    if (parts.length !== 2) return false;
    const [ip, mask] = parts;
    if (!isValidIPv4(ip)) return false;
    if (!/^\d{1,2}$/.test(mask)) return false;
    const m = Number(mask);
    return m >= 0 && m <= 32;
  };

  // Допускаем как числовые, так и строковые (AS:NNN) community
  const isValidCommunity = (value) => /^(\d+|\d+:\d+)$/.test(String(value).trim());

  const [wsOpen, setWsOpen] = useState(false);
  const [wsUrl, setWsUrl] = useState('');

  useEffect(() => {
    const controller = new AbortController();
    (async () => {
      try {
        const r = await api.get('/ws/url', { signal: controller.signal });
        setWsUrl(String(r.data?.url || ''));
      } catch (e) {
        if (e?.name === 'CanceledError' || e?.name === 'AbortError' || e?.code === 'ERR_CANCELED') return;
      }
    })();
    return () => controller.abort();
  }, []);

  const fetchItems = async (abortSignal) => {
    setLoading(true);
    const opts = abortSignal ? { signal: abortSignal } : {};
    try {
      const response = await api.get(`/ip-ranges`, { params: { offset: 0, limit: 0, format: 'std' }, ...opts });
      const payload = Array.isArray(response.data?.items) ? response.data.items : [];
      const total = payload.length;
      setItems(payload);
      setOriginalItems(payload);
      setTotalItems(Number.isFinite(total) ? total : 0);
      setEtag(response.headers?.etag || '');
      setLastModified(response.headers?.['last-modified'] || '');
      const lengthHeader = response.headers?.['content-length-source'];
      setContentLength(typeof lengthHeader !== 'undefined' ? Number(lengthHeader) : null);
      setError('');
    } catch (error) {
      if (error?.name === 'CanceledError' || error?.name === 'AbortError' || error?.code === 'ERR_CANCELED') return;
      console.error('Error fetching ip-ranges:', error);
      setError('Не удалось загрузить IP-диапазоны. Проверьте, запущен ли бэкенд.');
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    if (!didInit.current) {
      didInit.current = true;
      const controller = new AbortController();
      fetchItems(controller.signal);
      return () => controller.abort();
    }
  }, []);

  const handleAddItem = (item) => {
    const ipRange = item?.ipRange || newItem.ipRange;
    const community = item?.community || newItem.community;
    
    const cidrOk = isValidCidr(ipRange);
    const communityOk = isValidCommunity(community);
    
    if (!cidrOk || !communityOk) {
      setError('Введите корректный IP-диапазон в формате CIDR (например 192.168.1.0/24) и числовой community.');
      return;
    }
    
    setError('');
    const newRange = { ipRange: ipRange.trim(), community: String(community).trim() };
    setItems([...items, newRange]);
    if (!item) {
      setNewItem({ ipRange: '', community: '' });
      setNewInvalid({ ipRange: false, community: false });
    }
  };

  const handleEdit = (item) => {
    setItemBeingEdited(item);
    setEditingValue(item.community);
    setEditModalOpen(true);
  };

  const handleSaveEdit = () => {
    if (!itemBeingEdited) return;
    if (!isValidCommunity(editingValue)) {
      setError('Community должен быть числом.');
      return;
    }
    const updatedItems = items.map(i =>
      i.ipRange === itemBeingEdited.ipRange ? { ...i, community: String(editingValue).trim() } : i
    );
    setItems(updatedItems);
    setEditModalOpen(false);
    setItemBeingEdited(null);
  };

  const handleCancelEdit = () => {
    setEditModalOpen(false);
    setItemBeingEdited(null);
  };

  const handleDeleteItem = (ipRangeToDelete) => {
    setItems(items.filter(i => i.ipRange !== ipRangeToDelete));
    setSelectedItems(prev => {
      const next = new Set(prev);
      next.delete(ipRangeToDelete);
      return next;
    });
  };

  // Функции для работы с выбранными элементами
  const toggleSelectItem = (ipRange) => {
    setSelectedItems(prev => {
      const next = new Set(prev);
      if (next.has(ipRange)) {
        next.delete(ipRange);
      } else {
        next.add(ipRange);
      }
      return next;
    });
  };

  const selectAll = () => {
    setSelectedItems(new Set(paginatedItems.map(i => i.ipRange)));
  };

  const deselectAll = () => {
    setSelectedItems(new Set());
  };

  const handleBulkDelete = () => {
    if (selectedItems.size === 0) return;
    const count = selectedItems.size;
    setItems(items.filter(i => !selectedItems.has(i.ipRange)));
    setSelectedItems(new Set());
    window.notify?.success?.(`Удалено ${count} записей`);
  };

  const handleBulkExport = () => {
    if (selectedItems.size === 0) return;
    const selected = items.filter(i => selectedItems.has(i.ipRange));
    const header = ['ipRange', 'community'];
    const csv = [header, ...selected.map(i => [i.ipRange, i.community])]
      .map(r => r.map(x => `"${(x ?? '').toString().replace(/"/g, '""')}"`).join(','))
      .join('\n');
    const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `ip_ranges_selected_${new Date().toISOString().split('T')[0]}.csv`;
    a.click();
    URL.revokeObjectURL(url);
    window.notify?.success?.(`Экспортировано ${selectedItems.size} записей`);
  };

  const copyToClipboard = (text) => {
    navigator.clipboard.writeText(text).then(() => {
      window.notify?.success?.('Скопировано в буфер обмена');
    }).catch(() => {
      window.notify?.error?.('Не удалось скопировать');
    });
  };

  const duplicateItem = (item) => {
    setItems([...items, { ...item, ipRange: `${item.ipRange.split('/')[0]}/24` }]);
    window.notify?.success?.('Запись продублирована');
  };

  const confirmDelete = (item) => {
    setItemToDelete(item);
    setShowDeleteModal(true);
  };

  const executeDelete = () => {
    if (itemToDelete) {
      handleDeleteItem(itemToDelete.ipRange);
      setShowDeleteModal(false);
      setItemToDelete(null);
    }
  };

  const deduplicate = (arr) => {
    const seen = new Set();
    const out = [];
    for (const i of arr) {
      const key = String(i.ipRange).trim();
      if (!key) continue;
      if (seen.has(key)) continue;
      seen.add(key);
      out.push({ ipRange: key, community: String(i.community || '').trim() });
    }
    return out;
  };

  const computeDiff = (before, after) => {
    const mapBefore = new Map(before.map(i => [i.ipRange, i]));
    const mapAfter = new Map(after.map(i => [i.ipRange, i]));
    const added = [];
    const removed = [];
    const changed = [];
    for (const [k, v] of mapAfter) {
      if (!mapBefore.has(k)) { added.push(v); continue; }
      const prev = mapBefore.get(k);
      if (String(prev.community) !== String(v.community)) changed.push({ from: prev, to: v });
    }
    for (const [k, v] of mapBefore) {
      if (!mapAfter.has(k)) removed.push(v);
    }
    return { added, removed, changed };
  };

  const [showDiff, setShowDiff] = useState(false);
  const [diff, setDiff] = useState({ added: [], removed: [], changed: [] });
  const [confirmSaveOpen, setConfirmSaveOpen] = useState(false);
  const [historyOpen, setHistoryOpen] = useState(false);
  const [clearCommunitiesOpen, setClearCommunitiesOpen] = useState(false);

  // Аналитика
  const [analyzeOpen, setAnalyzeOpen] = useState(false);
  const [analyzeConfirmOpen, setAnalyzeConfirmOpen] = useState(false);
  const [analysisResultsOpen, setAnalysisResultsOpen] = useState(false);
  const [analysisPreview, setAnalysisPreview] = useState(null);
  const [overwriteConfirmOpen, setOverwriteConfirmOpen] = useState(false);
  const [analyzeFilters, setAnalyzeFilters] = useState({ community: '', minMask: 0, maxMask: 32, type: 'any', supernet16: false });

  const handlePreviewDiff = () => {
    const valid = items.filter(i => i != null && isValidCidr(i.ipRange) && isValidCommunity(i.community))
      .map(i => ({ ipRange: i.ipRange.trim(), community: String(i.community).trim() }));
    const unique = deduplicate(valid);
    setDiff(computeDiff(originalItems, unique));
    setShowDiff(true);
  };

  const handleSaveChanges = async () => {
    // подготовим diff и спросим подтверждение только при массовых изменениях (>10)
    const valid = items.filter(i => i != null && isValidCidr(i.ipRange) && isValidCommunity(i.community))
      .map(i => ({ ipRange: i.ipRange.trim(), community: String(i.community).trim() }));
    const unique = deduplicate(valid);
    const diffData = computeDiff(originalItems, unique);
    setDiff(diffData);
    
    // Умное подтверждение: показываем модалку только при массовых изменениях
    const totalChanges = (diffData.added?.length || 0) + (diffData.removed?.length || 0) + (diffData.changed?.length || 0);
    
    if (totalChanges > 10) {
      // Массовое изменение - требуется подтверждение
      setConfirmSaveOpen(true);
    } else {
      // Малое изменение - сохраняем сразу
      performSave();
    }
  };

  const performSave = async () => {
    setConfirmSaveOpen(false);
    setLoading(true);
    try {
      const valid = items.filter(i => isValidCidr(i.ipRange) && isValidCommunity(i.community))
        .map(i => ({ ipRange: i.ipRange.trim(), community: String(i.community).trim() }));
      const unique = deduplicate(valid);

      // Загрузим полный список, совместим с изменениями текущей страницы и сохраним
      const fullRes = await api.get(`/ip-ranges`, { params: { offset: 0, limit: 0, format: 'std' } });
      const full = Array.isArray(fullRes.data?.items) ? fullRes.data.items : (Array.isArray(fullRes.data) ? fullRes.data : []);
      const fullMap = new Map(full.map(i => [String(i.ipRange).trim(), { ipRange: String(i.ipRange).trim(), community: String(i.community || '').trim() }]));
      const originalPageMap = new Map(originalItems.map(i => [String(i.ipRange).trim(), true]));
      const uniqueMap = new Map(unique.map(i => [String(i.ipRange).trim(), { ipRange: String(i.ipRange).trim(), community: String(i.community).trim() }]));
      for (const key of originalPageMap.keys()) {
        if (!uniqueMap.has(key)) fullMap.delete(key);
      }
      for (const [key, val] of uniqueMap.entries()) fullMap.set(key, val);
      const fullToSave = Array.from(fullMap.values());

      const et = fullRes?.headers?.etag || etag;
      const response = await api.post(`/ip-ranges`, { ipRanges: fullToSave, etag: et }, { validateStatus: () => true });
      if (response.status === 412) {
        setError('Данные изменились в S3 (ETag mismatch). Обновите список и попробуйте снова.');
        return;
      }
      if (response.status >= 400) {
        throw new Error(`Save failed with status ${response.status}`);
      }
      setEtag(response.headers?.etag || et);
      await fetchItems();
      setSuccess('Изменения успешно сохранены!');
      setTimeout(() => setSuccess(''), 3000);
      notifyMutationSuccess('Изменения сохранены');
    } catch (error) {
      console.error('Error saving changes:', error);
      setError('Не удалось сохранить изменения.');
    } finally {
      setLoading(false);
    }
  };

  const handleImport = () => {
    setImportOpen(true);
  };

  const handleExport = async () => {
    try {
      const effectiveQ = searchTerm ? searchTerm : (filterCommunity ? filterCommunity : '');
      const response = await api.get(`/ip-ranges`, { params: { q: effectiveQ, offset: 0, limit: 0, format: 'std' } });
      const all = Array.isArray(response.data?.items) ? response.data.items : (Array.isArray(response.data) ? response.data : []);
      const header = ['ipRange', 'community'];
      const csv = [header, ...all.map(i => [i.ipRange, i.community])]
        .map(r => r.map(x => `"${(x ?? '').toString().replace(/"/g, '""')}"`).join(','))
        .join('\n');
      const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `ip_ranges_${new Date().toISOString().split('T')[0]}.csv`;
      a.click();
      URL.revokeObjectURL(url);
    } catch (e) {
      window.notify?.error('Экспорт IP-диапазонов не удался', String(e?.message || e));
    }
  };

  // Drag&Drop импорт (оставим как быстрый путь)
  const onDropImport = async (e) => {
    e.preventDefault();
    const file = e.dataTransfer?.files?.[0];
    if (!file) return;
    const text = await file.text();
    const lines = text.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
    const parsed = lines.map(l => {
      const [cidr, c] = l.split(/\s+/);
      return { ipRange: (cidr || '').trim(), community: (c || '').trim() };
    }).filter(i => i != null && isValidCidr(i.ipRange) && isValidCommunity(i.community));
    setItems(prev => {
      const merged = [...prev, ...parsed];
      const map = new Map();
      for (const it of merged) {
        const key = String(it.ipRange).trim();
        if (!map.has(key)) map.set(key, { ipRange: key, community: String(it.community).trim() });
      }
      return Array.from(map.values());
    });
  };

  const clearInvalid = () => {
    setItems(prev => prev.filter(i => i != null && (i.ipRange || i.community))
      .filter(i => isValidCidr(i.ipRange) && isValidCommunity(i.community))
    );
  };

  // ===== Импорт через модалку =====
  const [importOpen, setImportOpen] = useState(false);
  const parseLine = (line) => {
    const [cidr, c] = String(line).split(/\s+/);
    return { ipRange: (cidr || '').trim(), community: (c || '').trim() };
  };
  const validateItem = (obj) => isValidCidr(obj.ipRange) && isValidCommunity(obj.community);
  const applyImportedItems = (list) => {
    setItems(prev => {
      const merged = [...prev, ...list];
      const map = new Map();
      for (const it of merged) {
        const key = String(it.ipRange).trim();
        if (!map.has(key)) map.set(key, { ipRange: key, community: String(it.community).trim() });
      }
      return Array.from(map.values());
    });
    setImportOpen(false);
  };

  // Шаблон CSV для пустого состояния
  const downloadSampleCsv = () => {
    const header = ['ipRange','community'];
    const lines = [header, ['192.168.1.0/24', '65000:100']]
      .map(r => r.map(x => `"${(x ?? '').toString().replace(/"/g, '""')}"`).join(','))
      .join('\n');
    const blob = new Blob([lines], { type: 'text/csv;charset=utf-8;' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'ip_ranges_sample.csv';
    a.click();
    URL.revokeObjectURL(url);
  };

  // Сортировка
  const sortedItems = [...items].sort((a, b) => {
    let valA = a[sortField] || '';
    let valB = b[sortField] || '';
    if (typeof valA === 'string') valA = valA.toLowerCase();
    if (typeof valB === 'string') valB = valB.toLowerCase();
    if (valA < valB) return sortOrder === 'asc' ? -1 : 1;
    if (valA > valB) return sortOrder === 'asc' ? 1 : -1;
    return 0;
  });

  // Локальная фильтрация и пагинация
  const filtered = sortedItems.filter(i => {
    const byCommunity = !filterCommunity || i.community === filterCommunity;
    const term = String(searchTerm || '').toLowerCase();
    const bySearch = !term || String(i.ipRange).toLowerCase().includes(term);
    return byCommunity && bySearch;
  });
  const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize));
  const paginatedItems = filtered.slice((currentPage - 1) * pageSize, (currentPage) * pageSize);

  // Для фильтра - список всех уникальных community
  const allCommunities = Array.from(new Set(items.map(i => i.community)));

  // Сортировка по клику
  const handleSort = (field) => {
    if (sortField === field) {
      setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
    } else {
      setSortField(field);
      setSortOrder('asc');
    }
  };

  // Глобальные хоткеи: Ctrl+S — сохранить изменения
  useEffect(() => {
    const onKey = (e) => {
      if ((e.ctrlKey || e.metaKey) && (e.key === 's' || e.key === 'S')) {
        e.preventDefault();
        if (!loading) handleSaveChanges();
      }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [loading, items, originalItems, etag, sortField, sortOrder, filterCommunity]);

  const handleEditKeyDown = (e) => {
    if (e.key === 'Enter') {
      e.preventDefault();
      handleSaveEdit();
    }
    if (e.key === 'Escape') {
      e.preventDefault();
      handleCancelEdit();
    }
  };

  // ===== Аналитика: утилиты =====
  const parseMask = (cidr) => {
    const parts = String(cidr || '').split('/');
    const mask = Number(parts[1]);
    return Number.isFinite(mask) ? mask : NaN;
  };

  const countAddresses = (mask) => {
    if (!Number.isFinite(mask) || mask < 0 || mask > 32) return 0n;
    return 1n << BigInt(32 - mask);
  };

  const parseOctets = (ip) => {
    const [a, b, c, d] = String(ip || '').split('.').map(x => Number(x));
    return [a, b, c, d];
  };

  const isPrivateIPv4 = (ip) => {
    const [a, b] = parseOctets(ip);
    if (a === 10) return true; // 10.0.0.0/8
    if (a === 192 && b === 168) return true; // 192.168.0.0/16
    if (a === 172 && b >= 16 && b <= 31) return true; // 172.16.0.0/12
    return false;
  };

  const isCGNATIPv4 = (ip) => {
    const [a, b] = parseOctets(ip);
    return a === 100 && b >= 64 && b <= 127; // 100.64.0.0/10
  };

  const getIpCategory = (cidr) => {
    const baseIp = String(cidr || '').split('/')[0];
    if (isCGNATIPv4(baseIp)) return 'cgnat';
    if (isPrivateIPv4(baseIp)) return 'private';
    return 'public';
  };

  const formatBigInt = (n) => {
    try {
      const s = (n ?? 0n).toString();
      return s.replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
    } catch {
      return String(n);
    }
  };

  const buildAnalysisConfirmText = () => {
    const parts = [];
    parts.push(`Community: ${analyzeFilters.community || 'не выбрано'}`);
    parts.push(`Маска: /${analyzeFilters.minMask}..../${analyzeFilters.maxMask}`);
    const typeLabel = analyzeFilters.type === 'any' ? 'любой' : (analyzeFilters.type === 'private' ? 'только приватные' : (analyzeFilters.type === 'public' ? 'только публичные' : 'только CGNAT'));
    parts.push(`Тип: ${typeLabel}`);
    parts.push(`Агрегировать >2 префиксов в одном /16 до /16: ${analyzeFilters.supernet16 ? 'да' : 'нет'}`);
    return parts.join('\n');
  };

  // ===== Суммаризация CIDR для выбранного community =====
  const ipToInt = (ip) => {
    const [a, b, c, d] = parseOctets(ip);
    return (BigInt(a) << 24n) | (BigInt(b) << 16n) | (BigInt(c) << 8n) | BigInt(d);
  };

  const intToIp = (num) => {
    const a = Number((num >> 24n) & 255n);
    const b = Number((num >> 16n) & 255n);
    const c = Number((num >> 8n) & 255n);
    const d = Number(num & 255n);
    return `${a}.${b}.${c}.${d}`;
  };

  const cidrToRange = (cidr) => {
    const [ip, maskStr] = String(cidr).split('/');
    const mask = Number(maskStr);
    const ipNum = ipToInt(ip);
    const hostBits = 32 - mask;
    const start = ipNum & (~0n << BigInt(hostBits));
    const end = start | ((1n << BigInt(hostBits)) - 1n);
    return [start, end];
  };

  const countTrailingZeros32 = (n) => {
    if (n === 0n) return 32;
    let zeros = 0;
    for (let i = 0; i < 32; i++) {
      if ((n & (1n << BigInt(i))) !== 0n) { zeros = i; break; }
    }
    return zeros;
  };

  const floorLog2 = (n) => {
    let p = -1;
    while (n > 0n) { n >>= 1n; p++; }
    return p < 0 ? 0 : p;
  };

  const rangeToCidrs = (start, end) => {
    const res = [];
    let cur = start;
    while (cur <= end) {
      const maxAlignPow = countTrailingZeros32(cur);
      const remaining = end - cur + 1n;
      const remainingPow = floorLog2(remaining);
      const blockPow = Math.min(maxAlignPow, remainingPow);
      const mask = 32 - blockPow;
      res.push(`${intToIp(cur)}/${mask}`);
      cur += 1n << BigInt(blockPow);
    }
    return res;
  };

  const mergeIntervals = (intervals) => {
    if (intervals.length === 0) return [];
    intervals.sort((a, b) => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
    const merged = [];
    let [cs, ce] = intervals[0];
    for (let i = 1; i < intervals.length; i++) {
      const [s, e] = intervals[i];
      if (s <= ce + 1n) {
        if (e > ce) ce = e;
      } else {
        merged.push([cs, ce]);
        cs = s; ce = e;
      }
    }
    merged.push([cs, ce]);
    return merged;
  };

  const summarizeCidrs = (cidrs) => {
    const ranges = cidrs.map(cidrToRange);
    const merged = mergeIntervals(ranges);
    const out = [];
    for (const [s, e] of merged) {
      out.push(...rangeToCidrs(s, e));
    }
    return out;
  };

  // Агрессивная агрегация: если в одном /16 > 2 префиксов — свернуть в /16
  const get16BaseCidr = (cidr) => {
    const ip = String(cidr).split('/')[0];
    const ipNum = ipToInt(ip);
    const start = ipNum & (~0n << 16n);
    return `${intToIp(start)}/16`;
  };

  const applySupernet16 = (cidrs) => {
    const buckets = new Map();
    for (const c of cidrs) {
      const base16 = get16BaseCidr(c);
      if (!buckets.has(base16)) buckets.set(base16, []);
      buckets.get(base16).push(c);
    }
    const out = [];
    for (const [base16, list] of buckets) {
      if (list.length > 2) out.push(base16); else out.push(...list);
    }
    return Array.from(new Set(out));
  };

  const runAnalysis = () => {
    const community = String(analyzeFilters.community || '').trim();
    if (!community) { window.notify?.error?.('Выберите community'); return; }
    // исходные для community
    const allForCommunity = items.filter(i => i != null && i.community === community && isValidCidr(i.ipRange));
    const totalBefore = allForCommunity.length;

    const filtered = allForCommunity.filter(i => {
      const mask = parseMask(i.ipRange);
      if (mask < analyzeFilters.minMask || mask > analyzeFilters.maxMask) return false;
      const cat = getIpCategory(i.ipRange);
      if (analyzeFilters.type !== 'any' && cat !== analyzeFilters.type) return false;
      return true;
    });
    const unchanged = allForCommunity.filter(i => !filtered.includes(i));

    const beforeCidrs = filtered.map(i => i.ipRange);
    let afterCidrs = summarizeCidrs(beforeCidrs);
    if (analyzeFilters.supernet16) {
      afterCidrs = applySupernet16(afterCidrs);
    }
    const processedBefore = beforeCidrs.length;
    const processedAfter = afterCidrs.length;
    const totalAfter = processedAfter + unchanged.length;

    setAnalysisPreview({
      community,
      totalBefore,
      totalAfter,
      processedBefore,
      processedAfter,
      unchangedCount: unchanged.length,
      beforeCidrs,
      afterCidrs,
      unchangedCidrs: unchanged.map(i => i.ipRange)
    });
    setAnalysisResultsOpen(true);
  };

  const applyAnalysisToItems = () => {
    if (!analysisPreview) return;
    const { community, afterCidrs, unchangedCidrs } = analysisPreview;
    const keptOthers = items.filter(i => i != null && i.community !== community);
    const newCommunityItems = [
      ...unchangedCidrs.map(ipRange => ({ ipRange, community })),
      ...afterCidrs.map(ipRange => ({ ipRange, community }))
    ];
    // Удаляем дубликаты по ipRange, соблюдая глобальную уникальность ключа ipRange
    const map = new Map();
    for (const it of [...keptOthers, ...newCommunityItems]) {
      const key = String(it.ipRange).trim();
      if (!map.has(key)) map.set(key, { ipRange: key, community: String(it.community).trim() });
      else {
        // Если ipRange уже существует в другой community, перезапишем значением текущего (по выбранному community)
        // но только если этот ipRange относится к выбранному community
        if (it.community === community) map.set(key, { ipRange: key, community: String(it.community).trim() });
      }
    }
    const next = Array.from(map.values());
    setItems(next);
    setAnalysisResultsOpen(false);
    setOverwriteConfirmOpen(false);
    window.notify?.success?.('Диапазоны перезаписаны по результатам анализа. Не забудьте сохранить изменения.');
  };

  const exportAnalysisCsv = () => {
    const header = ['community', 'ranges', 'totalAddresses'];
    const lines = [header, ...analysisResult.map(r => [r.community, r.ranges, r.totalAddresses.toString()])]
      .map(r => r.map(x => `"${(x ?? '').toString().replace(/"/g, '""')}"`).join(','))
      .join('\n');
    const blob = new Blob([lines], { type: 'text/csv;charset=utf-8;' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `ip_ranges_analysis_${new Date().toISOString().split('T')[0]}.csv`;
    a.click();
    URL.revokeObjectURL(url);
  };

  return (
    <div>
      {error && (
        <ErrorAlert message={error} onClose={() => setError('')} />
      )}

      {success && (
        <div className="alert alert-success alert-dismissible" role="alert">
          <div className="d-flex">
            <div>
              <IconCheck className="icon alert-icon" />
            </div>
            <div>{success}</div>
          </div>
          <button
            type="button"
            className="btn-close"
            onClick={() => setSuccess('')}
          ></button>
        </div>
      )}

      <PageHeader
        title="IP-диапазоны"
        icon={<IconNetwork size={24} />}
        meta={<LastSaved timestamp={lastModified} variant="compact" />}
        actions={
          <PageHeaderActions
            loading={loading}
            onPreview={handlePreviewDiff}
            disablePreview={loading || items.length === 0}
            onRefresh={fetchItems}
            disableRefresh={loading}
            onImport={handleImport}
            onExport={handleExport}
            disableExport={items.length === 0}
            onClear={clearInvalid}
            disableClear={items.length === 0}
            onClearCommunities={() => {
              if (!filterCommunity) {
                setError('Выберите community в фильтре для очистки');
                return;
              }
              setClearCommunitiesOpen(true);
            }}
            disableClearCommunities={items.length === 0 || !filterCommunity}
            onSave={handleSaveChanges}
            disableSave={loading}
            onHistory={() => setHistoryOpen(true)}
            lastModified={lastModified}
            onOnlineUpdate={() => setWsOpen(true)}
            onBackgroundUpdate={async () => {
              try {
                const res = await fetch('/api/update-bgp/background', {
                  method: 'POST',
                });
                const data = await res.json().catch(() => ({}));
                if (!res.ok || data.ok === false) {
                  throw new Error(data?.message || `HTTP ${res.status}`);
                }
                notifyMutationSuccess('Фоновое обновление запущено', data);
              } catch (e) {
                window.notify?.error(
                  'Не удалось запустить фоновое обновление',
                  String(e),
                );
              }
            }}
          />
        }
      />

      <AddItemModal
        open={addModalOpen}
        onClose={() => setAddModalOpen(false)}
        onSubmit={handleAddItem}
        type="ipRange"
        communities={communities}
        loading={loading}
      />

      <div className="card card-flat">
        <div className="card-header">
          <div className="row align-items-center w-100 g-2">
            <div className="col">
              <div className="d-flex align-items-center justify-content-between flex-wrap gap-2">
                <div className="d-flex align-items-center gap-2">
                  <IconNetwork className="icon text-muted" size={18} />
                  <span className="text-muted text-uppercase small">Список</span>
                  <span className="badge bg-blue-lt text-blue">
                    {filtered.length}
                  </span>
                </div>
                <div className="d-none d-md-block text-muted small">
                  Всего записей: {filtered.length}
                </div>
              </div>
            </div>
            <div className="col-12 mt-2"></div>
            <div className="col d-flex gap-2 flex-wrap align-items-center">
              <div
                className="d-flex gap-2 w-auto flex-grow-1"
                style={{ minWidth: '300px' }}
              >
                <select
                  className="form-select w-auto"
                  value={filterCommunity}
                  onChange={(e) => {
                    setFilterCommunity(e.target.value);
                    setCurrentPage(1);
                  }}
                >
                  <option value="">Все community</option>
                  {allCommunities.map((community) => (
                    <option key={community} value={community}>
                      {community}
                    </option>
                  ))}
                </select>
                <div className="input-icon flex-grow-1">
                  <span className="input-icon-addon">
                    <IconSearch size={18} />
                  </span>
                  <input
                    type="text"
                    className="form-control"
                    placeholder="Поиск IP-диапазона..."
                    value={searchTerm}
                    onChange={(e) => {
                      setSearchTerm(e.target.value);
                      setCurrentPage(1);
                    }}
                    aria-label="Поиск IP-диапазона"
                  />
                </div>
              </div>
              <div className="d-flex gap-2">
                <button
                  className="btn btn-primary"
                  onClick={() => setAddModalOpen(true)}
                  title="Добавить новый IP-диапазон"
                >
                  <IconPlus className="me-1" size={18} />
                  Добавить
                </button>
              </div>
            </div>
          </div>
        </div>

        <div className="card-body p-0">
          {loading ? (
            <TableSkeleton rows={10} cols={3} flat />
          ) : paginatedItems.length === 0 ? (
            <TableEmpty cols={3} flat>
              <EmptyState
                title="Нет IP-диапазонов"
                description="Импортируйте или добавьте записи, чтобы начать."
                action={
                  <button className="btn btn-primary" onClick={handleImport}>
                    Импорт
                  </button>
                }
                secondaryAction={
                  <button
                    className="btn btn-outline-primary"
                    onClick={downloadSampleCsv}
                  >
                    Шаблон CSV
                  </button>
                }
              />
            </TableEmpty>
          ) : (
            <div className="table-responsive">
              <table className="table table-flat table-vcenter table-nowrap mb-0">
                <thead>
                  <tr>
                    <th style={{ width: '40px' }}>
                      <input
                        type="checkbox"
                        className="form-check-input"
                        checked={
                          paginatedItems.length > 0 &&
                          paginatedItems.every((i) => selectedItems.has(i.ipRange))
                        }
                        onChange={(e) => {
                          if (e.target.checked) {
                            selectAll();
                          } else {
                            deselectAll();
                          }
                        }}
                        title="Выбрать все на странице"
                      />
                    </th>
                    <th
                      className="cursor-pointer user-select-none"
                      onClick={() => handleSort('ipRange')}
                    >
                      <div className="d-flex align-items-center">
                        <IconNetwork size={16} className="me-1 text-muted" />
                        IP-диапазон
                        {sortField === 'ipRange' && (
                          <span className="ms-1">
                            {sortOrder === 'asc' ? (
                              <IconArrowUp size={14} />
                            ) : (
                              <IconArrowDown size={14} />
                            )}
                          </span>
                        )}
                      </div>
                    </th>
                    <th
                      className="cursor-pointer user-select-none"
                      onClick={() => handleSort('community')}
                    >
                      <div className="d-flex align-items-center">
                        <IconHash size={16} className="me-1 text-muted" />
                        Community
                        {sortField === 'community' && (
                          <span className="ms-1">
                            {sortOrder === 'asc' ? (
                              <IconArrowUp size={14} />
                            ) : (
                              <IconArrowDown size={14} />
                            )}
                          </span>
                        )}
                      </div>
                    </th>
                    <th className="text-end">Действия</th>
                  </tr>
                </thead>
                <tbody>
                  {paginatedItems.map((item) => (
                    <tr
                      key={item.ipRange}
                      className={
                        selectedItems.has(item.ipRange) ? 'table-selected' : ''
                      }
                    >
                      <td>
                        <input
                          type="checkbox"
                          className="form-check-input"
                          checked={selectedItems.has(item.ipRange)}
                          onChange={() => toggleSelectItem(item.ipRange)}
                          onClick={(e) => e.stopPropagation()}
                        />
                      </td>
                      <td className="font-monospace" title="CIDR">
                        <div className="d-flex align-items-center">
                          <IconNetwork
                            size={16}
                            className="me-2 text-muted"
                          />
                          <span>{item.ipRange}</span>
                          <Tooltip
                            content="Копировать IP-диапазон"
                            position="top"
                          >
                            <button
                              className="btn btn-ghost-secondary btn-icon btn-sm ms-2"
                              onClick={() => copyToClipboard(item.ipRange)}
                              aria-label="Копировать"
                              title="Копировать IP-диапазон"
                            >
                              <IconCopy size={14} />
                            </button>
                          </Tooltip>
                        </div>
                      </td>
                      <td>{renderCommunityBadge(item.community)}</td>
                      <td className="text-end">
                        <div
                          className="btn-group btn-group-sm"
                          role="group"
                          aria-label={`Действия с диапазоном ${item.ipRange}`}
                        >
                          <Tooltip content="Редактировать" position="top">
                            <button
                              className="btn btn-icon btn-outline-primary"
                              aria-label="Редактировать"
                              onClick={() => handleEdit(item)}
                              title="Редактировать"
                            >
                              <IconEdit size={16} />
                            </button>
                          </Tooltip>
                          <Tooltip content="Удалить" position="top">
                            <button
                              className="btn btn-icon btn-outline-danger"
                              aria-label="Удалить"
                              onClick={() => confirmDelete(item)}
                              title="Удалить"
                            >
                              <IconTrash size={16} />
                            </button>
                          </Tooltip>
                        </div>
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          )}
        </div>

        <div className="card-footer border-top-0">
          <Pagination
            currentPage={currentPage}
            totalPages={totalPages}
            totalItems={totalItems || filtered.length}
            pageSize={pageSize}
            onPageChange={setCurrentPage}
          />
        </div>
      </div>

      <BulkActionsBar
        selectedCount={selectedItems.size}
        totalCount={paginatedItems.length}
        onDeselectAll={deselectAll}
        onDelete={handleBulkDelete}
        onExport={handleBulkExport}
      />

      <ConfirmDialog
        open={showDeleteModal}
        title={'Удалить IP-диапазон?'}
        message={
          !itemToDelete
            ? ''
            : `Вы уверены, что хотите удалить "${itemToDelete.ipRange}"? Это действие необратимо.`
        }
        confirmText={'Удалить'}
        cancelText={'Отмена'}
        destructive
        size={'sm'}
        onConfirm={executeDelete}
        onCancel={() => setShowDeleteModal(false)}
      />

      <ConfirmDiffModal
        show={confirmSaveOpen}
        diff={diff}
        onConfirm={performSave}
        onClose={() => setConfirmSaveOpen(false)}
      />

      <ImportModal
        show={importOpen}
        title={'Импорт IP-диапазонов'}
        description={'Формат: CIDR ПРОБЕЛ COMMUNITY'}
        parseLine={parseLine}
        validateItem={validateItem}
        onConfirm={applyImportedItems}
        onClose={() => setImportOpen(false)}
        sampleHeader={['ipRange', 'community']}
        placeholder={'192.168.1.0/24 65000:100'}
      />

      <FormModal
        show={editModalOpen && !!itemBeingEdited}
        onClose={handleCancelEdit}
        onSubmit={handleSaveEdit}
        title="Редактировать community"
        submitLabel="Сохранить"
        submitIcon={IconCheck}
        size="md"
        centered
        disabled={!isValidCommunity(editingValue)}
      >
        {itemBeingEdited && (
          <>
            <p className="text-muted mb-2">
              IP-диапазон:{' '}
              <span className="font-monospace">{itemBeingEdited.ipRange}</span>
            </p>
            <div className="mb-3">
              <label className="form-label">Community</label>
              <CommunityAutocompleteInput
                value={editingValue}
                onChange={setEditingValue}
                communities={communities}
                onKeyDown={handleEditKeyDown}
                className={`form-control${
                  isValidCommunity(editingValue) ? '' : ' is-invalid'
                }`}
                useShortValue={true}
              />
              {!isValidCommunity(editingValue) && (
                <div className="invalid-feedback d-block">
                  Введите корректное значение community (число или N:N).
                </div>
              )}
            </div>
          </>
        )}
      </FormModal>

      <HistoryModal
        resource="ip-ranges"
        show={historyOpen}
        onClose={() => setHistoryOpen(false)}
        onRolledBack={() => fetchItems()}
      />

      <WsUpdateModal
        show={wsOpen}
        url={wsUrl}
        onClose={() => setWsOpen(false)}
      />

      <ConfirmDialog
        open={clearCommunitiesOpen}
        title={'Удалить записи community'}
        message={`Будут удалены все IP-диапазоны с community "${filterCommunity}". Действие НЕ сохраняет изменения автоматически.`}
        confirmText={'Удалить'}
        cancelText={'Отмена'}
        onConfirm={() => {
          setItems((prev) =>
            prev.filter((i) => i != null && i.community !== filterCommunity),
          );
          setClearCommunitiesOpen(false);
        }}
        onCancel={() => setClearCommunitiesOpen(false)}
      />
    </div>
  );
}

export default IPRangesManager;