#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Генерация YML-фидов Nordcross для Яндекс.Карт.

Источник — уже готовые YML-фиды салонов с business-car.ru (Bitrix), в которых
несколько марок (Geely, Nordcross). Скрипт скачивает фид каждого салона,
оставляет только офферы марки Nordcross (и связанные с ними категории) и
сохраняет результат в /var/www/bcfeeds/feed/yandexmaps/feed_nordcross_<SALON>.xml.

ВАЖНО: источник генерируется на лету и нестабилен — «холодный» запрос может
вернуть 0 байт или обрезанный XML. Поэтому скачивание делается с повторами и
проверкой целостности (XML парсится, корень <yml_catalog>, есть <shop>).
"""

import os
import time
import gzip
import logging
import urllib.request
import xml.etree.ElementTree as ET

# --- Настройки ---
SALONS      = ["GCK", "GCLO", "GCR", "GLB"]
SOURCE_TMPL = "https://business-car.ru/feed/yandex_new/?salon={salon}"
OUTPUT_DIR  = "/var/www/bcfeeds/feed/yandexmaps"
OUTPUT_TMPL = "feed_nordcross_{salon}.xml"
LOG_FILE    = "/var/www/bcfeeds/generate_nordcross_feeds.log"

KEEP_VENDOR = "nordcross"   # сравнение без учёта регистра
MAX_RETRIES = 6
RETRY_SLEEP = 5             # секунд между попытками
TIMEOUT     = 180
MIN_BYTES   = 1000          # ответ меньше — считаем браком (обрезка/заглушка)

HEADERS = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                  "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0 Safari/537.36",
    "Accept": "application/xml,text/xml,*/*",
    "Accept-Encoding": "gzip",
}

logging.basicConfig(
    filename=LOG_FILE,
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)


def fetch(salon):
    """Скачивает фид салона с повторами; возвращает разобранный ElementTree.Element или None."""
    url = SOURCE_TMPL.format(salon=salon)
    for attempt in range(1, MAX_RETRIES + 1):
        try:
            req = urllib.request.Request(url, headers=HEADERS)
            with urllib.request.urlopen(req, timeout=TIMEOUT) as r:
                data = r.read()
                if r.headers.get("Content-Encoding") == "gzip":
                    data = gzip.decompress(data)
            if len(data) < MIN_BYTES:
                raise ValueError(f"слишком короткий ответ ({len(data)} байт)")
            root = ET.fromstring(data)
            if root.tag != "yml_catalog" or root.find("shop") is None:
                raise ValueError("неожиданная структура XML")
            logging.info(f"[{salon}] скачано {len(data):,} байт (попытка {attempt})")
            return root
        except Exception as e:
            logging.warning(f"[{salon}] попытка {attempt}/{MAX_RETRIES} неудачна: {e}")
            if attempt < MAX_RETRIES:
                time.sleep(RETRY_SLEEP)
    logging.error(f"[{salon}] не удалось скачать корректный фид")
    return None


def filter_nordcross(root):
    """Оставляет в дереве только офферы Nordcross и связанные категории. Возвращает (kept, total)."""
    shop = root.find("shop")
    offers = shop.find("offers")
    total = 0
    kept = []
    if offers is not None:
        for offer in list(offers.findall("offer")):
            total += 1
            vendor = (offer.findtext("vendor") or "").strip().lower()
            if vendor == KEEP_VENDOR:
                kept.append(offer)
            else:
                offers.remove(offer)

    # Категории, на которые ссылаются оставшиеся офферы, плюс все их родители.
    cats = shop.find("categories")
    if cats is not None:
        by_id, parent = {}, {}
        for c in cats.findall("category"):
            cid = c.get("id")
            by_id[cid] = c
            parent[cid] = c.get("parentId")

        keep_ids = set()
        for offer in kept:
            cid = (offer.findtext("categoryId") or "").strip()
            while cid and cid in by_id and cid not in keep_ids:
                keep_ids.add(cid)
                cid = parent.get(cid)

        for c in list(cats.findall("category")):
            if c.get("id") not in keep_ids:
                cats.remove(c)

    return len(kept), total


def main():
    logging.info("=== Запуск генерации Nordcross-фидов ===")
    os.makedirs(OUTPUT_DIR, exist_ok=True)

    for salon in SALONS:
        root = fetch(salon)
        if root is None:
            logging.error(f"[{salon}] пропущен — сохранять нечего")
            continue
        try:
            kept, total = filter_nordcross(root)
            path = os.path.join(OUTPUT_DIR, OUTPUT_TMPL.format(salon=salon))
            ET.ElementTree(root).write(path, encoding="utf-8", xml_declaration=True)
            logging.info(f"[{salon}] сохранён {path}: Nordcross {kept} из {total} офферов")
        except Exception as e:
            logging.error(f"[{salon}] ошибка генерации: {e}")

    logging.info("=== Генерация завершена ===")


if __name__ == "__main__":
    main()
