# -*- coding: utf-8 -*-
"""DokoGen — Справочник переменных шаблонов и резолвер значений"""

from datetime import datetime
from typing import Dict, Any, List, Optional
import re

from . import declension as dc
from .models import Company, CommissionMember


def _norm_name(s):
    """Нормализация ФИО для сравнения: регистр, пробелы, точки, тире игнорируются."""
    s = (s or '').lower().replace('ё', 'е')
    return re.sub(r'[\s.,;:()«»"\'\-]+', '', s)


# ============================================================
# ОПИСАНИЕ ПЕРЕМЕННЫХ
# ============================================================
# Формат: (имя_переменной, группа, описание, ключ_резолвера)
# Группы: A — общие, B — 152-ФЗ

VARIABLE_DEFS = [
    # === Полное название организации (6 падежей) ===
    ('{{company_full_name}}', 'A', 'Полное наименование организации (им.п.)', 'company:full_name'),
    ('{{company_full_name_genitive}}', 'A', 'Полное наименование (род.п.)', 'decl:full_name:gent'),
    ('{{company_full_name_dative}}', 'A', 'Полное наименование (дат.п.)', 'decl:full_name:datv'),
    ('{{company_full_name_accs}}', 'A', 'Полное наименование (вин.п.)', 'decl:full_name:accs'),
    ('{{company_full_name_ablt}}', 'A', 'Полное наименование (твор.п.)', 'decl:full_name:ablt'),
    ('{{company_full_name_loct}}', 'A', 'Полное наименование (пр.п.)', 'decl:full_name:loct'),

    # === Краткое название организации (6 падежей) ===
    ('{{company_short_name}}', 'A', 'Краткое наименование (им.п.)', 'company:short_name'),
    ('{{company_short_name_genitive}}', 'A', 'Краткое наименование (род.п.)', 'decl:short_name:gent'),
    ('{{company_short_name_dative}}', 'A', 'Краткое наименование (дат.п.)', 'decl:short_name:datv'),
    ('{{company_short_name_accs}}', 'A', 'Краткое наименование (вин.п.)', 'decl:short_name:accs'),
    ('{{company_short_name_ablt}}', 'A', 'Краткое наименование (твор.п.)', 'decl:short_name:ablt'),
    ('{{company_short_name_loct}}', 'A', 'Краткое наименование (пр.п.)', 'decl:short_name:loct'),

    # === Должностные лица ===
    ('{{chief_position}}', 'A', 'Должность руководителя', 'company:chief_position'),
    ('{{chief_fio_initials}}', 'A', 'И.О. Фамилия руководителя', 'initials:chief'),
    ('{{chief_fio_short}}', 'A', 'Фамилия И.О. руководителя', 'short_fio:chief'),
    ('{{responsible_position}}', 'A', 'Должность ответственного', 'company:responsible_position'),
    ('{{responsible_fio_initials}}', 'A', 'И.О. Фамилия ответственного', 'initials:responsible'),
    ('{{responsible_fio_short}}', 'A', 'Фамилия И.О. ответственного', 'short_fio:responsible'),
    ('{{admin_position}}', 'A', 'Должность администратора', 'company:admin_position'),
    ('{{admin_fio_initials}}', 'A', 'И.О. Фамилия администратора', 'initials:admin'),
    ('{{admin_fio_short}}', 'A', 'Фамилия И.О. администратора', 'short_fio:admin'),

    # === Сведения об организации ===
    ('{{company_address}}', 'A', 'Адрес организации', 'company:address'),
    ('{{city_name}}', 'A', 'Город', 'company:city_name'),
    ('{{company_inn}}', 'A', 'ИНН', 'company:inn'),
    ('{{company_ogrn}}', 'A', 'ОГРН', 'company:ogrn'),
    ('{{company_ogrn_date}}', 'A', 'Дата ОГРН', 'company:ogrn_date'),
    ('{{company_kpp}}', 'A', 'КПП', 'company:kpp'),
    ('{{security_department}}', 'A', 'Наименование структурного подразделения по безопасности', 'company:security_department'),
    ('{{item.pd_actions}}', 'B', 'Действия с ПДн (в цикле ИС)', 'item:pd_actions'),

    # === Комиссия ===
    ('{{commission_list}}', 'A', 'Список комиссии', 'commission_list'),

    # === Специальные переменные ===
    ('{{date_year}}', 'A', 'Текущий год', 'date:year'),
    ('{{date}}', 'A', 'Текущая дата', 'date:full'),
    ('{{document_date}}', 'A', 'Дата документа', 'doc_date'),
    ('{{document_number}}', 'A', 'Номер документа', 'doc_number'),

    # === 152-ФЗ ===
    ('{{is_security_requirements}}', 'B', 'Требования уровня защищённости', 'security_reqs'),
    ('{{is_list}}', 'B', 'Список всех ИС (нумерованный)', 'is_list'),
    ('{{is_name}}', 'B', 'Наименование первой ИС', 'is:name'),
    ('{{name}}', 'B', 'Название ИС (алиас)', 'is:name'),
    ('{{description}}', 'B', 'Описание ИС (алиас)', 'is:description'),
    ('{{category}}', 'B', 'Категория ИС (алиас)', 'is:category'),
    ('{{number}}', 'B', 'Номер (алиас)', 'doc_number'),
    ('{{pd_count}}', 'B', 'Количество записей ПДн (алиас)', 'is:pd_count'),
    ('{{pd_list}}', 'B', 'Список ПДн (алиас)', 'is:pd_list'),
    ('{{pd_subjects}}', 'B', 'Субъекты ПДн (алиас)', 'is:pd_subjects'),
    ('{{threat_type}}', 'B', 'Тип угроз (алиас)', 'is:threat_type'),
    ('{{users}}', 'B', 'Пользователи ИС (алиас)', 'is:users'),
    ('{{defence_level}}', 'B', 'Уровень защищённости (алиас)', 'is:defence_level'),
    ('{{full_name_genitive}}', 'A', 'Полное наименование (род.п., алиас)', 'decl:full_name:gent'),
    ('{{document}}', 'A', 'Название бумажного документа (алиас)', 'company:paper_documents'),
    ('{{storage}}', 'A', 'Место хранения (алиас)', 'company:paper_storage'),
    ('{{responsible_fio_accs}}', 'A', 'ФИО ответственного (вин.п.)', 'fio_acl:responsible'),
    ('{{commission1}}', 'A', 'Должность члена комиссии 1', 'commission_member:0:position'),
    ('{{commission1_fio}}', 'A', 'ФИО члена комиссии 1', 'commission_member:0:fio'),
    ('{{commission2}}', 'A', 'Должность члена комиссии 2', 'commission_member:1:position'),
    ('{{commission2_fio}}', 'A', 'ФИО члена комиссии 2', 'commission_member:1:fio'),
    ('{{commission3}}', 'A', 'Должность члена комиссии 3', 'commission_member:2:position'),
    ('{{commission3_fio}}', 'A', 'ФИО члена комиссии 3', 'commission_member:2:fio'),
]

# Маппинг старых переменных <...> → {{...}}
OLD_VAR_MAP = {
    '<CompanyName1>': '{{company_full_name}}',
    '<CompanyName2>': '{{company_short_name}}',
    '<CompanyName1Gent>': '{{company_full_name_genitive}}',
    '<CompanyName2Gent>': '{{company_short_name_genitive}}',
    '<CompanyName1Datv>': '{{company_full_name_dative}}',
    '<CompanyName2Datv>': '{{company_short_name_dative}}',
    '<CompanyName1Accs>': '{{company_full_name_accs}}',
    '<CompanyName2Accs>': '{{company_short_name_accs}}',
    '<CompanyName1Ablt>': '{{company_full_name_ablt}}',
    '<CompanyName2Ablt>': '{{company_short_name_ablt}}',
    '<CompanyName1Loct>': '{{company_full_name_loct}}',
    '<CompanyName2Loct>': '{{company_short_name_loct}}',
    '<Address>': '{{company_address}}',
    '<INN>': '{{company_inn}}',
    '<OGRN>': '{{company_ogrn}}',
    '<KPP>': '{{company_kpp}}',
    '<ChiefFio>': '{{chief_fio_initials}}',
    '<ChiefFioShort>': '{{chief_fio_short}}',
    '<ChiefPosition>': '{{chief_position}}',
    '<RspFio>': '{{responsible_fio_initials}}',
    '<RspFioShort>': '{{responsible_fio_short}}',
    '<RspPosition>': '{{responsible_position}}',
    '<CommissionList>': '{{commission_list}}',
    '<DocumentNumber>': '{{document_number}}',
    '<DocumentDate>': '{{document_date}}',
    '<Date>': '{{date}}',
    '<SurveyDate>': '{{document_date}}',
}

VAR_PATTERN = re.compile(r'\{\{[^}]+\}\}')
OLD_VAR_PATTERN = re.compile(r'<[A-Za-z0-9_]+>')


# ============================================================
# ПОСТРОЕНИЕ СЛОВАРЯ ЗАМЕН
# ============================================================

def build_replacements(
    company: Company,
    is_list: List = None,
    doc_number: str = "",
    doc_date: str = "",
    direction: str = "152fz",
) -> Dict[str, str]:
    """Строит полный словарь замен {{переменная}} → значение."""
    if is_list is None:
        is_list = []

    # ---------- Комиссия ----------
    commission_data = _build_commission_data(company)

    # ---------- Контекст ----------
    ctx = {
        'company': company,
        'is_list': is_list,
        'commission_data': commission_data,
        'doc_number': doc_number or '',
        'doc_date': doc_date or '',
        'direction': direction,
    }

    replacements = {}
    for var_name, group, desc, source in VARIABLE_DEFS:
        if direction == '152fz' and group not in ('A', 'B'):
            continue
        value = _resolve(source, ctx)
        replacements[var_name] = str(value) if value else '—'

    # Индексированные переменные ИС (is_name_1, is_pd_list_2 и т.д.)
    for i, isys in enumerate(is_list, 1):
        _add_is_indexed_vars(replacements, isys, i)

    # Циклы
    replacements['_loops'] = _build_loop_data(company, is_list)
    return replacements


def _commission_role_display(members) -> List[str]:
    """Группирует роли комиссии для вывода:
    - Председатель/Секретарь/Заместитель — отдельной строкой со своей ролью;
    - обычные члены — первый получает «Члены комиссии», остальные — пустую роль
      (чтобы не повторять «Член комиссии» в каждой строке)."""
    result = []
    member_used = False
    for m in members:
        role = (m.role or 'член комиссии').strip()
        role_lower = role.lower()
        if ('председател' in role_lower or 'секретар' in role_lower
                or 'заместител' in role_lower or 'зам. председателя' in role_lower):
            result.append(role)
            continue
        # обычный член комиссии
        if not member_used:
            result.append('Члены комиссии')
            member_used = True
        else:
            result.append('')
    return result


def _build_commission_data(company: Company) -> Dict[str, str]:
    data = {}
    members = company.commission or []

    display_roles = _commission_role_display(members)
    lines = []
    for i, (m, role_disp) in enumerate(zip(members, display_roles), 1):
        if role_disp:
            line = (f"{i}. {role_disp}: {m.position} — {m.fio}" if m.position
                    else f"{i}. {role_disp}: {m.fio}")
        else:
            line = (f"{i}. {m.position} — {m.fio}" if m.position
                    else f"{i}. {m.fio}")
        lines.append(line)
    data['commission_list'] = '\n'.join(lines) if lines else '—'

    return data


def _add_is_indexed_vars(replacements: Dict[str, str], isys, idx: int):
    fields = {
        'is_name': isys.name or '—',
        'is_address': getattr(isys, 'address', '') or '—',
        'is_description': isys.description or '—',
        'is_has_internet': 'да' if isys.is_internet else 'нет',
        'is_defence_level': str(isys.defence_level or '—'),
        'is_category': ', '.join(isys.personal_data_category) or isys.category or '—',
        'is_pd_list': ', '.join(isys.personal_data_list or []) or '—',
        'is_users': ', '.join(isys.users_list or []) or '—',
        'is_pd_count': str(isys.pd_count or '—'),
        'is_purpose': isys.purpose or '—',
        'is_structure': getattr(isys, 'structure', '') or '—',
    }
    for f, v in fields.items():
        replacements[f'{{{{{f}_{idx}}}}}'] = str(v)


def _build_loop_data(company: Company, is_list: List) -> Dict[str, List[Dict]]:
    loops = {}

    # Комиссия
    members = company.commission or []
    if members:
        display_roles = _commission_role_display(members)
        items = []
        for m, role_disp in zip(members, display_roles):
            pos = m.position or '—'
            fio = m.fio or '—'
            if role_disp:
                full = f"{role_disp} {pos} — {fio}" if pos != '—' else f"{role_disp} — {fio}"
            else:
                full = f"{pos} — {fio}" if pos != '—' else fio
            # Строка подписи: «___________ / » (только подчёркивание и слэш).
            # Роль НЕ включаем — в шаблонах она выводится отдельной колонкой {{item.role}},
            # иначе получается дубль «Председатель комиссии | Председатель комиссии: ___».
            if 'Председатель' in role_disp:
                sig = f"{'_' * 11} / "
                sig_full = f"{role_disp}: {'_' * 11} / "
            elif role_disp:
                sig = f"{'_' * 17} / "
                sig_full = f"{role_disp}: {'_' * 17} / "
            else:
                sig = f"{'_' * 17} / "
                sig_full = f"{'_' * 17} / "
            initials = dc.get_initials(fio) if fio != '—' else ''
            items.append({
                'role': role_disp,
                'position': pos,
                'fio': fio,
                'fio_initials': dc.get_initials(fio) if fio != '—' else '',
                'fio_short': dc.get_short_fio(fio) if fio != '—' else '',
                'full': full,
                # Рамка подписи без роли и ФИО: «___________ / » (ФИО добавится)
                'signature': sig,
                # Полная подпись с ролью и ФИО: «Роль: ___________ / И.О. Фамилия»
                'signature_full': (sig_full + initials).strip(),
            })
        loops['commission'] = items

    # ИС
    if is_list:
        items = []
        for isys in is_list:
            # Пользователи ИС для вложенного цикла <!-- loop:users_loop -->
            # ВСЕ пользователи, включая администратора (без дублей по ФИО)
            seen = set()
            users_loop = []
            for u in (isys.users_list or []):
                key = _norm_name(u)
                if key and key in seen:
                    continue
                seen.add(key)
                users_loop.append({'fio': u, 'position': ''})
            items.append({
                'name': isys.name or '—',
                'description': isys.description or '—',
                'address': getattr(isys, 'address', '') or '—',
                'is_has_lan': 'Да' if isys.is_local_network else 'Нет',
                'has_internet': 'да' if isys.is_internet else 'нет',
                'defence_level': str(isys.defence_level or '—'),
                'threat_type': str(isys.threat_type or '3'),
                'category': ', '.join(isys.personal_data_category) or isys.category or '—',
                'pd_list': ', '.join(isys.personal_data_list or []) or '—',
                'pd_count': str(isys.pd_count or '—'),
                'users': ', '.join(isys.users_list or []) or '—',
                'users_loop': users_loop,
                'defense_tools_list': ', '.join(isys.defense_tools_list or []) or '—',
                'processing_modes': isys.processing_modes or '—',
                'pd_subjects': '; '.join(isys.pd_subjects_list or []) or '—',
                'is_purpose': isys.purpose or '—',
                'structure': getattr(isys, 'structure', '') or '—',
                'software': getattr(isys, 'software', '') or '—',
                'pd_actions': '; '.join(isys.pd_actions_list or []) or '—',
            })
        loops['information_systems'] = items

    # Бумажные документы
    def _storage_with_prep(storage: str) -> str:
        """«сейф» → «в сейфе» (предлог + предложный падеж).
        Если уже начинается с предлога — оставить как есть."""
        if not storage:
            return ''
        s = storage.strip()
        low = s.lower()
        if low.startswith(('в ', 'на ', 'под ', 'за ', 'у ', 'при ', 'из ', 'со ', 'во ', 'над ', 'перед ')):
            return s
        words = s.split()
        out = []
        # Исключения предложного падежа: «в шкафу», «в углу» (не «в шкафе»)
        loct_exceptions = {'шкаф': 'шкафу', 'угол': 'углу', 'край': 'краю', 'рот': 'рту', 'мост': 'мосту'}
        for w in words:
            core = w.strip('.,;')
            if not core or core.isdigit() or core.startswith('№'):
                out.append(w)
                continue
            if core.lower() in loct_exceptions:
                out.append(loct_exceptions[core.lower()])
                continue
            try:
                p = dc.morph.parse(core)[0]
                inf = p.inflect({'loct'})
                if inf:
                    out.append(inf.word)
                    continue
            except Exception:
                pass
            out.append(w)
        return 'в ' + ' '.join(out)

    paper_list = getattr(company, 'paper_documents_list', None) or []
    if paper_list:
        items = []
        for p in paper_list:
            storage = p.storage or ''
            items.append({
                'document': p.name or '',
                'storage': _storage_with_prep(storage),   # «в сейфе»
                'storage_raw': storage,                    # «сейф» (как введено)
            })
        loops['paper_documents'] = items

    # Сотрудники с доступом
    employees = getattr(company, 'employees_access', None) or []
    if employees:
        items = []
        for e in employees:
            items.append({
                'position': e.get('position', ''),
                'fio': e.get('fio', ''),
            })
        loops['employees_access'] = items

    return loops


# ============================================================
# РЕЗОЛВЕР ЗНАЧЕНИЙ
# ============================================================

def _resolve(source: str, ctx: Dict) -> str:
    """Преобразует ключ-источник в значение, подставляя из контекста."""
    company = ctx.get('company')
    is_list = ctx.get('is_list', [])
    commission_data = ctx.get('commission_data', {})
    doc_number = ctx.get('doc_number', '')
    doc_date = ctx.get('doc_date', '')

    # Прямые поля Company
    if source.startswith('company:'):
        field = source[8:]
        return str(getattr(company, field, '') or '')

    # Склонение названия
    if source.startswith('decl:'):
        _, field, case = source.split(':')
        name = getattr(company, field, '') or ''
        case_gr = dc._normalize_case(case)
        # Сначала берём ГОТОВОЕ склонение из модели (пользователь мог
        # подставить через Морфер/ИИ или ввести вручную), пересчитываем
        # только если поле пустое
        case_idx = {'nomn': 1, 'gent': 2, 'datv': 3, 'accs': 4, 'ablt': 5, 'loct': 6}.get(case_gr)
        if case_idx:
            if field == 'full_name':
                ready = getattr(company, f'company_name_{case_idx}', '') or ''
                if ready:
                    return ready
            elif field == 'short_name':
                ready = getattr(company, f'short_name_{case_idx}', '') or ''
                if ready:
                    return ready
        return dc.company_name_decline(name, case_gr) if name else '—'

    # Форматы ФИО
    if source.startswith('short_fio:'):
        who = source.split(':', 1)[1]
        fio = _get_fio(company, who)
        return dc.get_short_fio(fio) if fio else '—'
    if source.startswith('initials:'):
        who = source.split(':', 1)[1]
        fio = _get_fio(company, who)
        return dc.get_initials(fio) if fio else '—'

    # Комиссия
    if source in commission_data:
        return str(commission_data[source])

    # Даты
    if source.startswith('date:'):
        fmt = source.split(':', 1)[1]
        now = datetime.now()
        if fmt == 'year':
            return str(now.year)
        return now.strftime('%d.%m.%Y')
    if source == 'doc_date':
        return doc_date or datetime.now().strftime('%d.%m.%Y')
    if source == 'doc_number':
        num = str(doc_number).strip()
        return re.sub(r'^[Nn]?[oо]?[№#]\s*', '', num) or '—'

    # Требования защищённости
    if source == 'security_reqs':
        return _get_security_text(ctx)

    # IS-алиасы (первая ИС)
    if source.startswith('is:'):
        field = source[3:]
        if is_list:
            first = is_list[0]
            field_map = {
                'name': 'name',
                'description': 'description',
                'category': 'category',
                'defence_level': 'defence_level',
                'threat_type': 'threat_type',
                'pd_list': lambda: ', '.join(first.personal_data_list or []),
                'pd_count': 'pd_count',
                'pd_subjects': lambda: '; '.join(first.pd_subjects_list or []),
                'users': lambda: ', '.join(first.users_list or []),
            }
            if field in field_map:
                val = field_map[field]
                if callable(val):
                    return val() or '—'
                return str(getattr(first, val, '') or '—') if isinstance(val, str) else '—'
        return '—'
    
    # Список ИС
    if source == 'is_list':
        if is_list:
            return '\n'.join(f"{i}. {isys.name}" for i, isys in enumerate(is_list, 1))
        return '—'
    if source == 'is_list_comma':
        if is_list:
            return ', '.join(isys.name for isys in is_list)
        return '—'
    
    # Члены комиссии
    if source.startswith('commission_member:'):
        parts = source.split(':')
        idx, field = int(parts[1]), parts[2]
        members = getattr(company, 'commission', None) or []
        if idx < len(members):
            return str(getattr(members[idx], field, '') or '—')
        return '—'
    
    # Склонение ФИО по падежам
    if source.startswith('fio_acl:'):
        who = source.split(':', 1)[1]
        fio = _get_fio(company, who)
        return dc.decline(fio, 'accs') if fio else '—'

    return '—'


def _get_fio(company: Company, who: str) -> str:
    mapping = {
        'chief': 'chief_fio',
        'responsible': 'responsible_fio',
        'admin': 'admin_fio',
    }
    field = mapping.get(who, who + '_fio')
    return getattr(company, field, '') or ''


def _get_security_text(ctx: Dict) -> str:
    """Возвращает текст требований для уровня защищённости."""
    defence_level = ctx.get('extended_fields', {}).get('defence_level', '4')
    is_list = ctx.get('is_list', [])
    if not defence_level and is_list:
        defence_level = str(getattr(is_list[0], 'defence_level', '4') or '4')
    dl = str(defence_level).strip()[0]

    TEXTS = {
        '1': """Для обеспечения 1-го уровня защищённости...""",
        '2': """Для обеспечения 2-го уровня защищённости...""",
        '3': """Для обеспечения 3-го уровня защищённости...""",
        '4': """Для обеспечения 4-го уровня защищённости персональных данных необходимо:
— режим безопасности помещений;
— сохранность носителей ПДн;
— утверждение перечня лиц с доступом к ПДн;
— использование сертифицированных СЗИ.""",
    }
    return TEXTS.get(dl, TEXTS['4'])
