from __future__ import annotations

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

_QUERY_CACHE: Dict[str, str] = {}

_TYPE_FIELDS_QUERY = """
query ShopifyTypeFields($name: String!) {
  __type(name: $name) {
    fields {
      name
    }
  }
}
"""

# Scalar/list fields we prefer when Shopify exposes them on ProductVariant.
_VARIANT_SCALAR_FIELDS = (
    "id",
    "sku",
    "title",
    "price",
    "compareAtPrice",
    "barcode",
    "inventoryQuantity",
    "inventoryPolicy",
    "taxable",
    "weight",
    "weightUnit",
)

_PRODUCT_SCALAR_FIELDS = (
    "id",
    "title",
    "handle",
    "descriptionHtml",
    "vendor",
    "productType",
    "status",
    "tags",
    "createdAt",
    "updatedAt",
)


def type_field_names(client: Any, type_name: str) -> Set[str]:
    data = client.graphql(_TYPE_FIELDS_QUERY, {"name": type_name})
    fields = (data.get("__type") or {}).get("fields") or []
    return {str(item.get("name") or "").strip() for item in fields if item.get("name")}


def build_products_pull_query(client: Any) -> str:
    """Build a pull query from Shopify's live schema instead of hard-coded fields."""
    cache_key = str(getattr(client, "endpoint", "") or id(client))
    cached = _QUERY_CACHE.get(cache_key)
    if cached:
        return cached

    product_fields = type_field_names(client, "Product")
    variant_fields = type_field_names(client, "ProductVariant")
    inventory_fields = type_field_names(client, "InventoryItem") if "inventoryItem" in variant_fields else set()

    product_lines = _selection_lines(
        product_fields,
        _PRODUCT_SCALAR_FIELDS,
        nested={
            "seo": "seo { title description }",
            "options": "options { name values }",
            "metafields": "metafields(first: 50) { nodes { namespace key type value } }",
            "featuredMedia": "featuredMedia { preview { image { url altText } } }",
            "media": "media(first: 20) { nodes { mediaContentType preview { image { url altText } } } }",
        },
    )
    variant_lines = _variant_selection_lines(variant_fields, inventory_fields)

    query = f"""
query shopifyProductsPull($first: Int!, $after: String) {{
  products(first: $first, after: $after) {{
    pageInfo {{ hasNextPage endCursor }}
    nodes {{
      {" ".join(product_lines)}
      variants(first: 100) {{
        nodes {{
          {" ".join(variant_lines)}
        }}
      }}
    }}
  }}
}}
""".strip()
    _QUERY_CACHE[cache_key] = query
    return query


def _selection_lines(
    available: Set[str],
    preferred_scalars: tuple[str, ...],
    *,
    nested: Dict[str, str],
) -> List[str]:
    lines: List[str] = []
    for field in preferred_scalars:
        if field in available:
            lines.append(field)
    for field, fragment in nested.items():
        if field in available:
            lines.append(fragment)
    return lines or ["id"]


def _variant_selection_lines(variant_fields: Set[str], inventory_fields: Set[str]) -> List[str]:
    lines = _selection_lines(
        variant_fields,
        _VARIANT_SCALAR_FIELDS,
        nested={
            "selectedOptions": "selectedOptions { name value }",
            "metafields": "metafields(first: 50) { nodes { namespace key type value } }",
        },
    )

    if "weight" not in variant_fields and "inventoryItem" in variant_fields:
        inv_parts = ["id"]
        if "sku" in inventory_fields:
            inv_parts.append("sku")
        if "measurement" in inventory_fields:
            inv_parts.append("measurement { weight { value unit } }")
        if "requiresShipping" in inventory_fields:
            inv_parts.append("requiresShipping")
        if "tracked" in inventory_fields:
            inv_parts.append("tracked")
        lines.append(f"inventoryItem {{ {' '.join(inv_parts)} }}")

    return lines or ["id", "sku"]


def variant_weight(variant: Dict[str, Any]) -> tuple[Optional[Any], Optional[str]]:
    """Read weight from legacy ProductVariant fields or InventoryItem.measurement."""
    if variant.get("weight") is not None or variant.get("weightUnit") is not None:
        return variant.get("weight"), variant.get("weightUnit")
    inventory_item = variant.get("inventoryItem") or {}
    if not isinstance(inventory_item, dict):
        return None, None
    measurement = inventory_item.get("measurement") or {}
    if not isinstance(measurement, dict):
        return None, None
    weight = measurement.get("weight") or {}
    if not isinstance(weight, dict):
        return None, None
    return weight.get("value"), weight.get("unit")


def build_shopify_media_pull_query(client: Any) -> str:
    """Lightweight query for one-time media enrichment pulls."""
    product_fields = type_field_names(client, "Product")
    lines: List[str] = ["id"]
    if "handle" in product_fields:
        lines.append("handle")
    if "featuredMedia" in product_fields:
        lines.append("featuredMedia { preview { image { url altText } } }")
    if "media" in product_fields:
        lines.append("media(first: 20) { nodes { mediaContentType preview { image { url altText } } } }")
    variant_fields = type_field_names(client, "ProductVariant")
    variant_lines = ["id"]
    if "sku" in variant_fields:
        variant_lines.append("sku")
    return f"""
query shopifyMediaPull($first: Int!, $after: String) {{
  products(first: $first, after: $after) {{
    pageInfo {{ hasNextPage endCursor }}
    nodes {{
      {" ".join(lines)}
      variants(first: 100) {{
        nodes {{ {" ".join(variant_lines)} }}
      }}
    }}
  }}
}}
""".strip()


def extract_shopify_media(product: Dict[str, Any]) -> Dict[str, Any]:
    """Normalize Shopify product media fields for snapshot storage."""
    urls: List[str] = []
    featured = product.get("featuredMedia") or {}
    featured_preview = (featured.get("preview") or {}).get("image") or {}
    featured_url = str(featured_preview.get("url") or "").strip()
    if featured_url:
        urls.append(featured_url)
    for node in ((product.get("media") or {}).get("nodes") or []):
        if not isinstance(node, dict):
            continue
        preview = (node.get("preview") or {}).get("image") or {}
        url = str(preview.get("url") or "").strip()
        if url and url not in urls:
            urls.append(url)
    patch: Dict[str, Any] = {}
    if featured_url:
        patch["featured_image_url"] = featured_url
        patch["thumbnail_url"] = featured_url
    if urls:
        patch["image_urls"] = urls
        patch["primary_image_url"] = urls[0]
    if product.get("media"):
        patch["media"] = product.get("media")
    if product.get("featuredMedia"):
        patch["featuredMedia"] = product.get("featuredMedia")
    return patch
