from __future__ import annotations

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

from sqlalchemy import delete, select
from sqlalchemy.orm import Session

from db.models import ShopifyTaxonomyRegistry


# Furniture / kitchen / door / vanity verticals for this catalog.
DEFAULT_TAXONOMY_SEARCH_TERMS = (
    "furniture",
    "kitchen cabinet",
    "kitchen cabinets",
    "cabinet",
    "door",
    "doors",
    "bathroom vanity",
    "bathroom vanities",
    "vanity",
    "bathroom cabinet",
)

TAXONOMY_SEARCH_QUERY = """
query taxonomyCategories($search: String!, $first: Int!, $after: String) {
  taxonomy {
    categories(search: $search, first: $first, after: $after) {
      pageInfo { hasNextPage endCursor }
      nodes {
        id
        name
        fullName
        level
        parentId
        isLeaf
        isRoot
      }
    }
  }
}
"""

TAXONOMY_DESCENDANTS_QUERY = """
query taxonomyDescendants($categoryId: ID!, $first: Int!, $after: String) {
  taxonomy {
    categories(descendantsOf: $categoryId, first: $first, after: $after) {
      pageInfo { hasNextPage endCursor }
      nodes {
        id
        name
        fullName
        level
        parentId
        isLeaf
        isRoot
      }
    }
  }
}
"""


def pull_shopify_taxonomy_registry(
    session: Session,
    client: Any,
    *,
    search_terms: Optional[Iterable[str]] = None,
    include_descendants: bool = True,
    replace_existing: bool = True,
) -> Dict[str, Any]:
    """Pull Shopify Standard Product Taxonomy nodes for furniture-related verticals."""
    terms = [str(t).strip() for t in (search_terms or DEFAULT_TAXONOMY_SEARCH_TERMS) if str(t).strip()]
    collected: Dict[str, Dict[str, Any]] = {}

    for term in terms:
        for node in _paginate_taxonomy_search(client, term):
            taxonomy_id = str(node.get("id") or "").strip()
            if not taxonomy_id:
                continue
            payload = dict(node)
            payload["search_term"] = term
            collected[taxonomy_id] = payload

    root_ids = sorted(
        {
            taxonomy_id
            for taxonomy_id, node in collected.items()
            if node.get("isRoot") or int(node.get("level") or 0) <= 2
        }
    )
    if include_descendants:
        for category_id in root_ids:
            for node in _paginate_taxonomy_descendants(client, category_id):
                taxonomy_id = str(node.get("id") or "").strip()
                if not taxonomy_id:
                    continue
                if taxonomy_id not in collected:
                    collected[taxonomy_id] = dict(node)

    if replace_existing:
        session.execute(delete(ShopifyTaxonomyRegistry))

    fetched_at = datetime.utcnow()
    for taxonomy_id, node in sorted(collected.items(), key=lambda item: item[1].get("fullName") or ""):
        session.add(
            ShopifyTaxonomyRegistry(
                taxonomy_id=taxonomy_id,
                name=str(node.get("name") or "").strip() or taxonomy_id,
                full_name=str(node.get("fullName") or node.get("name") or "").strip() or taxonomy_id,
                parent_id=_clean(node.get("parentId")),
                level=int(node.get("level") or 0),
                is_leaf=bool(node.get("isLeaf")),
                is_root=bool(node.get("isRoot")),
                search_term=_clean(node.get("search_term")),
                raw_json=node,
                fetched_at=fetched_at,
            )
        )
    session.flush()
    return {
        "search_terms": terms,
        "category_count": len(collected),
        "root_ids_scanned": len(root_ids),
    }


def list_taxonomy_registry_rows(
    session: Session,
    *,
    search: Optional[str] = None,
    limit: Optional[int] = None,
) -> List[Dict[str, Any]]:
    stmt = select(ShopifyTaxonomyRegistry).order_by(ShopifyTaxonomyRegistry.full_name)
    if search:
        pattern = f"%{search.strip()}%"
        stmt = stmt.where(ShopifyTaxonomyRegistry.full_name.ilike(pattern))
    if limit:
        stmt = stmt.limit(limit)
    return [
        {
            "taxonomy_id": row.taxonomy_id,
            "name": row.name,
            "full_name": row.full_name,
            "parent_id": row.parent_id,
            "level": row.level,
            "is_leaf": row.is_leaf,
            "is_root": row.is_root,
            "search_term": row.search_term,
        }
        for row in session.scalars(stmt).all()
    ]


def suggest_shopify_product_category(
    session: Session,
    *,
    category_l1: Optional[str] = None,
    category_l2: Optional[str] = None,
    category_l3: Optional[str] = None,
    collection: Optional[str] = None,
    product_name: Optional[str] = None,
) -> Optional[Dict[str, Any]]:
    """Best-effort match of master taxonomy text to a pulled Shopify taxonomy node."""
    needles = _normalize_needles(category_l1, category_l2, category_l3, collection, product_name)
    if not needles:
        return None
    rows = session.scalars(select(ShopifyTaxonomyRegistry).order_by(ShopifyTaxonomyRegistry.level.desc())).all()
    best: Optional[Dict[str, Any]] = None
    best_score = 0
    for row in rows:
        haystack = _normalize(row.full_name)
        score = _score_match(needles, haystack)
        if score > best_score:
            best_score = score
            best = {
                "taxonomy_id": row.taxonomy_id,
                "full_name": row.full_name,
                "name": row.name,
                "score": score,
            }
    return best if best_score > 0 else None


def _paginate_taxonomy_search(client: Any, search: str) -> List[Dict[str, Any]]:
    return _paginate_categories(
        client,
        TAXONOMY_SEARCH_QUERY,
        {"search": search, "first": 100},
        connection_path=("taxonomy", "categories"),
    )


def _paginate_taxonomy_descendants(client: Any, category_id: str) -> List[Dict[str, Any]]:
    return _paginate_categories(
        client,
        TAXONOMY_DESCENDANTS_QUERY,
        {"categoryId": category_id, "first": 100},
        connection_path=("taxonomy", "categories"),
    )


def _paginate_categories(
    client: Any,
    query: str,
    variables: Dict[str, Any],
    *,
    connection_path: tuple[str, str],
) -> List[Dict[str, Any]]:
    out: List[Dict[str, Any]] = []
    after: Optional[str] = None
    while True:
        payload = dict(variables)
        payload["after"] = after
        data = client.graphql(query, payload)
        connection = data
        for key in connection_path:
            connection = (connection or {}).get(key) or {}
        nodes = connection.get("nodes") or []
        out.extend(nodes)
        page_info = connection.get("pageInfo") or {}
        if not page_info.get("hasNextPage"):
            break
        after = page_info.get("endCursor")
        if not after:
            break
    return out


def _normalize_needles(*values: Optional[str]) -> List[str]:
    needles: List[str] = []
    seen: Set[str] = set()
    for value in values:
        text = _normalize(value)
        if not text or text in seen:
            continue
        seen.add(text)
        needles.append(text)
    return needles


def _normalize(value: Optional[str]) -> str:
    return " ".join(str(value or "").strip().lower().replace("_", " ").replace("-", " ").split())


def _score_match(needles: List[str], haystack: str) -> int:
    score = 0
    for needle in needles:
        if needle in haystack:
            score += len(needle.split())
        for token in needle.split():
            if len(token) >= 4 and token in haystack:
                score += 1
    return score


def _clean(value: Any) -> Optional[str]:
    text = str(value or "").strip()
    return text or None
