# extractor.py  ·  обновлён 2025-07-23
from json import load, loads
from os.path import abspath
from bs4 import BeautifulSoup
from lxml import etree
from re import split, search, sub
# ─────────────────────────────────────────────────────────────────────────────
def extract_components_zarahome(parts):
    composition = []
    for part in parts:
        if part.get("areas") and part.get("description"):
            if len(parts) != 1:
                composition.append(part["description"])
            for area in part["areas"]:
                area_name = area["description"]
                percentage_area = area["percentageArea"]

                composition.append(f"{area_name} ({percentage_area})")
                for component in area["components"]:
                    material = component["material"]
                    percentage = component["percentage"]

                    composition.append(f"{percentage} {material}")
        elif part.get("components") and part.get("description"):
            if len(parts) != 1:
                composition.append(part["description"])
            for component in part["components"]:
                material = component["material"]
                percentage = component["percentage"]

                composition.append(f"{percentage} {material}")

    return composition
# ─────────────────────────────────────────────────────────────────────────────


class Extractor:
    # ----------------------------------------------------------------
    def __init__(self, json_data):

        self.methods = {
            "": (self.default_extract_method, []),

            "zarahome": (self.zarahome_extract_method, [
                "Краткое описание",
                "Артикул",
                "SKU",
                "PartNumber",
                "Название товара или услуги",
                "Полное описание",
                "Образец цвета",
                "Свойство: Цвет",
                "Свойство: Размер",
                "Цена закупки",
                "Свойство: Вес(г)",
                "Наличие на сайте",
                "Изображения",
                "Изображения варианта",
                "Параметр: Состав",
                "Параметр: Уход",
                "Параметр: Происхождение",
                "Размещение на сайте",
                "Свойство: Бренд"
            ]),

            # заглушки для старых магазинов
            "zara":      (self.zara_extract_method,      []),
            "eobuwie":   (self.eobuwie_extract_method,   []),
            "decathlon": (self.decathlon_extract_method, []),
            "chanel":    (self.chanel_extract_method,    []),
        }

        self.method  = json_data["method"]
        self.tags    = json_data["tags"]
        self.headers = self.methods[self.method][1].copy()

        for tag in self.tags:
            self.headers.insert(tag["column_number"], tag["column_name"])

    # ----------------------------------------------------------------
    # общие утилиты
    def extract(self, parser, recorder, categories):
        self.methods[self.method][0](parser, recorder, categories)

    def default_extract_method(self, *a, **kw):
        print("Default extractor → nothing to do.")

    def tags_extract(self, soup, row):
        dom = etree.HTML(str(soup))
        for tag in self.tags:
            res = dom.xpath(tag["xpath"])
            col = ""
            if res:
                for el in res:
                    col += ''.join(el.itertext()).strip() + "\n"
            row.insert(tag["column_number"], col)

    # ----------------------------------------------------------------
    # заглушки для других методов
    def zara_extract_method(self, *_, **__):      print("ZARA extractor disabled.")
    def eobuwie_extract_method(self, *_, **__):   print("Eobuwie extractor disabled.")
    def decathlon_extract_method(self, *_, **__): print("Decathlon extractor disabled.")
    def chanel_extract_method(self, *_, **__):    print("Chanel extractor disabled.")

    # ----------------------------------------------------------------
    #                Z  A  R  A   H  O  M  E
    # ----------------------------------------------------------------
    def zarahome_extract_method(self, parser, recorder, categories):

            BASE_API   = "https://www.zarahome.com/itxrest/3/catalog/store/85009924/80290000"
            USER_BRAND = "ZARAHOME"

            for i, category in enumerate(categories):
                table = [self.headers]
                print(f"Categories: {i + 1} / {len(categories)}  {category}")

                # ── HTML категории ───────────────────────────────────────
                html = parser.parse(category)
                if html is None:
                    print("Extractor Error: empty page"); continue
                soup = BeautifulSoup(html, "html.parser")

                script = soup.select_one("#serverApp-state")

    ####### Вывод того что есть Начало
    #            dump_name = f"state_dump_{int(time.time())}.json"
    #            pathlib.Path(dump_name).write_text(script.string, encoding="utf-8")
    #            print(f"🛈 serverApp-state saved → {dump_name}")
    #
    #            state = loads(script.string)
    #            print("TOP-LEVEL KEYS:", list(state.keys())[:20])
    #            print("inditex-data KEYS:", list(state.get("inditex-data", {}).keys()))
    ####### Вывод того что есть Конец

                if not script:
                    print("Extractor Error: script#serverApp-state not found"); continue
                try:
                    state = loads(script.string)
                except Exception as e:
                    print(f"Extractor Error: bad JSON ({e})"); continue

                # ── category_id ──────────────────────────────────────────
                cdata  = state.get("inditex-data", {})
                cat_id = (cdata.get("iCategoryId") or
                        cdata.get("categoryId")  or
                        cdata.get("iCategoryJSON", {}).get("id"))
                if not cat_id:
                    for k in state:
                        m = search(r"/category/(\d+)/product", k)
                        if m: cat_id = m.group(1); break
                if not cat_id:
                    print("Extractor Error: cannot detect category_id"); continue

                # ── блок с продуктами или их ID ─────────────────────────
                key = next((k for k in state if f"/category/{cat_id}/product" in k), None)
                if not key:
                    print("Extractor Error: products block not found"); continue
                prod_block = state[key]

                summaries = []

                # ★ Старая схема: products уже внутри
                if "products" in prod_block:
                    for grp in prod_block["products"]:
                        summaries += grp["bundleProductSummaries"]

                # ★ Новая схема: нужно тянуть по productIds
                else:
                    ids = (prod_block.get("productIds") or
                        prod_block.get("sortedProductIds") or
                        prod_block.get("sortedProductIdsByPricesAsc") or [])
                    print(f"→ pulling {len(ids)} products via API")
                    CHUNK = 20
                    for p in range(0, len(ids), CHUNK):
                        ids_chunk = ",".join(map(str, ids[p:p+CHUNK]))
                        api = (f"{BASE_API}/productsArray"
                            f"?languageId=-1&productIds={ids_chunk}&appId=1")
                        data = parser.parse(api, return_type="json")
                        summaries += data.get("products", [])
                    print("DEBUG summaries count:", len(summaries))
                    for p in summaries:
                            print("  •", p.get("id"), p.get("productUrl"))

                # ── путь категории для CSV ───────────────────────────────
    #         cat_json  = cdata.get("iCategoryJSON", {})
    #         cat_title = "/".join(cat_json.get("parentNames", []) +
    #                              [cat_json.get("name", "")])
    #         cat_path  = f"Каталог/ZaraHome/{cat_title}"
                
                seen = set()
                for n, prod in enumerate(summaries, 1):

                    short_url = prod.get("productUrl")
                    if not short_url or short_url in seen:
                        continue
                    seen.add(short_url)
                    print(f"Products: {n} / {len(summaries)}   "
                        f"https://www.zarahome.com/pl/{short_url}")

                    # ── у некоторых prod нет вариантов → смотрим глубже ──
                    variant_products = []
                    if prod.get("detail", {}).get("colors"):
                        variant_products.append(prod)
                    elif prod.get("bundleProductSummaries"):
                        variant_products += prod["bundleProductSummaries"]
                    else:
                        variant_products.append(prod)   # моно-товар без вариантов

                    # ── обрабатываем каждый vprod (вариант или сам товар) ─
                    for vprod in variant_products:
                        det = vprod["detail"]

                        url_full   = f"https://www.zarahome.com/pl/en/{vprod.get('productUrl','')}"
                        name       = vprod.get("name", "")
                        article    = det["displayReference"]
                        root_price = int(vprod.get("price", 0)) / 100
                        root_wt    = vprod.get("weight", "")

                        # ── все изображения ────────────────────────────
                        raw_xmedia  = (det.get("xmedia") or vprod.get("xmedia") or [])
                        default_idx = det.get("xmediaDefaultSet")
                        if isinstance(raw_xmedia, list) and raw_xmedia:
                            media_sets = [raw_xmedia[default_idx]] if isinstance(default_idx, int) else raw_xmedia
                        elif isinstance(raw_xmedia, dict):
                            media_sets = [raw_xmedia]
                        else:
                            media_sets = []

                        all_imgs = [
                            f"https://static.zarahome.net/8/photos4{loc['path']}/{m['idMedia']}2.jpg"
                            for loc in media_sets
                            for m   in loc["xmediaItems"][0]["medias"]
                        ]
                        all_imgs_s = "\n".join(all_imgs)

                        # ── состав / уход / происхождение ───────────────
                        comp_block = det.get("compositionDetail")
                        comp_txt = ""
                        if comp_block and comp_block.get("parts"):
                            comp_txt = "\n".join(
                                extract_components_zarahome(comp_block["parts"])
                            )
                        care = "\n".join(c["description"] for c in det.get("care", []))

                        trace = ""
                        if det.get("traceability"):
                            trace = "\n".join(
                                f"{v['name']}\n" + "\n".join(v["country"])
                                for v in det["traceability"].values()
                                if isinstance(v, dict) and v.get("country") and v.get("name")
                            )

                        # ── цвета и размеры ─────────────────────────────
                        colors_list = det.get("colors") or []
                        if not colors_list:      # моно-товар без цветов
                            colors_list = [{
                                "id": 0,
                                "name": "DEFAULT",
                                "image": {"url": ""},
                                "sizes": [{
    #                               "visibilityValue": "SHOW",
                                    "name": "",
                                    "description": "",
                                    "weight": root_wt,
                                    "price":  vprod.get("price", 0)
                                }]
                            }]

                        serial = 0
                        for clr in colors_list:
                            if clr.get("image") is None and clr["name"] != "DEFAULT":
                                continue

                            clr_code  = clr.get("id")
                            clr_name  = clr.get("name", "")
                            clr_image = ""
                            if clr.get("image") and clr["image"].get("url"):
                                clr_image = (f"https://static.zarahome.net/8/photos4"
                                            f"{clr['image']['url']}_3_1_5.jpg")

                            # картинки именно этого цвета
                            media_sets = [loc for loc in media_sets
                                        if loc.get("colorCode") == clr_code] or media_sets
                            clr_imgs = [
                                f"https://static.zarahome.net/8/photos4{loc['path']}/{m['idMedia']}2.jpg"
                                for loc in media_sets
                                for m   in loc["xmediaItems"][0]["medias"]
                            ]
                            clr_imgs_s = "\n".join(clr_imgs)

                            for size in clr["sizes"]:
    #                            if size["visibilityValue"] != "SHOW":
    #                                continue
                                #suffix = "" if serial == 0 else f"-{serial}"                               Раскомментить если надо добавлять "-1,2,3" к артикуду при повторениях

                                serial += 1
                                visibility = size.get("visibilityValue", "UNKNOWN")
                                size_name  = size.get("name", "")
                                size_descr = size.get("description", "")
                                size_full  = f"{size_descr} ({size_name})" if size_descr else size_name
                                size_weight = size.get("weight") or root_wt
                                size_price  = int(size.get("price") or vprod.get("price", 0)) / 100

                                # ── путь категории из sectionNameEN / familyName / subFamilyName
                                sec  = vprod.get("sectionNameEN")   or ""   # верхний уровень
                                fam  = vprod.get("familyName")      or ""   # семья
                                sub  = vprod.get("subFamilyName")   or ""   # подсемья

                                cat_parts = [p for p in (sec, fam, sub) if p]   # убираем пустые
                                cat_path  = "Каталог/ZaraHome/" + "/".join(cat_parts)
                                
                                sku_val       = size.get("sku", "")
                                partnumber_val = size.get("partnumber", "")
                                
                                table.append([
                                    url_full,
                                    f"{article}", #{suffix}",                                               Раскомментить если надо добавлять "-1,2,3" к артикуду при повторениях
                                    name,
                                    sku_val,              # ← SKU
                                    partnumber_val,       # ← PartNumber
                                    det.get("longDescription", ""),
                                    clr_image,
                                    clr_name,
                                    size_full,
                                    size_price,
                                    size_weight,
                                    visibility,
                                    all_imgs_s,
                                    clr_imgs_s,
                                    comp_txt,
                                    care,
                                    trace,
                                    cat_path,
                                    USER_BRAND
                                ])

                # ── запись CSV ──────────────────────────────────────────
                csv_name = category.split("/")[-1]
                recorder.record(csv_name, table)


def get_extractor():
    with open(abspath("parse_settings.json"), "r", encoding="utf-8") as file:
        return Extractor(load(file))