"""Evaluate Shopify smart (automated) collection rules against catalog product data."""

from __future__ import annotations

import re
from decimal import Decimal, InvalidOperation
from typing import Any, Dict, List, Optional, Sequence


def build_smart_collection_product_context(
    fields: Dict[str, Any],
    *,
    product_input: Optional[Dict[str, Any]] = None,
    taxonomy_category_id: Optional[str] = None,
    channel_sku: Optional[str] = None,
    price: Optional[float] = None,
) -> Dict[str, Any]:
    product = product_input or {}
    tags = product.get("tags") or fields.get("tags") or []
    if isinstance(tags, str):
        tags = [part.strip() for part in tags.replace("|", ",").split(",") if part.strip()]

    variant = {}
    variants = product.get("variants") or []
    if variants and isinstance(variants[0], dict):
        variant = variants[0]

    variant_price = variant.get("price")
    if variant_price is None and price is not None:
        variant_price = str(price)

    return {
        "title": _text(product.get("title") or fields.get("title") or fields.get("name")),
        "product_type": _text(
            product.get("productType") or fields.get("product_type") or fields.get("category_l1")
        ),
        "vendor": _text(product.get("vendor") or fields.get("vendor") or fields.get("brand") or fields.get("manufacturer")),
        "tags": [str(tag).strip() for tag in tags if str(tag).strip()],
        "variant_title": _variant_title(variant),
        "variant_sku": _text(variant.get("sku") or channel_sku or fields.get("sku")),
        "variant_price": _text(variant_price),
        "taxonomy_category_id": _text(taxonomy_category_id or product.get("category")),
    }


def evaluate_smart_collection_rules(
    rules_json: Optional[Dict[str, Any]],
    context: Dict[str, Any],
) -> Dict[str, Any]:
    """Return whether product data satisfies a smart collection rule set."""
    if not isinstance(rules_json, dict):
        return {
            "matches": False,
            "match_mode": "all",
            "rule_results": [],
            "summary": "Smart collection has no rule definition in registry.",
        }

    rules = [rule for rule in (rules_json.get("rules") or []) if isinstance(rule, dict)]
    if not rules:
        return {
            "matches": False,
            "match_mode": "all",
            "rule_results": [],
            "summary": "Smart collection has no rules configured.",
        }

    disjunctive = bool(rules_json.get("appliedDisjunctively"))
    rule_results = [_evaluate_rule(rule, context) for rule in rules]
    if disjunctive:
        matches = any(result["matches"] for result in rule_results)
    else:
        matches = all(result["matches"] for result in rule_results)

    return {
        "matches": matches,
        "match_mode": "any" if disjunctive else "all",
        "rule_results": rule_results,
        "summary": _summary_for_rules(rule_results, matches=matches, disjunctive=disjunctive),
    }


def smart_collection_guidance(
    *,
    collection_title: str,
    collection_handle: Optional[str],
    evaluation: Dict[str, Any],
) -> Dict[str, Any]:
    """Human-facing guidance for taxonomy dashboards when a SKU won't enter a smart collection."""
    failed = [row for row in evaluation.get("rule_results") or [] if not row.get("matches")]
    actions: List[str] = []
    for row in failed:
        action = row.get("suggested_action")
        if action and action not in actions:
            actions.append(action)

    if evaluation.get("matches"):
        status = "matched"
        message = f"Product data satisfies the smart collection rules for '{collection_title}'."
    else:
        status = "rule_mismatch"
        message = (
            f"Product does not match smart collection '{collection_title}'. "
            "Shopify will not allow manual assignment; update product data or collection rules."
        )

    return {
        "status": status,
        "collection_title": collection_title,
        "collection_handle": collection_handle,
        "collection_type": "smart",
        "match_mode": evaluation.get("match_mode"),
        "summary": evaluation.get("summary"),
        "message": message,
        "failed_rules": failed,
        "recommended_actions": actions,
    }


def _evaluate_rule(rule: Dict[str, Any], context: Dict[str, Any]) -> Dict[str, Any]:
    column = str(rule.get("column") or "").strip().upper()
    relation = str(rule.get("relation") or "EQUALS").strip().upper()
    condition = str(rule.get("condition") or "").strip()
    actual = _column_value(column, context)
    matches = _compare(actual, relation, condition, column=column)

    return {
        "column": column,
        "relation": relation,
        "condition": condition,
        "actual": actual,
        "matches": matches,
        "suggested_action": _suggested_action(column, relation, condition, actual, matches),
    }


def _column_value(column: str, context: Dict[str, Any]) -> Any:
    if column == "TAG":
        return context.get("tags") or []
    if column == "TITLE":
        return context.get("title")
    if column == "TYPE":
        return context.get("product_type")
    if column == "VENDOR":
        return context.get("vendor")
    if column == "VARIANT_TITLE":
        return context.get("variant_title")
    if column == "VARIANT_PRICE":
        return context.get("variant_price")
    if column in {"PRODUCT_CATEGORY_ID", "PRODUCT_TAXONOMY_NODE_ID"}:
        return context.get("taxonomy_category_id")
    if column == "VARIANT_INVENTORY":
        return context.get("variant_inventory")
    return context.get(column.lower())


def _compare(actual: Any, relation: str, condition: str, *, column: str) -> bool:
    if column == "TAG":
        tags = [str(item).strip() for item in (actual or []) if str(item).strip()]
        if relation == "EQUALS":
            return condition in tags
        if relation == "NOT_EQUALS":
            return condition not in tags
        if relation == "CONTAINS":
            return any(condition.lower() in tag.lower() for tag in tags)
        if relation == "NOT_CONTAINS":
            return not any(condition.lower() in tag.lower() for tag in tags)
        return False

    text = _text(actual)
    cond = condition.strip()
    if relation == "EQUALS":
        return text.lower() == cond.lower()
    if relation == "NOT_EQUALS":
        return text.lower() != cond.lower()
    if relation == "STARTS_WITH":
        return text.lower().startswith(cond.lower())
    if relation == "ENDS_WITH":
        return text.lower().endswith(cond.lower())
    if relation == "CONTAINS":
        return cond.lower() in text.lower()
    if relation == "NOT_CONTAINS":
        return cond.lower() not in text.lower()
    if relation in {"GREATER_THAN", "LESS_THAN"}:
        return _numeric_compare(text, cond, relation)
    return text.lower() == cond.lower()


def _numeric_compare(actual: str, condition: str, relation: str) -> bool:
    try:
        left = Decimal(actual or "0")
        right = Decimal(condition or "0")
    except InvalidOperation:
        return False
    if relation == "GREATER_THAN":
        return left > right
    if relation == "LESS_THAN":
        return left < right
    return False


def _suggested_action(
    column: str,
    relation: str,
    condition: str,
    actual: Any,
    matches: bool,
) -> Optional[str]:
    if matches:
        return None
    if column == "TAG":
        if relation in {"EQUALS", "CONTAINS"}:
            return f"Add product tag '{condition}'."
        if relation == "NOT_EQUALS":
            return f"Remove product tag '{condition}'."
        if relation == "NOT_CONTAINS":
            return f"Ensure no product tag contains '{condition}'."
    label = _column_label(column)
    if relation == "EQUALS":
        return f"Set {label} to '{condition}' (current: '{_display_actual(actual)}')."
    if relation == "NOT_EQUALS":
        return f"Change {label} so it is not '{condition}' (current: '{_display_actual(actual)}')."
    if relation == "CONTAINS":
        return f"Set {label} to include '{condition}' (current: '{_display_actual(actual)}')."
    if relation == "STARTS_WITH":
        return f"Set {label} to start with '{condition}' (current: '{_display_actual(actual)}')."
    if relation == "ENDS_WITH":
        return f"Set {label} to end with '{condition}' (current: '{_display_actual(actual)}')."
    if relation in {"GREATER_THAN", "LESS_THAN"}:
        op = "greater than" if relation == "GREATER_THAN" else "less than"
        return f"Set {label} to be {op} {condition} (current: '{_display_actual(actual)}')."
    return f"Adjust {label} to satisfy {relation} '{condition}'."


def _summary_for_rules(rule_results: Sequence[Dict[str, Any]], *, matches: bool, disjunctive: bool) -> str:
    if matches:
        return "Matches smart collection rules."
    failed = [row for row in rule_results if not row.get("matches")]
    if not failed:
        return "Does not match smart collection rules."
    joiner = " or " if disjunctive else " and "
    parts = [
        f"{row.get('column')} {row.get('relation')} '{row.get('condition')}'"
        for row in failed[:3]
    ]
    suffix = joiner.join(parts)
    if len(failed) > 3:
        suffix += f"{joiner}+{len(failed) - 3} more"
    return f"Failed rule(s): {suffix}."


def _column_label(column: str) -> str:
    return {
        "TITLE": "product title",
        "TYPE": "product type",
        "VENDOR": "vendor",
        "VARIANT_TITLE": "variant title",
        "VARIANT_PRICE": "variant price",
        "PRODUCT_CATEGORY_ID": "Shopify product category",
        "PRODUCT_TAXONOMY_NODE_ID": "Shopify product category",
    }.get(column, column.lower().replace("_", " "))


def _display_actual(actual: Any) -> str:
    if isinstance(actual, list):
        return ", ".join(str(item) for item in actual) or "(empty)"
    return str(actual or "(empty)")


def _text(value: Any) -> str:
    return re.sub(r"\s+", " ", str(value or "").strip())


def _variant_title(variant: Dict[str, Any]) -> str:
    option_values = variant.get("optionValues") or []
    if isinstance(option_values, list) and option_values:
        first = option_values[0]
        if isinstance(first, dict):
            name = _text(first.get("name"))
            if name:
                return name
    return "Default Title"
