# -*- coding: utf-8 -*-
"""DokoGen — автоматический подбор угроз безопасности информации (236 приказ).

Логика программы: по характеристикам объекта КИИ (тип, сеть, нарушитель)
подбирает актуальные угрозы из перечня БДУ ФСТЭК (threats_fstec.json).
Если перечень не скачан — использует базовый threats.json.

Правила эвристические; при наличии методического документа (например,
«Методика оценки угроз безопасности информации» ФСТЭК) — адаптируются под него.
"""

import os
import json


def load_threats(dict_dir=None):
    """Список угроз: сначала скачанный с ФСТЭК (с кодами УБИ), иначе базовый."""
    if dict_dir is None:
        dict_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'dictionaries')
    fstec = os.path.join(dict_dir, 'threats_fstec.json')
    if os.path.exists(fstec):
        try:
            with open(fstec, encoding='utf-8') as f:
                data = json.load(f)
                if data:
                    return data
        except Exception:
            pass
    base = os.path.join(dict_dir, 'threats.json')
    try:
        with open(base, encoding='utf-8') as f:
            return json.load(f)
    except Exception:
        return []


# ---------------------------------------------------------------
# Ключевые слова (в названии угрозы) по признакам объекта
# ---------------------------------------------------------------
# Базовый набор — актуален для любого объекта КИИ
BASE_KEYWORDS = [
    'вредоносн', 'несанкционированн', 'утечк', 'перехват', 'модификац',
    'уничтожен', 'отказ в обслуживании', 'нсд', 'биос', 'пароль',
    'криптографическ', 'шифрован', 'аутентификац',
]

# Объект имеет подключение к сети Интернет / сеть электросвязи
INTERNET_KEYWORDS = [
    'удалённ', 'удаленн', 'сетев', 'фишинг', 'ddos', 'отказ в обслуживании',
    'веб', 'электронн', 'почт', 'беспроводн', 'wi-fi', 'wifi', 'vpn',
    'маршрутизац', 'протокол', 'интернет',
]

# Объект изолирован (нет подключения к Интернету)
ISOLATED_KEYWORDS = [
    'съёмн', 'съемн', 'физическ', 'локальн', 'инсайд', 'носител',
    'биос', 'желез', 'аппаратн',
]

# АСУ / технологический процесс
ASU_KEYWORDS = [
    'технологическ', 'асутп', 'промышленн', 'scada', 'датчик',
    'исполнительн', 'технологическ', 'производств', 'процесс',
]

# ИТКС / сети связи
NETWORK_KEYWORDS = [
    'сеть', 'канал', 'протокол', 'маршрутизац', 'связ', 'передач',
    'коммутац', 'трафик',
]

# Внутренний нарушитель
INSIDER_KEYWORDS = [
    'инсайд', 'сотрудник', 'привилегированн', 'администратор',
    'внутренн', 'персонал',
]

# Внешний нарушитель
EXTERNAL_KEYWORDS = [
    'внешн', 'удалённ', 'удаленн', 'дистанционн',
]


def _is_asu(isys) -> bool:
    t = (getattr(isys, 'object_type', '') or '').lower()
    return 'асу' in t or 'автоматизированн' in t or 'технологическ' in t


def _is_network(isys) -> bool:
    t = (getattr(isys, 'object_type', '') or '').lower()
    return 'телекоммуникац' in t or 'сеть' in t


def _has_internet(isys) -> bool:
    """Есть ли подключение к сети Интернет / внешним сетям."""
    if getattr(isys, 'is_internet', False):
        return True
    desc = ((getattr(isys, 'description', '') or '') + ' ').lower()
    operator = (getattr(isys, 'net_operator', '') or '').lower()
    net_category = (getattr(isys, 'net_category', '') or '').lower()

    # Явные признаки ОТСУТСТВИЯ внешней сети
    if 'отсутств' in operator:                      # оператор «Отсутствует»
        return False
    if 'отсутств' in desc and ('интернет' in desc or 'подключ' in desc):
        return False
    if 'не требуется' in desc and ('доменн' in desc or 'интернет' in desc):
        return False
    if 'изолирован' in desc:
        return False

    # Сеть общего пользования — это и есть внешняя сеть (Интернет)
    if 'общего пользован' in net_category:
        return True

    # Указан реальный оператор/провайдер — внешняя связь есть
    if operator and operator not in ('нет', 'отсутствует', 'не требуется', '-'):
        return True

    # Консервативно: без явных признаков внешней сети считаем, что её нет
    return False


def _is_insider(isys) -> bool:
    a = (getattr(isys, 'attacker_category', '') or '').lower()
    return 'внутрен' in a


def _score(threat_lower, keywords):
    """Сколько ключевых слов встретилось в названии угрозы."""
    return sum(1 for kw in keywords if kw in threat_lower)


def suggest_threats_program(isys, threats=None, limit=None):
    """Подбор угроз по характеристикам объекта КИИ (инвертированная логика).

    По умолчанию актуальны ВСЕ угрозы из перечня (БДУ ФСТЭК).
    Исключаются только заведомо неактуальные для данного объекта:
      - нет сети Интернет → убираем угрозы удалённого доступа, веб, почты,
        беспроводных сетей, DDoS, облачных и т.п.;
      - объект типа АСУ → убираем угрозы BIOS, грид-систем, виртуализации
        (у технологического оборудования их, как правило, нет).
    Такой подход даёт полный список, а не «15 самых похожих».

    Возвращает список строк (с кодами УБИ, если они есть в перечне).
    """
    if threats is None:
        threats = load_threats()
    if not threats:
        return []

    has_internet = _has_internet(isys)
    is_asu = _is_asu(isys)
    is_network = _is_network(isys)

    # --- Слова-исключения (если встретились в названии угрозы — она неактуальна) ---
    exclude = []

    if not has_internet:
        # Объект изолирован: угрозы, реализуемые только через внешние сети
        exclude += [
            'удалённ', 'удаленн', 'дистанционн', 'веб', 'электронн', 'почт',
            'фишинг', 'беспроводн', 'wi-fi', 'wifi', 'интернет', 'ddos', 'vpn',
            'облачн', 'грид', 'внешн',
        ]

    if is_asu:
        # АСУ: нет BIOS/виртуализации/браузера в привычном понимании
        exclude += ['биос', 'грид', 'гипервизор', 'виртуальн', 'браузер']

    if not is_network and not is_asu:
        # Обычная ИС: неактуальны чисто сетевые/телеком-угрозы без указания
        # — оставляем всё, кроме специфики каналов связи, если их нет
        pass

    result = []
    for t in threats:
        tl = str(t).lower()
        if any(kw in tl for kw in exclude):
            continue
        result.append(t)

    if limit and len(result) > limit:
        result = result[:limit]
    return result
