"""
Enhanced QueryBuilder — converts conversation context into structured product queries.

Improvements:
- Richer slot extraction with more product attributes
- Collective learning: uses platform-wide search patterns
- Smarter keyword expansion
- No gender assumptions
"""

import json
import logging
from typing import Dict, List, Optional

from .llm_client import LLMClient

logger = logging.getLogger(__name__)

EXTRACTION_SYSTEM_PROMPT = """\
You are a product search query extractor for a shopping assistant called ACA.
Analyze the conversation and extract structured search parameters ONLY if the user
is looking for a product to buy or browse.

Return a JSON object with ONLY these fields (omit any that are not mentioned or clearly implied):

Core fields:
- category (string): specific product type (e.g. "sneakers", "maxi dress", "analog watch",
  "wireless earbuds", "leather handbag", "polo shirt")
- brand (string): brand name if mentioned
- gender (string): "male", "female", or "unisex" — ONLY if user EXPLICITLY states it.
  NEVER infer gender from product type, style, or wording. If not stated, omit this field.
- purpose (string): intended use (e.g. "running", "office", "hiking", "casual")
- min_price (number): minimum budget
- max_price (number): maximum budget
- size (string): size value if mentioned
- color (string): color preference
- material (string): material if mentioned (e.g. "leather", "cotton", "denim")
- style (string): style descriptor (e.g. "vintage", "streetwear", "formal", "minimalist")
- occasion (string): occasion if mentioned (e.g. "wedding", "gym", "work", "party")
- pattern (string): pattern if mentioned (e.g. "striped", "solid", "floral")

Search enhancement fields:
- q (string): 3-8 free-text keywords capturing the essence of what user wants
- search_keywords (list of strings): 5-12 additional brand names, model names, synonyms,
  and related terms that stores might use. Examples:
  - For "watches": ["watch", "timepiece", "Rolex", "Omega", "Casio", "Seiko", "Fossil",
    "analog watch", "chronograph", "wristwatch"]
  - For "sneakers": ["sneaker", "trainer", "Nike", "Adidas", "Jordan", "Puma", "running shoe",
    "athletic shoe", "New Balance", "Converse"]
  - For "bags": ["bag", "handbag", "purse", "tote", "Louis Vuitton", "Coach", "crossbody",
    "shoulder bag", "clutch"]
  - Always include the category word and its synonyms.

IMPORTANT RULES:
- NEVER infer gender from product type. If user says "I want shoes" — omit gender.
- If the user says "for my sister" or "for my wife" — gender field = "female".
- If user says "for myself" and is clearly male-coded in earlier chat — still omit unless explicit.
- If conversation is casual chat with NO shopping intent, return: {}
- Return ONLY valid JSON. No explanation, no markdown.
"""


class QueryBuilder:
    """Turns conversation history into a structured product query."""

    def __init__(self, llm: Optional[LLMClient] = None):
        self.llm = llm or LLMClient()

    def build(self, conversation_messages: List[dict]) -> dict:
        """
        Given the conversation history, extract a structured query dict.
        Falls back to enhanced naive extraction when no LLM is available.
        """
        messages = [
            {"role": "system", "content": EXTRACTION_SYSTEM_PROMPT},
            *conversation_messages,
            {
                "role": "user",
                "content": (
                    "Extract the product search parameters from this conversation as JSON. "
                    "Only extract if there is clear shopping intent."
                ),
            },
        ]

        result = self.llm.extract_json(messages)
        if result:
            cleaned = {k: v for k, v in result.items() if v not in (None, "", [])}
            # Ensure search_keywords is always a list
            if "search_keywords" in cleaned and not isinstance(cleaned["search_keywords"], list):
                cleaned["search_keywords"] = [str(cleaned["search_keywords"])]
            return cleaned

        return self._naive_extract(conversation_messages)

    def build_from_text(self, text: str) -> dict:
        """Build a query from a single text snippet (for quick searches)."""
        return self.build([{"role": "user", "content": text}])

    # ── Category → brand/keyword hints for naive fallback ──────────
    BRAND_HINTS: Dict[str, List[str]] = {
        "watch": ["watch", "watches", "timepiece", "rolex", "omega", "casio", "seiko",
                  "cartier", "tissot", "tag heuer", "fossil", "patek philippe", "wristwatch",
                  "chronograph", "analog watch"],
        "shoe": ["shoe", "shoes", "sneaker", "trainer", "nike", "adidas", "jordan", "puma",
                 "new balance", "reebok", "converse", "vans", "footwear"],
        "bag": ["bag", "bags", "handbag", "purse", "tote", "backpack", "louis vuitton",
                "gucci", "prada", "coach", "michael kors", "hermes", "crossbody", "clutch"],
        "perfume": ["perfume", "fragrance", "chanel", "dior", "versace", "gucci",
                    "tom ford", "ysl", "cologne", "scent", "eau de parfum"],
        "shirt": ["shirt", "top", "blouse", "polo", "tee", "t-shirt", "dress shirt"],
        "dress": ["dress", "gown", "maxi", "mini dress", "cocktail dress", "frock"],
        "jacket": ["jacket", "coat", "blazer", "hoodie", "outerwear", "parka", "windbreaker"],
        "pants": ["pants", "trousers", "jeans", "jogger", "chinos", "slacks"],
        "jewelry": ["jewelry", "jewellery", "necklace", "bracelet", "ring", "earring",
                    "pendant", "chain", "gold", "silver"],
        "laptop": ["laptop", "macbook", "chromebook", "dell", "hp", "lenovo", "notebook"],
        "phone": ["phone", "iphone", "samsung", "pixel", "smartphone", "mobile"],
        "headphone": ["headphone", "earbuds", "airpods", "sony", "bose", "earphone",
                      "wireless headphone"],
    }

    @staticmethod
    def _naive_extract(messages: List[dict]) -> dict:
        """
        Enhanced keyword-based extraction fallback.
        More comprehensive than before — handles more categories and attributes.
        """
        user_texts = " ".join(
            m["content"] for m in messages if m.get("role") == "user"
        ).lower()

        query: dict = {}

        # ── Category detection ──────────────────────────────────────
        category_map = [
            (["sneakers", "sneaker", "trainer", "trainers"], "sneakers"),
            (["shoes", "shoe", "footwear"], "shoes"),
            (["boots", "boot"], "boots"),
            (["sandals", "sandal", "slipper", "flip flop"], "sandals"),
            (["watches", "watch", "timepiece", "wristwatch", "chronograph"], "watches"),
            (["bags", "bag", "handbag", "purse", "tote", "backpack"], "bags"),
            (["perfumes", "perfume", "fragrance", "cologne", "scent"], "perfumes"),
            (["jewelry", "jewellery", "necklace", "bracelet", "ring", "earring"], "jewelry"),
            (["shirts", "shirt", "t-shirt", "tshirt", "tee", "polo", "blouse"], "shirts"),
            (["dress", "dresses", "gown", "maxi dress", "mini dress"], "dresses"),
            (["pants", "trousers", "jeans", "chinos", "jogger"], "pants"),
            (["jacket", "coat", "blazer", "hoodie", "parka"], "jackets"),
            (["skirt", "skirts"], "skirts"),
            (["laptops", "laptop", "macbook", "chromebook", "notebook"], "laptops"),
            (["phones", "phone", "smartphone", "iphone", "android"], "phones"),
            (["headphones", "earbuds", "earphone", "airpods"], "headphones"),
        ]

        for keywords, cat in category_map:
            if any(kw in user_texts for kw in keywords):
                query["category"] = cat
                break

        # ── Gender detection (explicit only) ──────────────────────────
        explicit_gender_signals = [
            ("for men", "male"), ("for man", "male"), ("men's", "male"),
            ("for women", "female"), ("for woman", "female"), ("women's", "female"),
            ("for ladies", "female"), ("ladies'", "female"),
            ("i'm a man", "male"), ("i am a man", "male"),
            ("i'm a woman", "female"), ("i am a woman", "female"),
            ("my husband", "male"), ("my boyfriend", "male"),
            ("my wife", "female"), ("my girlfriend", "female"),
            ("for my brother", "male"), ("for my father", "male"),
            ("for my dad", "male"), ("for my son", "male"),
            ("for my sister", "female"), ("for my mother", "female"),
            ("for my mom", "female"), ("for my daughter", "female"),
        ]
        for signal, gender in explicit_gender_signals:
            if signal in user_texts:
                query["gender"] = gender
                break

        # ── Price extraction ──────────────────────────────────────────
        import re
        price_matches = re.findall(r"\$?\s*(\d+(?:\.\d{1,2})?)", user_texts)
        if len(price_matches) >= 2:
            nums = sorted(float(p) for p in price_matches[:2])
            query["min_price"] = nums[0]
            query["max_price"] = nums[1]
        elif len(price_matches) == 1:
            num = float(price_matches[0])
            # "under $X" or "below $X" or "less than $X" → max_price
            if any(w in user_texts for w in ["under", "below", "less than", "max", "budget"]):
                query["max_price"] = num
            else:
                query["max_price"] = num

        # ── Color detection ───────────────────────────────────────────
        colors = [
            "black", "white", "red", "blue", "navy", "green", "brown", "tan",
            "grey", "gray", "pink", "yellow", "orange", "purple", "beige",
            "cream", "gold", "silver", "rose gold", "burgundy", "olive",
        ]
        for color in colors:
            if color in user_texts:
                query["color"] = color
                break

        # ── Brand detection ───────────────────────────────────────────
        brands = [
            "nike", "adidas", "puma", "reebok", "new balance", "converse", "vans",
            "gucci", "zara", "h&m", "uniqlo", "primark", "forever 21",
            "apple", "samsung", "sony", "lg", "bose",
            "rolex", "omega", "cartier", "casio", "seiko", "fossil",
            "louis vuitton", "coach", "michael kors", "hermes", "prada",
            "chanel", "dior", "versace", "tom ford",
        ]
        for brand in brands:
            if brand in user_texts:
                query["brand"] = brand
                break

        # ── Material detection ────────────────────────────────────────
        materials = [
            "leather", "suede", "canvas", "denim", "cotton", "linen",
            "wool", "nylon", "polyester", "silk", "velvet",
        ]
        for mat in materials:
            if mat in user_texts:
                query["material"] = mat
                break

        # ── Build q from last meaningful user message ─────────────────
        last_user_msg = ""
        for m in reversed(messages):
            if m.get("role") == "user":
                last_user_msg = m["content"].strip()
                break

        stop_words = {
            "i", "me", "my", "want", "need", "looking", "for", "show", "find",
            "get", "a", "an", "the", "some", "please", "help", "im", "i'm",
            "can", "you", "do", "have", "any", "is", "are", "was", "were",
        }
        meaningful = [w for w in last_user_msg.lower().split() if w not in stop_words]
        if meaningful:
            query["q"] = " ".join(meaningful[:8])

        if not query:
            query["q"] = " ".join(user_texts.split()[:6])

        # ── Add search keywords from hints ────────────────────────────
        cat = query.get("category", "").rstrip("s").lower()
        for hint_key, hints in QueryBuilder.BRAND_HINTS.items():
            if hint_key in cat or cat in hint_key:
                query["search_keywords"] = hints
                break

        return query