import os
import re
import html
import csv
import json
import time
import requests
import traceback
import xml.etree.ElementTree as ET
from io import StringIO
from datetime import datetime
from pathlib import Path
from urllib.parse import urlsplit, urlunsplit

from dotenv import load_dotenv
from google.oauth2.service_account import Credentials
from googleapiclient.discovery import build


# --------------------------------------------------
# ENV
# --------------------------------------------------
BASE_DIR = Path(__file__).resolve().parent
load_dotenv(BASE_DIR / ".env")

SERVICE_ACCOUNT_FILE = os.getenv("GOOGLE_SERVICE_ACCOUNT_FILE", "").strip()
HTTP_TIMEOUT = int(os.getenv("HTTP_TIMEOUT", "60"))
SLEEP_BETWEEN = int(os.getenv("SLEEP_BETWEEN_CLIENTS", "2"))
LOG_DIR = Path(os.getenv("LOG_DIR", str(BASE_DIR / "logs")))
DEFAULT_EMPTY_VALUE = os.getenv("DEFAULT_EMPTY_VALUE", "NINCS ADAT!").strip()

LOG_DIR.mkdir(parents=True, exist_ok=True)


# --------------------------------------------------
# XML namespace
# --------------------------------------------------
NS = {"g": "http://base.google.com/ns/1.0"}


# --------------------------------------------------
# Default oszlopok
# --------------------------------------------------
DEFAULT_COLUMNS = [
    "id",
    "title",
    "description",
    "product_type",
    "link",
    "availability",
    "price",
    "sale_price",
    "additional_images",
]


# --------------------------------------------------
# LOG
# --------------------------------------------------
def log(message: str) -> None:
    now = datetime.now()
    log_file = LOG_DIR / f"{now.strftime('%Y-%m-%d')}.log"
    line = f"[{now.strftime('%H:%M:%S')}] {message}"

    print(line)

    with open(log_file, "a", encoding="utf-8") as f:
        f.write(line + "\n")


def get_latest_log_file() -> Path | None:
    files = sorted(LOG_DIR.glob("*.log"), reverse=True)
    return files[0] if files else None


def read_latest_log(lines: int = 100) -> str:
    latest = get_latest_log_file()
    if not latest or not latest.exists():
        return "Nincs log fájl."

    with open(latest, "r", encoding="utf-8") as f:
        content = f.readlines()

    return "".join(content[-lines:])


# --------------------------------------------------
# Google Sheets service
# --------------------------------------------------
def get_sheets_service():
    if not SERVICE_ACCOUNT_FILE:
        raise ValueError("GOOGLE_SERVICE_ACCOUNT_FILE nincs beállítva.")

    credentials = Credentials.from_service_account_file(
        SERVICE_ACCOUNT_FILE,
        scopes=["https://www.googleapis.com/auth/spreadsheets"],
    )

    return build("sheets", "v4", credentials=credentials, cache_discovery=False)


# --------------------------------------------------
# Segédfüggvények
# --------------------------------------------------
def clean_text(value) -> str:
    return (value or "").strip()


def find_text(item, tag: str, namespace=None) -> str:
    if namespace:
        return clean_text(item.findtext(tag, default="", namespaces=namespace))
    return clean_text(item.findtext(tag, default=""))


def clean_product_url(url: str) -> str:
    url = clean_text(url)
    if not url:
        return ""

    parts = urlsplit(url)
    return urlunsplit((parts.scheme, parts.netloc, parts.path, "", ""))


def normalize_value(value) -> str:
    if value is None:
        return DEFAULT_EMPTY_VALUE

    value = str(value).strip()

    if value == "":
        return DEFAULT_EMPTY_VALUE

    return value


def chunk_list(items: list, size: int) -> list[list]:
    if size <= 0:
        raise ValueError("A chunk méretnek 0-nál nagyobbnak kell lennie.")

    return [items[i:i + size] for i in range(0, len(items), size)]

# --------------------------------------------------
# UNAS API HELPER
# --------------------------------------------------
def build_url_from_sef(base_url: str, sef_url: str) -> str:
    base_url = clean_text(base_url)
    sef_url = clean_text(sef_url)

    if not sef_url:
        return ""

    if sef_url.startswith("http://") or sef_url.startswith("https://"):
        return clean_product_url(sef_url)

    return base_url.rstrip("/") + "/" + sef_url.lstrip("/")

def strip_html(value: str) -> str:
    value = clean_text(value)

    if not value:
        return ""

    value = html.unescape(value)

    value = value.replace("<!HTML KOD>", "")
    value = value.replace("<!HTML KÓD>", "")

    value = re.sub(r"<script.*?>.*?</script>", " ", value, flags=re.I | re.S)
    value = re.sub(r"<style.*?>.*?</style>", " ", value, flags=re.I | re.S)
    value = re.sub(r"<[^>]+>", " ", value)

    value = html.unescape(value)
    value = re.sub(r"\s+", " ", value).strip()

    return value

# --------------------------------------------------
# Árukereső termék extract
# --------------------------------------------------
def extract_product_arukereso(item) -> dict:
    return {
        "id": find_text(item, "identifier"),
        "title": find_text(item, "name"),
        "description": strip_html(find_text(item, "description")),

        "product_type": find_text(item, "category"),
        "link": find_text(item, "product_url"),
        "availability": "",
        "price": find_text(item, "price"),
        "sale_price": "",
        "sale_price_effective_date": "",
        "product_weight": "",

        "net_price": find_text(item, "net_price"),
        "category": find_text(item, "category"),
        "manufacturer": find_text(item, "manufacturer"),
        "image_url": find_text(item, "image_url"),
        "delivery_cost": find_text(item, "delivery_cost"),
        "delivery_time": find_text(item, "delivery_time"),
        "basket_disabled": find_text(item, "BasketDisabled", ""),
    }


# --------------------------------------------------
# XML termék extract (Google style / UNAS)
# --------------------------------------------------
def extract_product_xml(item) -> dict:
    additional_images = [
        clean_text(img.text)
        for img in item.findall("g:additional_image_link", NS)
        if clean_text(img.text)
    ]

    return {
        "id": find_text(item, "g:id", NS),
        "title": find_text(item, "title"),
        "description": find_text(item, "g:description", NS),
        "product_type": find_text(item, "g:product_type", NS),
        "link": find_text(item, "link"),
        "condition": find_text(item, "g:condition", NS),
        "availability": find_text(item, "g:availability", NS),
        "material": find_text(item, "g:material", NS),
        "pattern": find_text(item, "g:pattern", NS),
        "adult": find_text(item, "g:adult", NS),
        "is_bundle": find_text(item, "g:is_bundle", NS),
        "image_link": find_text(item, "g:image_link", NS),
        "additional_images": " | ".join(additional_images),
        "price": find_text(item, "g:price", NS),
        "sale_price": find_text(item, "g:sale_price", NS),
        "sale_price_effective_date": find_text(item, "g:sale_price_effective_date", NS),
        "custom_label_0": find_text(item, "g:custom_label_0", NS),
        "custom_label_1": find_text(item, "g:custom_label_1", NS),
        "custom_label_2": find_text(item, "g:custom_label_2", NS),
        "custom_label_3": find_text(item, "g:custom_label_3", NS),
        "custom_label_4": find_text(item, "g:custom_label_4", NS),
        "brand": find_text(item, "g:brand", NS),
        "gtin": find_text(item, "g:gtin", NS),
        "mpn": find_text(item, "g:mpn", NS),
        "google_product_category": find_text(item, "g:google_product_category", NS),
        "product_weight": find_text(item, "g:product_weight", NS),
        "shipping_weight": find_text(item, "g:shipping_weight", NS),
        "identifier_exists": find_text(item, "g:identifier_exists", NS),
        "age_group": find_text(item, "g:age_group", NS),
        "gender": find_text(item, "g:gender", NS),
        "color": find_text(item, "g:color", NS),
        "size": find_text(item, "g:size", NS),
        "item_group_id": find_text(item, "g:item_group_id", NS),
    }


# --------------------------------------------------
# Árukereső parse
# --------------------------------------------------
def parse_arukereso_feed(content: bytes) -> list[dict]:
    if not content:
        raise ValueError("Üres válasz érkezett, nincs mit parse-olni.")

    cleaned = content.lstrip()

    try:
        root = ET.fromstring(cleaned)
    except ET.ParseError as e:
        sample = cleaned[:500].decode("utf-8", errors="replace")
        raise ValueError(f"Érvénytelen XML. Válasz eleje: {sample}") from e

    return [extract_product_arukereso(item) for item in root.findall("./product")]


# --------------------------------------------------
# XML parse
# --------------------------------------------------
def parse_xml_feed(content: bytes) -> list[dict]:
    if not content:
        raise ValueError("Üres válasz érkezett, nincs mit parse-olni.")

    cleaned = content.lstrip()

    try:
        root = ET.fromstring(cleaned)
    except ET.ParseError as e:
        sample = cleaned[:500].decode("utf-8", errors="replace")
        raise ValueError(f"Érvénytelen XML. Válasz eleje: {sample}") from e

    return [extract_product_xml(item) for item in root.findall("./channel/item")]


# --------------------------------------------------
# TSV parse
# --------------------------------------------------
def parse_tsv_feed(content: bytes) -> list[dict]:
    if not content:
        raise ValueError("Üres válasz érkezett, nincs mit parse-olni.")

    text = content.decode("utf-8-sig", errors="replace")
    reader = csv.DictReader(StringIO(text), delimiter="\t")

    products = []
    for row in reader:
        clean_row = {}
        for k, v in row.items():
            key = (k or "").strip()
            value = (v or "").strip()
            if key:
                clean_row[key] = value
        if clean_row:
            products.append(clean_row)

    if products:
        log(f"[TSV] elérhető mezők: {', '.join(products[0].keys())}")

    return products

# --------------------------------------------------
# UNAS API PARSER
# --------------------------------------------------
def parse_unas_productdb_csv(client: dict, content: bytes) -> list[dict]:
    if not content:
        raise ValueError("Üres válasz érkezett, nincs mit parse-olni.")

    text = content.decode("utf-8-sig", errors="replace")
    reader = csv.DictReader(StringIO(text), delimiter=",", quotechar='"')

    base_url = client.get("base_url", "").strip()
    allowed_statuses = client.get("allowed_statuses", ["1", "2"])

    products = []
    skipped_status = 0

    for row in reader:
        clean_row = {}

        for k, v in row.items():
            if not k:
                continue

            key = (k or "").strip().replace("\ufeff", "")
            value = (v or "").strip()
            clean_row[key] = value

        if not clean_row:
            continue

        status = clean_row.get("Státusz", "").strip()

        if allowed_statuses and status not in allowed_statuses:
            skipped_status += 1
            continue

        if "Rövid Leírás" in clean_row:
            clean_row["Rövid Leírás"] = strip_html(clean_row.get("Rövid Leírás", ""))

        if "Tulajdonságok" in clean_row:
            clean_row["Tulajdonságok"] = strip_html(clean_row.get("Tulajdonságok", ""))
            
        sef_url = clean_row.get("SEF URL", "").strip()
        clean_row["Link"] = build_url_from_sef(base_url, sef_url)

        products.append(clean_row)

    if products:
        log(f"[UNAS ProductDB CSV] elérhető mezők: {', '.join(products[0].keys())}")

    log(
        f"[UNAS ProductDB CSV] termékek: {len(products)} | "
        f"kihagyva státusz miatt: {skipped_status}"
    )

    return products

# --------------------------------------------------
# Feed parser választó
# --------------------------------------------------
def parse_client_feed(client: dict, content: bytes) -> list[dict]:
    source_type = client.get("type", "xml").strip().lower()

    if source_type == "tsv":
        return parse_tsv_feed(content)

    if source_type == "xml":
        return parse_xml_feed(content)

    if source_type == "arukereso_xml":
        return parse_arukereso_feed(content)
    
    if source_type == "unas_productdb_csv":
        return parse_unas_productdb_csv(client, content)

    raise ValueError(f"Ismeretlen feed típus: {source_type}")


# --------------------------------------------------
# Oszlopellenőrzés
# --------------------------------------------------
def validate_columns(products, columns, client_name):
    if not products:
        return

    known_keys = set()

    for product in products:
        known_keys.update(product.keys())

    unknown = [c for c in columns if c not in known_keys]

    if unknown:
        raise ValueError(
            f"[{client_name}] Ismeretlen oszlop(ok): {', '.join(unknown)}"
        )

# --------------------------------------------------
# Sheet műveletek
# --------------------------------------------------
def clear_sheet(service, sheet_id: str, tab: str) -> None:
    service.spreadsheets().values().clear(
        spreadsheetId=sheet_id,
        range=f"{tab}!A:ZZ",
        body={}
    ).execute()


def update_sheet(service, sheet_id: str, tab: str, products: list[dict], columns: list[str]) -> None:
    values = [columns]

    for product in products:
        row = [normalize_value(product.get(col, "")) for col in columns]
        values.append(row)

    service.spreadsheets().values().update(
        spreadsheetId=sheet_id,
        range=f"{tab}!A1",
        valueInputOption="RAW",
        body={"values": values},
    ).execute()


def update_sheet_chunk(
    service,
    sheet_id: str,
    tab: str,
    products: list[dict],
    columns: list[str],
) -> None:
    values = [columns]

    for product in products:
        row = [normalize_value(product.get(col, "")) for col in columns]
        values.append(row)

    clear_sheet(service, sheet_id, tab)

    service.spreadsheets().values().update(
        spreadsheetId=sheet_id,
        range=f"{tab}!A1",
        valueInputOption="RAW",
        body={"values": values},
    ).execute()


def update_sheet_chunks(
    service,
    sheet_ids: list[str],
    tab: str,
    products: list[dict],
    columns: list[str],
    chunk_size: int,
    client_name: str,
) -> None:
    if not isinstance(sheet_ids, list) or not sheet_ids:
        raise ValueError(f"[{client_name}] A sheet_ids mezőnek nem üres listának kell lennie.")

    cleaned_sheet_ids = [str(s).strip() for s in sheet_ids if str(s).strip()]
    if not cleaned_sheet_ids:
        raise ValueError(f"[{client_name}] A sheet_ids lista üres vagy hibás.")

    chunks = chunk_list(products, chunk_size)

    if len(chunks) > len(cleaned_sheet_ids):
        raise ValueError(
            f"[{client_name}] Kevés sheet_id van megadva. "
            f"Szükséges: {len(chunks)}, megadott: {len(cleaned_sheet_ids)}"
        )

    for index, chunk in enumerate(chunks):
        target_sheet_id = cleaned_sheet_ids[index]
        log(f"[{client_name}] chunk {index + 1}/{len(chunks)} -> {len(chunk)} sor")
        update_sheet_chunk(
            service=service,
            sheet_id=target_sheet_id,
            tab=tab,
            products=chunk,
            columns=columns,
        )

    for index in range(len(chunks), len(cleaned_sheet_ids)):
        target_sheet_id = cleaned_sheet_ids[index]
        log(f"[{client_name}] extra sheet ürítése: {index + 1}")
        clear_sheet(service, target_sheet_id, tab)


def get_sheet_values(service, sheet_id: str, tab: str) -> list[list[str]]:
    result = service.spreadsheets().values().get(
        spreadsheetId=sheet_id,
        range=f"{tab}!A:ZZ"
    ).execute()

    return result.get("values", [])


def col_to_a1(col_index: int) -> str:
    result = ""
    col_num = col_index + 1

    while col_num > 0:
        col_num, remainder = divmod(col_num - 1, 26)
        result = chr(65 + remainder) + result

    return result


def batch_update_prices(
    service,
    sheet_id: str,
    tab: str,
    key_column: str,
    update_columns: list[str],
    feed_products: list[dict],
) -> dict:
    values = get_sheet_values(service, sheet_id, tab)

    if not values:
        raise ValueError("A Google Sheet üres, nincs fejléc.")

    header = values[0]
    data_rows = values[1:]

    log(f"[{tab}] sheet header: {', '.join(header)}")

    if key_column not in header:
        raise ValueError(f"A key oszlop nem található a sheetben: {key_column}")

    missing_update_cols = [col for col in update_columns if col not in header]
    if missing_update_cols:
        raise ValueError(
            f"Hiányzó frissítendő oszlop(ok) a sheetben: {', '.join(missing_update_cols)}"
        )

    key_col_index = header.index(key_column)
    update_col_indexes = {col: header.index(col) for col in update_columns}

    sheet_row_map = {}
    for idx, row in enumerate(data_rows, start=2):
        key_value = row[key_col_index].strip() if key_col_index < len(row) else ""
        if key_value:
            sheet_row_map[key_value] = idx

    data = []
    matched = 0
    skipped = 0

    for product in feed_products:
        key_value = (product.get(key_column) or "").strip()
        if not key_value:
            skipped += 1
            continue

        sheet_row = sheet_row_map.get(key_value)
        if not sheet_row:
            skipped += 1
            continue

        for col in update_columns:
            col_index = update_col_indexes[col]
            col_letter = col_to_a1(col_index)
            range_name = f"{tab}!{col_letter}{sheet_row}"

            data.append({
                "range": range_name,
                "values": [[normalize_value(product.get(col, ""))]]
            })

        matched += 1

    if data:
        service.spreadsheets().values().batchUpdate(
            spreadsheetId=sheet_id,
            body={
                "valueInputOption": "RAW",
                "data": data
            }
        ).execute()

    return {
        "matched": matched,
        "skipped": skipped,
        "updated_cells": len(data),
    }


# --------------------------------------------------
# Clients betöltés
# --------------------------------------------------
def load_clients() -> list[dict]:
    clients_file = BASE_DIR / "clients.json"
    if not clients_file.exists():
        raise FileNotFoundError(f"Nem található a clients.json: {clients_file}")

    with open(clients_file, "r", encoding="utf-8") as f:
        clients = json.load(f)

    if not isinstance(clients, list):
        raise ValueError("A clients.json gyökérelemének listának kell lennie.")

    return clients


# --------------------------------------------------
# HTTP feed letöltés
# --------------------------------------------------
def fetch_feed(feed_url: str, client_name: str) -> bytes:
    feed_url = (feed_url or "").strip()

    # HTTP / HTTPS feed
    if feed_url.lower().startswith(("http://", "https://")):
        response = requests.get(
            feed_url,
            timeout=HTTP_TIMEOUT,
            headers={
                "User-Agent": "Mozilla/5.0 (compatible; FeedSync/1.0; +https://salesinnovo.com)"
            },
            allow_redirects=True,
        )

        log(f"[{client_name}] HTTP status: {response.status_code}")
        log(f"[{client_name}] Content-Type: {response.headers.get('Content-Type', '')}")
        log(f"[{client_name}] Final URL: {response.url}")

        preview = response.text[:300].replace("\n", " ").replace("\r", " ")
        log(f"[{client_name}] Válasz eleje: {preview}")

        response.raise_for_status()
        return response.content

    # Lokális fájl
    path = Path(feed_url)

    if not path.exists():
        raise ValueError(f"Nem található a lokális feed fájl: {feed_url}")

    log(f"[{client_name}] Lokális fájl betöltés: {path}")

    content = path.read_bytes()

    preview = content[:300].decode("utf-8", errors="replace").replace("\n", " ").replace("\r", " ")
    log(f"[{client_name}] Fájl eleje: {preview}")

    return content


# --------------------------------------------------
# Teljes sync egy ügyfélre
# --------------------------------------------------
def process_client(service, client: dict) -> dict:
    name = client.get("name", "unknown")
    feed_url = client.get("feed_url", "").strip()
    sheet_id = client.get("sheet_id", "").strip()
    sheet_ids = client.get("sheet_ids", [])
    sheet_tab = client.get("sheet_tab", "Feed").strip()
    columns = client.get("columns", DEFAULT_COLUMNS)
    chunk_size = int(client.get("chunk_size", 0) or 0)

    try:
        if not feed_url:
            raise ValueError("Hiányzik a feed_url.")

        has_single_sheet = bool(sheet_id)
        has_multi_sheet = bool(sheet_ids)

        if not has_single_sheet and not has_multi_sheet:
            raise ValueError("Hiányzik a sheet_id vagy a sheet_ids.")

        if has_multi_sheet and chunk_size <= 0:
            raise ValueError("Ha sheet_ids van megadva, a chunk_size mezőnek is 0-nál nagyobbnak kell lennie.")

        if not isinstance(columns, list) or not columns:
            raise ValueError("A columns mezőnek nem üres listának kell lennie.")

        log(f"[{name}] indul")
        log(f"[{name}] feed típus: {client.get('type', 'xml')}")
        log(f"[{name}] oszlopok: {', '.join(columns)}")

        if has_multi_sheet:
            log(f"[{name}] chunkolt mód aktív | chunk_size: {chunk_size} | cél sheetek: {len(sheet_ids)}")
        else:
            log(f"[{name}] egyetlen sheet mód")

        feed_content = fetch_feed(feed_url, name)
        products = parse_client_feed(client, feed_content)
        validate_columns(products, columns, name)

        log(f"[{name}] termékek száma: {len(products)}")

        if has_multi_sheet:
            update_sheet_chunks(
                service=service,
                sheet_ids=sheet_ids,
                tab=sheet_tab,
                products=products,
                columns=columns,
                chunk_size=chunk_size,
                client_name=name,
            )
        else:
            update_sheet(
                service=service,
                sheet_id=sheet_id,
                tab=sheet_tab,
                products=products,
                columns=columns,
            )

        log(f"[{name}] kész")
        return {"client": name, "ok": True, "count": len(products), "error": ""}

    except Exception as e:
        log(f"[{name}] HIBA: {str(e)}")
        log(traceback.format_exc())
        return {"client": name, "ok": False, "count": 0, "error": str(e)}


# --------------------------------------------------
# Ár-sync egy ügyfélre
# --------------------------------------------------
def process_client_price_sync_chunked(service, client: dict) -> dict:
    name = client.get("name", "unknown")
    feed_url = client.get("feed_url", "").strip()
    sheet_id = client.get("sheet_id", "").strip()
    sheet_ids = client.get("sheet_ids", [])
    sheet_tab = client.get("sheet_tab", "Feed").strip()
    price_sync = client.get("price_sync", {})
    chunk_size = int(client.get("chunk_size", 0) or 0)

    try:
        if not price_sync.get("enabled", False):
            return {
                "client": name,
                "ok": True,
                "matched": 0,
                "skipped": 0,
                "updated_cells": 0,
                "error": "disabled",
            }

        key_column = price_sync.get("key_column", "id")
        update_columns = price_sync.get("update_columns", ["price", "sale_price"])

        if not feed_url:
            raise ValueError("Hiányzik a feed_url.")

        # SINGLE SHEET mód
        if sheet_id:
            log(f"[{name}] ár-sync indul (egy sheet)")
            feed_content = fetch_feed(feed_url, name)
            feed_products = parse_client_feed(client, feed_content)

            log(f"[{name}] ár-feed termékek: {len(feed_products)}")

            result = batch_update_prices(
                service=service,
                sheet_id=sheet_id,
                tab=sheet_tab,
                key_column=key_column,
                update_columns=update_columns,
                feed_products=feed_products,
            )

            log(
                f"[{name}] ár-sync kész | illesztett: {result['matched']} | "
                f"kihagyott: {result['skipped']} | frissített cellák: {result['updated_cells']}"
            )

            return {
                "client": name,
                "ok": True,
                "matched": result["matched"],
                "skipped": result["skipped"],
                "updated_cells": result["updated_cells"],
                "error": "",
            }

        # CHUNKOLT mód több sheet-re
        elif sheet_ids:
            if chunk_size <= 0:
                raise ValueError("chunk_size kell, ha sheet_ids van megadva.")

            feed_content = fetch_feed(feed_url, name)
            feed_products = parse_client_feed(client, feed_content)

            log(f"[{name}] ár-sync chunkolt mód indul | chunk_size: {chunk_size} | sheetek: {len(sheet_ids)}")
            log(f"[{name}] ár-feed termékek: {len(feed_products)}")

            chunks = chunk_list(feed_products, chunk_size)

            if len(chunks) > len(sheet_ids):
                raise ValueError(f"[{name}] Kevés sheet_id a chunkokhoz: szükséges {len(chunks)}, megadott {len(sheet_ids)}")

            matched_total = 0
            skipped_total = 0
            updated_cells_total = 0

            for index, chunk in enumerate(chunks):
                target_sheet_id = sheet_ids[index]
                log(f"[{name}] chunk {index+1}/{len(chunks)} -> {len(chunk)} sor")

                result = batch_update_prices(
                    service=service,
                    sheet_id=target_sheet_id,
                    tab=sheet_tab,
                    key_column=key_column,
                    update_columns=update_columns,
                    feed_products=chunk,
                )

                matched_total += result["matched"]
                skipped_total += result["skipped"]
                updated_cells_total += result["updated_cells"]

            # Ürítjük a nem használt extra sheeteket
            for index in range(len(chunks), len(sheet_ids)):
                target_sheet_id = sheet_ids[index]
                log(f"[{name}] extra sheet ürítése: {index+1}")
                clear_sheet(service, target_sheet_id, sheet_tab)

            log(
                f"[{name}] ár-sync chunkolt kész | illesztett: {matched_total} | "
                f"kihagyott: {skipped_total} | frissített cellák: {updated_cells_total}"
            )

            return {
                "client": name,
                "ok": True,
                "matched": matched_total,
                "skipped": skipped_total,
                "updated_cells": updated_cells_total,
                "error": "",
            }

        else:
            raise ValueError("Hiányzik a sheet_id vagy sheet_ids.")

    except Exception as e:
        log(f"[{name}] ár-sync HIBA: {str(e)}")
        log(traceback.format_exc())
        return {
            "client": name,
            "ok": False,
            "matched": 0,
            "skipped": 0,
            "updated_cells": 0,
            "error": str(e),
        }


# --------------------------------------------------
# Összes ügyfél teljes sync
# --------------------------------------------------
def run_all_clients() -> list[dict]:
    results = []
    clients = load_clients()
    service = get_sheets_service()

    log("==== START ====")

    for client in clients:
        results.append(process_client(service, client))
        time.sleep(SLEEP_BETWEEN)

    log("==== END ====")

    return results


# --------------------------------------------------
# Összes ügyfél ár-sync
# --------------------------------------------------
def run_price_sync_clients() -> list[dict]:
    results = []
    clients = load_clients()
    service = get_sheets_service()

    log("==== PRICE SYNC START ====")

    for client in clients:
        price_sync = client.get("price_sync", {})
        if price_sync.get("enabled", False):
            results.append(process_client_price_sync_chunked(service, client))
            time.sleep(SLEEP_BETWEEN)

    log("==== PRICE SYNC END ====")

    return results