"""Transform Shopify API responses into standard commerce schema.

Maps vendor-specific field names and structures to our canonical Pydantic
models. Each method takes raw Shopify GraphQL response nodes and returns
a list of schema instances.

Shopify uses GraphQL global IDs like "gid://shopify/Product/123" —
we store these in the source_id field for traceability and extract
the numeric ID when needed.
"""

from datetime import datetime
from decimal import Decimal
from typing import Any, Dict, List, Optional
from uuid import UUID

from platoon.data.schema import (
    Collection,
    Customer,
    InventoryItem,
    InventoryLevel,
    LineItem,
    Order,
    Product,
    Variant,
)
from platoon.models import deterministic_id


class ShopifyTransformer:
    """Transforms raw Shopify GraphQL nodes into standard schema instances.

    Each method handles one entity type. The transformer is stateless —
    all context comes from the input data.

    Usage:
        transformer = ShopifyTransformer()
        products = transformer.products(raw_product_nodes)
        orders = transformer.orders(raw_order_nodes)
    """

    def products(self, nodes: List[Dict]) -> List[Product]:
        """Transform Shopify product nodes into Product schema.

        Expected node shape (from GraphQL):
            { id, title, descriptionHtml, vendor, productType, tags,
              status, variants: { edges: [{ node: { id, sku, title,
              price, compareAtPrice, inventoryQuantity } }] },
              createdAt, updatedAt }
        """
        results = []
        for node in nodes:
            product_gid = node.get("id", "")
            product_uuid = deterministic_id("products", product_gid)

            variants = []
            for v in _extract_nodes(node, "variants"):
                variant_gid = v.get("id", "")
                variants.append(Variant(
                    id=deterministic_id("variants", variant_gid),
                    product_id=product_uuid,
                    sku=v.get("sku"),
                    title=v.get("title", ""),
                    price=_decimal(v.get("price", "0")),
                    compare_at_price=_decimal(v.get("compareAtPrice")) if v.get("compareAtPrice") else None,
                    inventory_quantity=v.get("inventoryQuantity", 0),
                    weight=v.get("weight"),
                    barcode=v.get("barcode"),
                    source_id=variant_gid,
                ))

            product = Product(
                id=product_uuid,
                title=node.get("title", ""),
                description=node.get("descriptionHtml") or node.get("description"),
                vendor=node.get("vendor"),
                product_type=node.get("productType"),
                tags=node.get("tags", []),
                status=(node.get("status") or "ACTIVE").lower(),
                variants=variants,
                created_at=_parse_dt(node.get("createdAt")),
                updated_at=_parse_dt(node.get("updatedAt")),
                source_id=product_gid,
            )
            results.append(product)

        return results

    def orders(self, nodes: List[Dict]) -> List[Order]:
        """Transform Shopify order nodes into Order schema.

        Expected node shape:
            { id, name, email, displayFinancialStatus, displayFulfillmentStatus,
              totalPriceSet: { shopMoney: { amount } },
              subtotalPriceSet, totalTaxSet, totalDiscountsSet,
              currencyCode, lineItems: { edges: [...] },
              customer: { id }, createdAt, updatedAt }
        """
        results = []
        for node in nodes:
            order_gid = node.get("id", "")
            order_uuid = deterministic_id("orders", order_gid)

            # Extract line items
            line_items = []
            for li in _extract_nodes(node, "lineItems"):
                li_gid = li.get("id", "")
                product_gid = li.get("product", {}).get("id") if li.get("product") else None
                variant_gid = li.get("variant", {}).get("id") if li.get("variant") else None

                line_items.append(LineItem(
                    id=deterministic_id("line_items", li_gid),
                    product_id=deterministic_id("products", product_gid) if product_gid else None,
                    variant_id=deterministic_id("variants", variant_gid) if variant_gid else None,
                    title=li.get("title", ""),
                    sku=li.get("sku"),
                    quantity=li.get("quantity", 1),
                    price=_money(li.get("originalUnitPriceSet") or li.get("discountedUnitPriceSet")),
                    total_discount=_money(li.get("totalDiscountSet")),
                    source_id=li_gid,
                ))

            # Customer reference
            customer_gid = None
            if node.get("customer") and node["customer"].get("id"):
                customer_gid = node["customer"]["id"]

            results.append(Order(
                id=order_uuid,
                order_number=node.get("name"),
                customer_id=deterministic_id("customers", customer_gid) if customer_gid else None,
                email=node.get("email"),
                financial_status=_status_lower(node.get("displayFinancialStatus")),
                fulfillment_status=_status_lower(node.get("displayFulfillmentStatus")),
                total_price=_money(node.get("totalPriceSet")),
                subtotal_price=_money(node.get("subtotalPriceSet")),
                total_tax=_money(node.get("totalTaxSet")),
                total_discounts=_money(node.get("totalDiscountsSet")),
                currency=node.get("currencyCode", "USD"),
                line_items=line_items,
                created_at=_parse_dt(node.get("createdAt")),
                updated_at=_parse_dt(node.get("updatedAt")),
                source_id=order_gid,
            ))

        return results

    def customers(self, nodes: List[Dict]) -> List[Customer]:
        """Transform Shopify customer nodes into Customer schema.

        Expected node shape (Shopify Admin API 2025-01+):
            { id, email, firstName, lastName, numberOfOrders,
              amountSpent: { amount, currencyCode }, tags, createdAt, updatedAt }

        Note: numberOfOrders is returned as a String (UnsignedInt64 scalar).
              amountSpent is MoneyV2. Legacy field totalSpentV2 is accepted as fallback.
        """
        results = []
        for node in nodes:
            cust_gid = node.get("id", "")

            # amountSpent (current) takes priority over totalSpentV2 (legacy)
            total_spent = Decimal("0")
            if node.get("amountSpent"):
                total_spent = _decimal(node["amountSpent"].get("amount", "0"))
            elif node.get("totalSpentV2"):
                total_spent = _decimal(node["totalSpentV2"].get("amount", "0"))

            # numberOfOrders is a String in the API, ordersCount is legacy int
            orders_count = node.get("numberOfOrders") or node.get("ordersCount", 0)
            orders_count = int(orders_count) if orders_count else 0

            results.append(Customer(
                id=deterministic_id("customers", cust_gid),
                email=node.get("email"),
                first_name=node.get("firstName"),
                last_name=node.get("lastName"),
                orders_count=orders_count,
                total_spent=total_spent,
                tags=node.get("tags", []),
                created_at=_parse_dt(node.get("createdAt")),
                updated_at=_parse_dt(node.get("updatedAt")),
                source_id=cust_gid,
            ))

        return results

    def inventory(self, nodes: List[Dict]) -> List[InventoryItem]:
        """Transform Shopify inventory item nodes into InventoryItem schema.

        Expected node shape:
            { id, sku, unitCost: { amount }, tracked,
              variant: { id },
              inventoryLevels: { edges: [{ node: {
                  id, location: { id, name },
                  quantities: [{ name, quantity }]
              } }] } }
        """
        results = []
        for node in nodes:
            item_gid = node.get("id", "")
            item_uuid = deterministic_id("inventory_items", item_gid)

            variant_gid = None
            if node.get("variant") and node["variant"].get("id"):
                variant_gid = node["variant"]["id"]

            cost = None
            if node.get("unitCost") and node["unitCost"].get("amount"):
                cost = _decimal(node["unitCost"]["amount"])

            # Parse inventory levels per location
            levels = []
            for level_node in _extract_nodes(node, "inventoryLevels"):
                level_gid = level_node.get("id", "")
                location = level_node.get("location", {})

                # Parse quantity breakdown
                quantities = {}
                for q in level_node.get("quantities", []):
                    quantities[q.get("name", "")] = q.get("quantity", 0)

                levels.append(InventoryLevel(
                    id=deterministic_id("inventory_levels", level_gid),
                    inventory_item_id=item_uuid,
                    location_id=location.get("id"),
                    location_name=location.get("name"),
                    available=quantities.get("available", 0),
                    on_hand=quantities.get("on_hand", 0),
                    incoming=quantities.get("incoming", 0),
                    committed=quantities.get("committed", 0),
                    reserved=quantities.get("reserved", 0),
                    source_id=level_gid,
                ))

            results.append(InventoryItem(
                id=item_uuid,
                variant_id=deterministic_id("variants", variant_gid) if variant_gid else None,
                sku=node.get("sku"),
                cost=cost,
                tracked=node.get("tracked", True),
                levels=levels,
                created_at=_parse_dt(node.get("createdAt")),
                updated_at=_parse_dt(node.get("updatedAt")),
                source_id=item_gid,
            ))

        return results


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _extract_nodes(parent: Dict, field: str) -> List[Dict]:
    """Extract nodes from a Shopify GraphQL connection (edges/nodes pattern)."""
    connection = parent.get(field, {})
    if isinstance(connection, list):
        return connection  # Already flat (bulk operation format)
    edges = connection.get("edges", [])
    return [edge["node"] for edge in edges if "node" in edge]


def _parse_dt(value: Optional[str]) -> Optional[datetime]:
    """Parse Shopify ISO datetime string."""
    if not value:
        return None
    return datetime.fromisoformat(value.replace("Z", "+00:00"))


def _decimal(value: Any) -> Decimal:
    """Safely convert to Decimal."""
    if value is None:
        return Decimal("0")
    return Decimal(str(value))


def _money(money_set: Optional[Dict]) -> Decimal:
    """Extract amount from a Shopify MoneyV2/MoneyBag field.

    Shopify returns money as: { shopMoney: { amount: "10.00", currencyCode: "USD" } }
    or directly as: { amount: "10.00" }
    """
    if not money_set:
        return Decimal("0")
    if "shopMoney" in money_set:
        return _decimal(money_set["shopMoney"].get("amount", "0"))
    if "amount" in money_set:
        return _decimal(money_set["amount"])
    return Decimal("0")


def _status_lower(value: Optional[str]) -> Optional[str]:
    """Lowercase a Shopify status enum value."""
    if not value:
        return None
    return value.lower().replace("_", " ")
