"""Shopify kitchen package products with native fixed bundle inventory wiring."""

from __future__ import annotations

import json
import logging
from typing import Any, Dict, List, Optional

from db.kitchen_packages import KitchenPackage

logger = logging.getLogger(__name__)

BOM_NAMESPACE = "custom"
BOM_KEY = "kitchen_package_bom"
PACKAGE_CODE_KEY = "kitchen_package_code"

VARIANT_LOOKUP_DETAIL_QUERY = """
query variantBySku($query: String!) {
  productVariants(first: 1, query: $query) {
    nodes {
      id
      sku
      product { id }
    }
  }
}
"""

PRODUCT_VARIANT_RELATIONSHIP_BULK_UPDATE = """
mutation productVariantRelationshipBulkUpdate($input: [ProductVariantRelationshipUpdateInput!]!) {
  productVariantRelationshipBulkUpdate(input: $input) {
    parentProductVariants {
      id
      productVariantComponents(first: 100) {
        nodes {
          id
          quantity
          productVariant { id sku }
        }
      }
    }
    userErrors { code field message }
  }
}
"""


def build_shopify_package_product_set_input(
    package: KitchenPackage,
    *,
    skip_missing_components: bool = True,
    existing_product_id: Optional[str] = None,
) -> Dict[str, Any]:
    components = [
        {
            "sku": c.master_sku,
            "brochure_sku": c.brochure_sku,
            "qty": c.qty,
            "description": c.description,
        }
        for c in package.components
        if (c.master_exists or not skip_missing_components)
    ]
    if not components:
        raise ValueError(
            f"Package {package.package_master_sku} has no Shopify-linkable components "
            f"(missing: {package.missing_component_skus()[:10]})"
        )

    title = _package_title(package.collection_name, package.label)
    handle = _handle(package.package_master_sku, title)
    tags = [
        "kitchen-package",
        package.package_code,
        package.collection_code.lower(),
        "bundle-fixed-price",
    ]
    metafields = [
        {
            "namespace": BOM_NAMESPACE,
            "key": BOM_KEY,
            "type": "json",
            "value": json.dumps(
                {
                    "package_code": package.package_code,
                    "price_usd": package.price_usd,
                    "components": components,
                },
                separators=(",", ":"),
            ),
        },
        {
            "namespace": BOM_NAMESPACE,
            "key": PACKAGE_CODE_KEY,
            "type": "single_line_text_field",
            "value": package.package_code,
        },
    ]
    product_input: Dict[str, Any] = {
        "title": title,
        "handle": handle,
        "status": "ACTIVE",
        "vendor": package.brand or "Home Surplus",
        "productType": "Kitchen Package",
        "tags": tags,
        "descriptionHtml": _description_html(package, components),
        "metafields": metafields,
        "productOptions": [{"name": "Title", "values": [{"name": "Default Title"}]}],
        "variants": [
            {
                "optionValues": [{"optionName": "Title", "name": "Default Title"}],
                "sku": package.package_master_sku,
                "price": f"{float(package.price_usd):.2f}",
            }
        ],
    }
    if existing_product_id:
        product_input["id"] = existing_product_id
    return product_input


def upsert_shopify_kitchen_package(
    client: Any,
    package: KitchenPackage,
    *,
    dry_run: bool = True,
    skip_missing_components: bool = True,
    component_channel_skus: Optional[Dict[str, str]] = None,
    product_set_mutation: Optional[str] = None,
    variant_lookup_query: Optional[str] = None,
    relationship_mutation: Optional[str] = None,
) -> Dict[str, Any]:
    from shopify.product_sync import PRODUCT_SET_MUTATION

    mutation = product_set_mutation or PRODUCT_SET_MUTATION
    lookup = variant_lookup_query or VARIANT_LOOKUP_DETAIL_QUERY
    relationship_update = relationship_mutation or PRODUCT_VARIANT_RELATIONSHIP_BULK_UPDATE

    existing_id = None
    if not dry_run and client is not None:
        existing = _find_variant_detail_by_sku(client, package.package_master_sku, lookup)
        existing_id = existing.get("product_id") if existing else None

    product_input = build_shopify_package_product_set_input(
        package,
        skip_missing_components=skip_missing_components,
        existing_product_id=existing_id,
    )
    bundle_input = build_shopify_variant_bundle_input(
        package,
        component_channel_skus=component_channel_skus,
        skip_missing_components=skip_missing_components,
    )
    result: Dict[str, Any] = {
        "channel": "shopify",
        "sku": package.package_master_sku,
        "package_code": package.package_code,
        "price_usd": package.price_usd,
        "component_count": len(package.components),
        "missing_components": package.missing_component_skus(),
        "dry_run": dry_run,
        "product_set_input": product_input if dry_run else {"sku": package.package_master_sku, "title": product_input["title"]},
        "bundle_mode": "native_variant_bundle",
        "note": "Fixed-price Shopify product with native bundle component relationships for inventory.",
    }
    if dry_run:
        result["bundle_input"] = bundle_input
        result["status"] = "dry_run"
        return result

    if client is None:
        raise ValueError("Shopify client is required when dry_run is false")

    data = client.graphql(mutation, {"input": product_input})
    payload = (data or {}).get("productSet") or {}
    errors = payload.get("userErrors") or []
    product = payload.get("product") or {}
    if errors:
        result["status"] = "failed"
        result["errors"] = errors
        logger.warning("Shopify package upsert failed sku=%s errors=%s", package.package_master_sku, errors)
        return result

    parent_detail = _find_variant_detail_by_sku(client, package.package_master_sku, lookup, max_attempts=4)
    if not parent_detail or not parent_detail.get("variant_id"):
        result["status"] = "failed"
        result["errors"] = [{"field": ["input", "variants"], "message": "Parent package variant was not found after productSet."}]
        return result

    relationship_input = _resolve_relationship_input(
        client,
        package,
        parent_variant_id=parent_detail["variant_id"],
        component_channel_skus=component_channel_skus,
        lookup_query=lookup,
        skip_missing_components=skip_missing_components,
    )
    if relationship_input["missing_component_variants"]:
        result["status"] = "failed"
        result["errors"] = [
            {
                "field": ["input", "productVariantRelationshipsToCreate"],
                "message": "Missing Shopify component variants: " + ", ".join(relationship_input["missing_component_variants"]),
            }
        ]
        result["bundle_input"] = relationship_input["input"]
        return result

    relationship_result = client.graphql(relationship_update, {"input": [relationship_input["input"]]})
    relationship_payload = (relationship_result or {}).get("productVariantRelationshipBulkUpdate") or {}
    relationship_errors = relationship_payload.get("userErrors") or []
    if relationship_errors:
        result["status"] = "failed"
        result["errors"] = relationship_errors
        result["bundle_input"] = relationship_input["input"]
        logger.warning(
            "Shopify bundle relationship update failed sku=%s errors=%s",
            package.package_master_sku,
            relationship_errors,
        )
        return result

    result["status"] = "ok"
    result["product_id"] = product.get("id") or parent_detail.get("product_id")
    result["parent_variant_id"] = parent_detail["variant_id"]
    result["bundle_input"] = relationship_input["input"]
    result["bundle_component_count"] = len(relationship_input["input"]["productVariantRelationshipsToCreate"])
    result["action"] = "update" if existing_id else "create"
    return result


def build_shopify_variant_bundle_input(
    package: KitchenPackage,
    *,
    parent_variant_id: Optional[str] = None,
    component_channel_skus: Optional[Dict[str, str]] = None,
    skip_missing_components: bool = True,
) -> Dict[str, Any]:
    relationships = []
    for component in package.components:
        if not component.master_exists and skip_missing_components:
            continue
        component_sku = (
            (component_channel_skus or {}).get(component.master_sku)
            or component.master_sku
        )
        relationships.append(
            {
                "sku": component_sku,
                "quantity": int(component.qty),
            }
        )
    if not relationships:
        raise ValueError(
            f"Package {package.package_master_sku} has no Shopify-linkable bundle components "
            f"(missing: {package.missing_component_skus()[:10]})"
        )
    payload: Dict[str, Any] = {
        "productVariantRelationshipsToCreate": relationships,
    }
    if parent_variant_id:
        payload["parentProductVariantId"] = parent_variant_id
    return payload


def _find_variant_detail_by_sku(
    client: Any,
    sku: str,
    lookup_query: str,
    *,
    max_attempts: int = 1,
) -> Optional[Dict[str, str]]:
    try:
        for _ in range(max_attempts):
            data = client.graphql(lookup_query, {"query": f'sku:"{sku}"'})
            nodes = (((data or {}).get("productVariants") or {}).get("nodes") or [])
            if nodes:
                node = nodes[0] or {}
                product = node.get("product") or {}
                return {
                    "variant_id": str(node.get("id") or "").strip(),
                    "product_id": str(product.get("id") or "").strip(),
                    "sku": str(node.get("sku") or sku).strip(),
                }
    except Exception as exc:
        logger.warning("Shopify variant lookup failed sku=%s err=%s", sku, exc)
        return None
    return None


def _resolve_relationship_input(
    client: Any,
    package: KitchenPackage,
    *,
    parent_variant_id: str,
    component_channel_skus: Optional[Dict[str, str]],
    lookup_query: str,
    skip_missing_components: bool,
) -> Dict[str, Any]:
    bundle_input = build_shopify_variant_bundle_input(
        package,
        parent_variant_id=parent_variant_id,
        component_channel_skus=component_channel_skus,
        skip_missing_components=skip_missing_components,
    )
    missing_component_variants: List[str] = []
    relationships_to_create = []
    for relation in bundle_input["productVariantRelationshipsToCreate"]:
        detail = _find_variant_detail_by_sku(client, relation["sku"], lookup_query)
        if not detail or not detail.get("variant_id"):
            missing_component_variants.append(relation["sku"])
            continue
        relationships_to_create.append(
            {
                "id": detail["variant_id"],
                "quantity": int(relation["quantity"]),
            }
        )
    return {
        "input": {
            "parentProductVariantId": parent_variant_id,
            "productVariantRelationshipsToCreate": relationships_to_create,
        },
        "missing_component_variants": missing_component_variants,
    }


def _handle(sku: str, title: str) -> str:
    import re

    return re.sub(r"[^a-z0-9]+", "-", f"{title}-{sku}".lower()).strip("-")[:200] or sku.lower()


def _description_html(package: KitchenPackage, components: List[Dict[str, Any]]) -> str:
    lines = [
        f"<p><strong>{package.label}</strong> package for {package.collection_name}.</p>",
        "<p>Includes:</p><ul>",
    ]
    for comp in components:
        desc = comp.get("description") or comp.get("sku")
        lines.append(f"<li>{comp.get('qty', 1)} x {desc} ({comp.get('sku')})</li>")
    lines.append("</ul>")
    return "".join(lines)


def _package_title(collection_name: str, label: str) -> str:
    collection = str(collection_name or "").strip()
    package_label = str(label or "").strip()
    if collection and package_label:
        return f"{collection} - {package_label}"
    return collection or package_label
