from __future__ import annotations

from typing import Any, Dict, List, Optional, Sequence, Set

from sqlalchemy import select
from sqlalchemy.orm import Session

from db.models import PlytixAttributeCatalog
from plytix.product_normalize import (
    chunk_attributes,
    merge_raw_products,
    normalize_plytix_api_product,
    resolve_pull_attribute_names,
)


def load_catalog_attribute_codes(
    session: Session,
    *,
    source_label: Optional[str] = None,
    exclude_destination_hints: Optional[Sequence[str]] = ("shopify",),
) -> List[str]:
    """Active attribute codes from plytix_attribute_catalog (CSV or API import)."""
    stmt = (
        select(PlytixAttributeCatalog.attribute_code, PlytixAttributeCatalog.destination_hint)
        .where(PlytixAttributeCatalog.is_active.is_(True))
        .order_by(PlytixAttributeCatalog.attribute_code)
    )
    if source_label:
        stmt = stmt.where(PlytixAttributeCatalog.source_label == source_label)
    excluded_hints = {
        str(hint).strip().lower()
        for hint in (exclude_destination_hints or [])
        if str(hint).strip()
    }
    codes: List[str] = []
    seen: Set[str] = set()
    for code, hint in session.execute(stmt).all():
        text = str(code).strip()
        if not text or text in seen:
            continue
        if excluded_hints and str(hint or "").strip().lower() in excluded_hints:
            continue
        if excluded_hints and "shopify" in excluded_hints and text.startswith("shopify_"):
            continue
        seen.add(text)
        codes.append(text)
    return codes


def fetch_plytix_products(
    client: Any,
    path: str,
    *,
    page_size: int,
    max_pages: int,
    session: Optional[Session] = None,
    attribute_names: Optional[Sequence[str]] = None,
) -> List[Dict[str, Any]]:
    """Fetch Plytix products with explicit attribute selection and normalized payloads.

    Plytix families define a default attribute set per product, but products can also carry
    values on global / non-family attributes (the UI shows both). We therefore request the
    union of all catalog attribute codes for every product; the API returns only attributes
    that have values on that SKU. Empty or non-applicable fields are omitted downstream.
    """
    from app.jobs.plytix_pull import _fetch_product_pages

    catalog_codes: List[str] = []
    if session is not None:
        catalog_codes = load_catalog_attribute_codes(session)
    resolved = resolve_pull_attribute_names(catalog_codes, extra=attribute_names)
    batches = chunk_attributes(resolved) or [list(resolve_pull_attribute_names(None))]

    merged_by_key: Dict[str, Dict[str, Any]] = {}
    for batch in batches:
        for product in _fetch_product_pages(
            client,
            path,
            page_size=page_size,
            max_pages=max_pages,
            attributes=batch,
        ):
            if not isinstance(product, dict):
                continue
            key = str(product.get("id") or product.get("sku") or "").strip()
            if not key:
                continue
            merged_by_key[key] = merge_raw_products(merged_by_key.get(key), product)

    return [normalize_plytix_api_product(product) for product in merged_by_key.values()]
