# -*- coding: utf-8 -*-
"""
Синхронизация прайса сайта с Excel.

Как пользоваться:
1. Откройте prices.xlsx и правьте таблицу на листе "Цены"
2. Запустите файл "обновить-цены.bat" (двойной клик)
3. Залейте на хостинг обновлённый prices-data.js

Колонки Excel:
A Раздел | B Подкатегория | C Название | D Цена (число) | E Ед.изм | F Кол-во | G Показывать (Да/Нет)
"""

from __future__ import annotations

import json
import re
import sys
from pathlib import Path

try:
    import openpyxl
    from openpyxl.styles import Alignment, Font, PatternFill, Border, Side
    from openpyxl.utils import get_column_letter
    from openpyxl.worksheet.datavalidation import DataValidation
except ImportError:
    print("Нужна библиотека openpyxl. Установите: pip install openpyxl")
    sys.exit(1)

ROOT = Path(__file__).resolve().parent
XLSX_PATH = ROOT / "prices.xlsx"
JS_PATH = ROOT / "prices-data.js"

YES_VALUES = {"да", "yes", "y", "1", "true", "+", "д"}


def js_str(value: str) -> str:
    return json.dumps(value, ensure_ascii=False)


def parse_prices_js(text: str) -> list[dict]:
    """Parse controlled prices-data.js format into nested Python structure."""
    cats: list[dict] = []

    # Top-level categories: either with children or with items
    # Split by top-level objects roughly via brace matching after window assignment
    body = re.sub(r"^[\s\S]*?window\.PRICES_DATA\s*=\s*", "", text.strip())
    body = re.sub(r";\s*$", "", body).strip()
    if not (body.startswith("[") and body.endswith("]")):
        raise ValueError("Некорректный формат prices-data.js")

    # Tokenize top-level category objects by scanning braces
    inner = body[1:-1]
    objects: list[str] = []
    depth = 0
    start = None
    for i, ch in enumerate(inner):
        if ch == "{":
            if depth == 0:
                start = i
            depth += 1
        elif ch == "}":
            depth -= 1
            if depth == 0 and start is not None:
                objects.append(inner[start : i + 1])
                start = None

    def parse_items(blob: str) -> list[dict]:
        items = []
        for item in re.finditer(
            r'\{\s*name:\s*("(?:\\.|[^"\\])*")\s*,\s*price:\s*("(?:\\.|[^"\\])*")\s*,\s*amount:\s*("(?:\\.|[^"\\])*")\s*,\s*unit:\s*("(?:\\.|[^"\\])*")\s*\}',
            blob,
        ):
            items.append(
                {
                    "name": json.loads(item.group(1)),
                    "price": json.loads(item.group(2)),
                    "amount": json.loads(item.group(3)),
                    "unit": json.loads(item.group(4)),
                }
            )
        return items

    for obj in objects:
        title_m = re.search(r'title:\s*("(?:\\.|[^"\\])*")', obj)
        if not title_m:
            continue
        title = json.loads(title_m.group(1))
        children_m = re.search(r"children:\s*\[([\s\S]*)\]\s*$", obj.strip().rstrip("}"))
        # Better: find children array bounds
        children_idx = obj.find("children:")

        if children_idx != -1:
            ch_start = obj.find("[", children_idx)
            # find matching closing bracket for children array
            depth = 0
            ch_end = None
            for i in range(ch_start, len(obj)):
                if obj[i] == "[":
                    depth += 1
                elif obj[i] == "]":
                    depth -= 1
                    if depth == 0:
                        ch_end = i
                        break
            children_blob = obj[ch_start + 1 : ch_end] if ch_end else ""
            children = []
            # child objects
            depth = 0
            start = None
            for i, ch in enumerate(children_blob):
                if ch == "{":
                    if depth == 0:
                        start = i
                    depth += 1
                elif ch == "}":
                    depth -= 1
                    if depth == 0 and start is not None:
                        child_obj = children_blob[start : i + 1]
                        ctitle_m = re.search(r'title:\s*("(?:\\.|[^"\\])*")', child_obj)
                        if not ctitle_m:
                            continue
                        children.append(
                            {
                                "title": json.loads(ctitle_m.group(1)),
                                "items": parse_items(child_obj),
                            }
                        )
                        start = None
            cats.append({"title": title, "children": children})
        else:
            cats.append({"title": title, "items": parse_items(obj)})

    return cats


def fmt_price_number(value) -> str:
    try:
        num = float(value)
        if num.is_integer():
            num = int(num)
        return f"{num:,}".replace(",", " ")
    except Exception:
        return str(value).strip()


def extract_number_from_price(price: str):
    """'от 1 200 ₽/м²' -> 1200"""
    if price is None:
        return ""
    if isinstance(price, (int, float)):
        return price
    s = str(price)
    s = re.sub(r"^от\s+", "", s, flags=re.I)
    s = re.sub(r"\s*₽.*$", "", s)
    s = s.replace(" ", "").replace("\u00a0", "")
    s = s.replace(",", ".")
    try:
        num = float(s)
        return int(num) if num.is_integer() else num
    except Exception:
        return s


def build_price_string(number, unit: str) -> str:
    unit = (unit or "").strip()
    num = fmt_price_number(number)
    if unit and unit != "—":
        return f"от {num} ₽/{unit}"
    return f"от {num} ₽"


def flatten_for_excel(data: list[dict]) -> list[dict]:
    rows = []
    for cat in data:
        section = cat.get("title", "")
        children = cat.get("children") or []
        if children:
            for child in children:
                sub = child.get("title", "")
                for item in child.get("items") or []:
                    rows.append(
                        {
                            "section": section,
                            "subcategory": sub,
                            "name": item.get("name", ""),
                            "price": extract_number_from_price(item.get("price", "")),
                            "unit": item.get("unit", ""),
                            "amount": item.get("amount", "1"),
                            "show": "Да",
                        }
                    )
        else:
            for item in cat.get("items") or []:
                rows.append(
                    {
                        "section": section,
                        "subcategory": "",
                        "name": item.get("name", ""),
                        "price": extract_number_from_price(item.get("price", "")),
                        "unit": item.get("unit", ""),
                        "amount": item.get("amount", "1"),
                        "show": "Да",
                    }
                )
    return rows


def export_xlsx_from_js() -> None:
    if not JS_PATH.exists():
        print(f"Не найден {JS_PATH.name}")
        sys.exit(1)

    data = parse_prices_js(JS_PATH.read_text(encoding="utf-8"))
    rows = flatten_for_excel(data)

    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "Цены"

    headers = ["Раздел", "Подкатегория", "Название работы", "Цена", "Ед.изм", "Кол-во", "Показывать"]
    header_fill = PatternFill("solid", fgColor="1F4E79")
    header_font = Font(color="FFFFFF", bold=True)
    thin = Border(
        left=Side(style="thin", color="BDD7EE"),
        right=Side(style="thin", color="BDD7EE"),
        top=Side(style="thin", color="BDD7EE"),
        bottom=Side(style="thin", color="BDD7EE"),
    )

    for col, title in enumerate(headers, start=1):
        cell = ws.cell(1, col, title)
        cell.fill = header_fill
        cell.font = header_font
        cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)

    for i, row in enumerate(rows, start=2):
        values = [
            row["section"],
            row["subcategory"],
            row["name"],
            row["price"],
            row["unit"],
            row["amount"],
            row["show"],
        ]
        for col, value in enumerate(values, start=1):
            cell = ws.cell(i, col, value)
            cell.border = thin
            cell.alignment = Alignment(vertical="center", wrap_text=True)
            if col == 4:
                cell.alignment = Alignment(horizontal="right", vertical="center")

    widths = [34, 18, 70, 12, 10, 10, 12]
    for i, width in enumerate(widths, start=1):
        ws.column_dimensions[get_column_letter(i)].width = width

    ws.freeze_panes = "A2"
    ws.auto_filter.ref = f"A1:G{len(rows) + 1}"

    dv = DataValidation(type="list", formula1='"Да,Нет"', allow_blank=False)
    dv.error = "Выберите Да или Нет"
    dv.errorTitle = "Показывать"
    ws.add_data_validation(dv)
    dv.add(f"G2:G{max(2, len(rows) + 200)}")

    # Instruction sheet
    info = wb.create_sheet("Инструкция", 0)
    info["A1"] = "Как обновлять цены на сайте"
    info["A1"].font = Font(bold=True, size=14)
    lines = [
        "",
        "1) Редактируйте только лист «Цены».",
        "2) Можно менять названия, цены, единицы, количество.",
        "3) Чтобы временно скрыть позицию с сайта — поставьте в колонке «Показывать» значение «Нет».",
        "4) Чтобы удалить позицию навсегда — удалите строку.",
        "5) Чтобы добавить новую позицию — добавьте новую строку в конец таблицы.",
        "6) Для разделов с подкатегориями заполняйте и «Раздел», и «Подкатегория»",
        "   (например: Раздел = Демонтажные работы, Подкатегория = Полы).",
        "7) Для разделов без подкатегорий оставьте «Подкатегория» пустой",
        "   (например: Электромонтажные работы).",
        "8) В колонке «Цена» пишите только число, например: 650",
        "   На сайте автоматически станет: от 650 ₽/шт (если ед.изм = шт).",
        "9) После правок закройте Excel и запустите файл «обновить-цены.bat».",
        "10) На хостинг REG.RU залейте обновлённый файл prices-data.js",
        "    (и при желании сам prices.xlsx — для вашего удобного хранения).",
        "",
        "Важно: сайт сам Excel не читает. Сначала bat обновляет prices-data.js, потом этот файл идёт на сайт.",
    ]
    for idx, line in enumerate(lines, start=2):
        info[f"A{idx}"] = line
    info.column_dimensions["A"].width = 110

    wb.save(XLSX_PATH)
    print(f"Создан/обновлён Excel: {XLSX_PATH.name} ({len(rows)} строк)")


def nest_from_rows(rows: list[dict]) -> list[dict]:
    """Build nested PRICES_DATA from flat excel rows, preserving order."""
    result: list[dict] = []
    section_map: dict[str, dict] = {}
    child_map: dict[tuple[str, str], dict] = {}

    for row in rows:
        section = row["section"]
        sub = row["subcategory"]
        item = {
            "name": row["name"],
            "price": build_price_string(row["price"], row["unit"]),
            "amount": str(row["amount"] or "1"),
            "unit": row["unit"] or "—",
        }

        if section not in section_map:
            section_obj = {"title": section}
            section_map[section] = section_obj
            result.append(section_obj)

        section_obj = section_map[section]

        if sub:
            key = (section, sub)
            if key not in child_map:
                if "children" not in section_obj:
                    section_obj["children"] = []
                child = {"title": sub, "items": []}
                child_map[key] = child
                section_obj["children"].append(child)
            child_map[key]["items"].append(item)
        else:
            if "items" not in section_obj:
                section_obj["items"] = []
            section_obj["items"].append(item)

    # Cleanup: if a section has children, drop empty items key
    for section_obj in result:
        if section_obj.get("children"):
            section_obj.pop("items", None)
        elif "items" not in section_obj:
            section_obj["items"] = []

    return result


def write_prices_js(data: list[dict]) -> None:
    parts = ["window.PRICES_DATA = ["]
    for ci, cat in enumerate(data):
        parts.append("  {")
        parts.append(f"    title: {js_str(cat['title'])},")
        if cat.get("children"):
            parts.append("    children: [")
            for si, sub in enumerate(cat["children"]):
                parts.append("      {")
                parts.append(f"        title: {js_str(sub['title'])},")
                parts.append("        items: [")
                for ii, item in enumerate(sub["items"]):
                    comma = "," if ii < len(sub["items"]) - 1 else ""
                    parts.append(
                        "          { "
                        f"name: {js_str(item['name'])}, "
                        f"price: {js_str(item['price'])}, "
                        f"amount: {js_str(item['amount'])}, "
                        f"unit: {js_str(item['unit'])} "
                        f"}}{comma}"
                    )
                parts.append("        ]")
                parts.append("      }" + ("," if si < len(cat["children"]) - 1 else ""))
            parts.append("    ]")
        else:
            parts.append("    items: [")
            for ii, item in enumerate(cat.get("items") or []):
                comma = "," if ii < len(cat["items"]) - 1 else ""
                parts.append(
                    "      { "
                    f"name: {js_str(item['name'])}, "
                    f"price: {js_str(item['price'])}, "
                    f"amount: {js_str(item['amount'])}, "
                    f"unit: {js_str(item['unit'])} "
                    f"}}{comma}"
                )
            parts.append("    ]")
        parts.append("  }" + ("," if ci < len(data) - 1 else ""))
    parts.append("];")
    parts.append("")
    JS_PATH.write_text("\n".join(parts), encoding="utf-8")


def import_xlsx_to_js() -> None:
    if not XLSX_PATH.exists():
        print(f"Не найден {XLSX_PATH.name}. Сначала создайте его командой: python update-prices.py --init")
        sys.exit(1)

    wb = openpyxl.load_workbook(XLSX_PATH, data_only=True)
    if "Цены" not in wb.sheetnames:
        print("В Excel нет листа «Цены»")
        sys.exit(1)
    ws = wb["Цены"]

    rows = []
    skipped = 0
    for r in range(2, ws.max_row + 1):
        section = ws.cell(r, 1).value
        sub = ws.cell(r, 2).value
        name = ws.cell(r, 3).value
        price = ws.cell(r, 4).value
        unit = ws.cell(r, 5).value
        amount = ws.cell(r, 6).value
        show = ws.cell(r, 7).value

        section = str(section).strip() if section is not None else ""
        sub = str(sub).strip() if sub is not None else ""
        name = str(name).strip() if name is not None else ""
        unit = str(unit).strip() if unit is not None else ""
        amount = str(amount).strip() if amount is not None else "1"
        show_s = str(show).strip().lower() if show is not None else "да"

        if not section and not name:
            continue
        if not name:
            skipped += 1
            continue
        if show_s not in YES_VALUES:
            skipped += 1
            continue
        if price in (None, ""):
            skipped += 1
            continue

        rows.append(
            {
                "section": section or "Прочее",
                "subcategory": sub,
                "name": name,
                "price": price,
                "unit": unit,
                "amount": amount or "1",
            }
        )

    data = nest_from_rows(rows)
    write_prices_js(data)
    total_items = sum(
        len(c.get("items") or [])
        + sum(len(ch.get("items") or []) for ch in (c.get("children") or []))
        for c in data
    )
    print(f"Готово: {JS_PATH.name}")
    print(f"Разделов: {len(data)}")
    print(f"Позиций на сайте: {total_items}")
    if skipped:
        print(f"Пропущено строк (скрытые/пустые): {skipped}")
    print("Теперь залейте prices-data.js на хостинг REG.RU")


def main() -> None:
    args = sys.argv[1:]
    if "--init" in args or "--export" in args or not XLSX_PATH.exists():
        export_xlsx_from_js()
        if "--init" in args or "--export" in args:
            return
    import_xlsx_to_js()


if __name__ == "__main__":
    main()
