import json
import logging
import re
import sys
from pathlib import Path
from typing import Any, Dict, List, Literal, Optional, Union

from langchain_core.runnables import RunnableConfig
from langchain_core.tools import tool
from pydantic import BaseModel, Field

from .tool_module import SearchEngine as LegacySearchEngine

logger = logging.getLogger(__name__)

USE_LOCAL_SQLITE = True

PROJECT_ROOT = Path(__file__).resolve().parents[3]
if str(PROJECT_ROOT) not in sys.path:
    sys.path.append(str(PROJECT_ROOT))

# Preference module is deprecated; use internal SearchEngine only.
ProductSearchEngine = None

class LiteralSearch(BaseModel):
    """Lane 1: Tìm NGUYÊN VĂN — không filter, chỉ LIKE raw text."""
    model_config = {"extra": "forbid"}

    raw_text: str = Field(
        default="",
        description=(
            "Câu search NGUYÊN VĂN từ user. Copy y hệt phần liên quan đến sản phẩm. "
            "BẮT BUỘC BỎ HẾT các từ giao tiếp thừa (tìm, mua, cái, chiếc, nhé, nha, ấy, kia, thế, đồ, cho, mình...). "
            "TỰ ĐỘNG SỬA LỖI CHÍNH TẢ/TYPO nếu nhận diện được (VD: 'nghọ nghiêng' -> 'ngọ nghiêng', 'khoát' -> 'khoác'). "
            "VD: 'mình muốn tìm áo hình con ngựa ấy' -> raw_text='áo hình con ngựa'."
        ),
    )


class InferredSearch(BaseModel):
    model_config = {"extra": "ignore"}

    product_line_vn: List[str] = Field(
        default_factory=list, 
        description=(
            "Dòng sản phẩm. BẮT BUỘC ĐIỀN để filter SQL chính xác.\n"
            "QUY TẮC MAPPING (TUYỆT ĐỐI TUÂN THỦ):\n"
            "1. Thời tiết: 'trời mưa' -> ['Áo khoác', 'Áo khoác gió']; 'trời lạnh/mùa đông' -> ['Áo khoác', 'Áo len', 'Áo nỉ', 'Áo giữ nhiệt', 'Quần dài', 'Quần nỉ']; 'mùa hè/nắng nóng' -> ['Áo phông', 'Áo ba lỗ', 'Quần soóc', 'Váy liền', 'Áo khoác chống nắng'].\n"
            "2. Dịp: 'đi tiệc/đám cưới' -> ['Váy liền', 'Chân váy', 'Áo kiểu', 'Áo sơ mi', 'Blazer']; 'đi làm/công sở' -> ['Áo sơ mi', 'Áo Polo', 'Quần Khaki', 'Quần jean', 'Váy liền', 'Blazer', 'Chân váy']; 'đi gym/thể thao' -> ['Bộ thể thao', 'Áo ba lỗ', 'Áo phông', 'Quần nỉ', 'Quần leggings', 'Áo bra active']; 'đi biển' -> ['Váy liền', 'Áo phông', 'Quần soóc', 'Áo ba lỗ', 'Dép'].\n"
            "3. Lễ Tết: 30/4 & 2/9 -> ['Áo phông', 'Áo kiểu', 'Váy liền', 'Quần Khaki']; Tết -> ['Váy liền', 'Chân váy', 'Áo kiểu', 'Áo sơ mi', 'Quần Khaki', 'Bộ quần áo', 'Áo dài']; 20/10 -> ['Váy liền', 'Chân váy', 'Áo kiểu', 'Áo sơ mi', 'Blazer'].\n"
            "4. Đồ lót: 'sịp/quần lót' -> ['Quần lót', 'Quần lót đùi', 'Quần lót tam giác']; 'áo lót/bra' -> ['Áo lót', 'Áo bra active']. TỰ ĐỘNG bỏ qua gender/age nếu là đồ lót.\n"
            "5. COMBO/SET: Nếu nói 'combo đi biển', 'đồ đi làm', 'set đồ' -> KHÔNG ĐƯỢC chỉ điền mỗi 'Áo' hoặc 'Quần', PHẢI ĐIỀN ĐỦ CÁC MÓN PHÙ HỢP NGỮ CẢNH (VD: đi biển -> ['Áo phông', 'Quần soóc', 'Váy liền', 'Áo ba lỗ']). \n"
            "TUYỆT ĐỐI KHÔNG kế thừa product_line_vn từ insight nếu khách hỏi 'set đồ' hay 'combo'. PHẢI RESET và suy luận tươi!"
        )
    )
    gender_by_product: Optional[str] = Field(
        default=None, 
        description=(
            "Giới tính mục tiêu. TUYỆT ĐỐI KHÔNG TỰ SUY DIỄN nếu khách KHÔNG nhắc đến (VD: khách chỉ gõ 'áo ngọ nghiêng' -> BẮT BUỘC ĐỂ RỖNG). "
            "NẾU khách nói 'cho nam' -> điền 'men' (hệ thống sẽ tự động lấy cả men & unisex). "
            "NẾU khách nói 'cho nữ' -> điền 'women' (hệ thống sẽ tự lấy cả women & unisex). "
            "NẾU khách nói 'váy', 'đầm', 'chân váy' -> BẮT BUỘC điền 'women' (trừ khi nói rõ cho bé). "
            "NẾU khách đính chính 'cho nữ cơ mà' -> cập nhật thành 'women'."
        )
    )
    age_by_product: Optional[Literal["others", "adult", "kid"]] = Field(
        default=None, 
        description=(
            "Độ tuổi mục tiêu (others/adult/kid). TUYỆT ĐỐI KHÔNG TỰ SUY DIỄN nếu khách KHÔNG nhắc đến (VD: khách chỉ gõ 'áo ngọ nghiêng' -> BẮT BUỘC ĐỂ RỖNG). "
            "Chỉ điền 'kid' nếu khách nói 'cho bé', 'trẻ em', 'bé gái', 'bé trai'. "
            "Chỉ điền 'adult' nếu khách hỏi đồ đi làm/công sở, hoặc nhấn mạnh 'cho người lớn'."
        )
    )
    gender_target: Optional[str] = Field(default=None, description="Alias cũ của gender_by_product. Dùng để tương thích ngược.")
    age_group: Optional[str] = Field(default=None, description="Alias cũ của age_by_product. Dùng để tương thích ngược.")
    master_color: Optional[str] = Field(
        default=None, 
        description=(
            "Màu sắc chủ đạo. SUY LUẬN TỪ SỰ KIỆN: "
            "30/4 & 2/9 -> 'Màu đỏ'. "
            "Tết -> 'Màu đỏ' hoặc 'Màu vàng'. "
            "Giáng sinh -> 'Màu đỏ' hoặc 'Màu trắng' hoặc 'Tông nổi bật'. "
            "Valentine (nữ) -> 'Màu hồng'."
        )
    )
    tags: List[str] = Field(
        default_factory=list,
        description=(
            "AI suy luận ý định khách -> chọn từ các TRỤC CỐ ĐỊNH sau: "
            "Trục 1 (Màu & Tông): Màu đen, Màu trắng, Màu xám, Màu đỏ, Tông trung tính (Neutral), Tông sáng (Light), Tông nổi bật (Dark), ... "
            "Trục 2 (Dịp): Đi làm / Công sở, Đi chơi / dạo phố, Mặc nhà, Tập thể thao, Đi tiệc, Đi biển, Du lịch. "
            "Trục 3 (Mùa): Mùa hè, Mùa đông, Cả 2 mùa, Giao mùa. "
            "Trục 4 (Phong cách & Tính năng): Cơ bản (Basic), Năng động, Thanh lịch, Thoáng mát, Giữ ấm, Thấm hút, Chống nắng, Cản gió. "
            "Trục 5 (Dáng): Oversize, Slim, Regular, Wide leg, Cropped. "
            "MAPPING SỰ KIỆN: 30/4 & 2/9 -> 'Màu đỏ', 'Đi chơi / dạo phố'; Tết -> 'Màu đỏ', 'Đi chơi / dạo phố'; 20/10 -> 'Đi chơi / dạo phố', 'Đi tiệc'."
        )
    )
    keywords: List[str] = Field(
        default_factory=list, 
        description=(
            "Từ khóa BỔ TRỢ AI suy luận thêm (VD: chất liệu, tính năng, sự kiện). Tối đa 2 cụm. "
            "MAPPING THỜI TIẾT: mưa -> 'chống thấm', 'trượt nước', 'cản gió'; lạnh -> 'giữ ấm', 'dày dặn'; hè -> 'thoáng mát', 'thấm hút'. "
            "MAPPING SỰ KIỆN: 30/4 & 2/9 -> 'cờ đỏ sao vàng', 'quốc khánh', 'du xuân'; Tết -> 'Tết', 'tươi tắn', 'du xuân'; 20/10 -> 'thanh lịch', 'sang trọng'; Giáng sinh -> 'giáng sinh', 'trang trí'; Valentine -> 'Valentine', 'lãng mạn'."
        )
    )
    price_min: Optional[int] = Field(default=None, description="Giá thấp nhất (VND).")
    price_max: Optional[int] = Field(default=None, description="Giá cao nhất (VND).")
    discount_min: Optional[int] = Field(
        default=None, 
        description="NẾU khách nói 'sale', 'giảm giá', 'hàng sale', 'outlet', 'clearance' -> BẮT BUỘC set discount_min=1."
    )
    discount_max: Optional[int] = Field(default=None, description="% giảm giá tối đa.")
    discovery_mode: Optional[str] = Field(
        default=None, 
        description=(
            "NẾU khách nói 'mới nhất', 'hàng mới về', 'collection mới' -> BẮT BUỘC 'new'. "
            "NẾU khách nói 'bán chạy nhất', 'best seller', 'được mua nhiều' -> BẮT BUỘC 'best_seller'."
        )
    )
    size: Optional[str] = Field(default=None, description="Size cần tìm (Vd: S, M, L, XL, 29, 30...).")


class SearchItem(BaseModel):
    """
    Supports both the current dual-lane schema and older flat tool calls.
    Older endpoints still pass fields like query/product_name/product_line_vn directly.
    """

    model_config = {"extra": "ignore"}

    literal: LiteralSearch = Field(default_factory=LiteralSearch, description="Tìm kiếm theo văn bản thuần túy (nguyên văn người dùng).")
    inferred: InferredSearch = Field(default_factory=InferredSearch, description="Tìm kiếm dựa trên suy luận AI về các thuộc tính sản phẩm.")
    magento_ref_code: Optional[str] = Field(
        default=None, 
        description="Mã SKU sản phẩm. BẮT BUỘC ĐIỀN khi khách cho mã cụ thể HOẶC khi khách yêu cầu phối đồ/mặc cùng với sản phẩm trước đó (Lấy mã từ Latest Interest trong user_insight để gọi lại SP cũ và lấy outfit_recommendations)."
    )
    reasoning: Optional[str] = Field(default=None, description="Lý do AI chọn sản phẩm/thuộc tính này.")
    user_insight: Optional[Dict[str, Any]] = Field(default=None, description="Thông tin insight người dùng phục vụ cá nhân hóa.")

    # Legacy flat fields.
    query: Optional[str] = Field(default=None, description="Câu truy vấn gốc của người dùng.")
    raw_text: Optional[str] = Field(default=None, description="Văn bản thuần của yêu cầu tìm kiếm.")
    description: Optional[str] = Field(default=None, description="Mô tả chi tiết hơn về sản phẩm cần tìm.")
    product_name: Optional[str] = Field(default=None, description="Tên sản phẩm cụ thể.")
    product_line_vn: Optional[Union[str, List[str]]] = Field(default=None, description="Dòng sản phẩm.")
    gender_by_product: Optional[str] = Field(default=None, description="Giới tính (male, female, unisex).")
    age_by_product: Optional[str] = Field(default=None, description="Độ tuổi (others, adult, kid).")
    gender_target: Optional[str] = Field(default=None, description="Alias cho giới tính.")
    age_group: Optional[str] = Field(default=None, description="Alias cho độ tuổi.")
    master_color: Optional[str] = Field(default=None, description="Màu sắc.")
    tags: Optional[Union[str, List[str]]] = Field(default=None, description="Các tags phân loại (occ:, wthr:, func:, style:, fit:).")
    keywords: Optional[Union[str, List[str]]] = Field(default=None, description="Từ khóa tìm kiếm bổ trợ.")
    price_min: Optional[int] = Field(default=None, description="Giá tối thiểu.")
    price_max: Optional[int] = Field(default=None, description="Giá tối đa.")
    discount_min: Optional[int] = Field(default=None, description="% giảm giá tối thiểu.")
    discount_max: Optional[int] = Field(default=None, description="% giảm giá tối đa.")
    discovery_mode: Optional[str] = Field(default=None, description="new/best_seller.")
    size: Optional[str] = Field(default=None, description="Kích cỡ.")


class MultiSearchParams(BaseModel):
    searches: List[SearchItem] = Field(..., description="Danh sach cac yeu cau tim kiem.")
    reasoning: Optional[str] = None
    user_insight: Optional[Dict[str, Any]] = None


_PRODUCT_ENGINE = ProductSearchEngine() if ProductSearchEngine else None


def _coerce_list(value: Any) -> List[str]:
    if value is None:
        return []
    if isinstance(value, list):
        return [str(v).strip() for v in value if str(v).strip()]
    if isinstance(value, tuple):
        return [str(v).strip() for v in value if str(v).strip()]
    text = str(value).strip()
    if not text:
        return []
    if "/" in text:
        return [part.strip() for part in text.split("/") if part.strip()]
    return [text]


def _clean_legacy_description(description: Optional[str]) -> str:
    if not description:
        return ""
    text = str(description)
    text = re.sub(
        r"\b(product_name|description_text|product_line_vn|material_group|style|fitting|form_sleeve|form_neckline|season)\s*:\s*",
        " ",
        text,
        flags=re.IGNORECASE,
    )
    text = re.sub(r"[.;]+", " ", text)
    return re.sub(r"\s+", " ", text).strip()


def _first_text(*values: Any) -> str:
    for value in values:
        if value is None:
            continue
        text = str(value).strip()
        if text:
            return text
    return ""


def _build_literal_text(item: SearchItem) -> str:
    return _first_text(
        item.literal.raw_text,
        item.raw_text,
        item.query,
        item.product_name,
        _clean_legacy_description(item.description),
    )


def _set_if_present(target: Dict[str, Any], key: str, value: Any) -> None:
    if value is None:
        return
    if isinstance(value, list) and not value:
        return
    if value == "":
        return
    target[key] = value


def _build_inferred_payload(item: SearchItem) -> Dict[str, Any]:
    data = item.inferred.model_dump(exclude_none=True)

    if data.get("gender_target") and not data.get("gender_by_product"):
        data["gender_by_product"] = data["gender_target"]
    if data.get("age_group") and not data.get("age_by_product"):
        data["age_by_product"] = data["age_group"]

    flat_fields = [
        "gender_by_product",
        "age_by_product",
        "gender_target",
        "age_group",
        "master_color",
        "price_min",
        "price_max",
        "discount_min",
        "discount_max",
        "discovery_mode",
        "size",
    ]
    for field_name in flat_fields:
        current = data.get(field_name)
        if current not in (None, "", []):
            continue
        value = getattr(item, field_name, None)
        _set_if_present(data, field_name, value)

    if data.get("gender_target") and not data.get("gender_by_product"):
        data["gender_by_product"] = data["gender_target"]
    if data.get("age_group") and not data.get("age_by_product"):
        data["age_by_product"] = data["age_group"]

    product_lines = data.get("product_line_vn") or item.product_line_vn
    data["product_line_vn"] = _coerce_list(product_lines)
    data["tags"] = _coerce_list(data.get("tags") or item.tags)
    data["keywords"] = _coerce_list(data.get("keywords") or item.keywords)

    return {
        "product_line_vn": data.get("product_line_vn", []),
        "gender_by_product": data.get("gender_by_product"),
        "age_by_product": data.get("age_by_product"),
        "master_color": data.get("master_color"),
        "tags": data.get("tags", []),
        "keywords": data.get("keywords", []),
        "price_min": data.get("price_min"),
        "price_max": data.get("price_max"),
        "discount_min": data.get("discount_min"),
        "discount_max": data.get("discount_max"),
        "discovery_mode": data.get("discovery_mode"),
        "size": data.get("size"),
    }


def _build_lead_input(item: SearchItem) -> Any:
    if not (LeadSearchInput and ProductLiteralSearch and ProductInferredSearch):
        return None
    return LeadSearchInput(
        literal=ProductLiteralSearch(raw_text=_build_literal_text(item)),
        inferred=ProductInferredSearch(**_build_inferred_payload(item)),
        magento_ref_code=item.magento_ref_code,
        reasoning=item.reasoning,
    )


def _augment_product(product: Dict[str, Any]) -> Dict[str, Any]:
    p = dict(product)
    sku = (
        p.get("sku")
        or p.get("magento_ref_code")
        or p.get("internal_ref_code")
        or p.get("code")
        or ""
    )
    name = p.get("name") or p.get("product_name") or p.get("magento_product_name") or ""
    price = p.get("price", p.get("sale_price", p.get("sale_price_vnd", 0)))
    original_price = p.get("original_price", p.get("price_vnd", price))
    image = p.get("image") or p.get("product_image_url_thumbnail") or p.get("thumbnail_image_url") or ""
    url = p.get("url") or p.get("product_web_url") or p.get("magento_url_key") or ""

    p.setdefault("sku", sku)
    p.setdefault("code", sku)
    p.setdefault("magento_ref_code", sku)
    p.setdefault("internal_ref_code", sku)
    p.setdefault("name", name)
    p.setdefault("product_name", name)
    p.setdefault("magento_product_name", name)
    p.setdefault("price", price)
    p.setdefault("sale_price", price)
    p.setdefault("sale_price_vnd", price)
    p.setdefault("price_vnd", original_price)
    p.setdefault("image", image)
    p.setdefault("product_image_url_thumbnail", image)
    p.setdefault("thumbnail_image_url", image)
    p.setdefault("url", url)
    p.setdefault("product_web_url", url)
    p.setdefault("magento_url_key", url)
    return p


def _dedup_products(products: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    seen: set[str] = set()
    deduped: List[Dict[str, Any]] = []
    for product in products:
        sku = str(product.get("sku") or product.get("magento_ref_code") or product.get("internal_ref_code") or "")
        if sku and sku in seen:
            continue
        if sku:
            seen.add(sku)
        deduped.append(product)
    return deduped


async def _run_product_search_engine(
    item: SearchItem,
    *,
    reasoning: Optional[str],
    user_insight: Optional[Dict[str, Any]],
) -> Dict[str, Any]:
    if not _PRODUCT_ENGINE:
        raise RuntimeError("ProductSearchEngine is not available")

    req = _build_lead_input(item)
    result = await _PRODUCT_ENGINE.search(
        req,
        reasoning=item.reasoning or reasoning,
        user_insight=item.user_insight or user_insight,
    )
    products = [_augment_product(p) for p in result.get("products", [])]
    result["products"] = products
    result["engine_used"] = "product_search_engine"
    return result


async def _run_legacy_engine(item: SearchItem, *, use_sqlite: bool) -> Dict[str, Any]:
    engine = LegacySearchEngine(use_sqlite=use_sqlite)
    res = await engine.search(
        literal=_build_literal_text(item),
        inferred=_build_inferred_payload(item),
    )
    products = [_augment_product(p) for p in res.get("results", [])]
    return {
        "status": "success" if products else "no_results",
        "count": len(products),
        "tier": res.get("tier", 0),
        "search_mode": res.get("message", "legacy_search"),
        "message": res.get("message", ""),
        "products": products,
        "engine_used": "legacy_search_engine",
    }


@tool(args_schema=MultiSearchParams)
async def data_retrieval_tool(
    searches: List[SearchItem],
    reasoning: Optional[str] = None,
    user_insight: Optional[Dict[str, Any]] = None,
    config: RunnableConfig = None,
) -> str:
    """
    Tim kiem san pham Canifa bang ProductSearchEngine core.
    Wrapper nay giu compatibility voi schema cu va schema dual-lane moi.
    """
    configurable = config.get("configurable", {}) if config else {}
    db_source = configurable.get("db_source", "starrocks")

    if USE_LOCAL_SQLITE:
        use_sqlite = True
    else:
        use_sqlite = (db_source == "sqlite")

    logger.info("Gía trị truyển xuống searches: %s", searches)

    shared_user_insight = user_insight or configurable.get("user_insight")

    per_search_results: List[Dict[str, Any]] = []
    all_products: List[Dict[str, Any]] = []
    errors: List[str] = []

    for item in searches:
        literal_text = _build_literal_text(item)
        logger.info("[Tool] Searching via ProductSearchEngine: %s", literal_text)
        try:
            result = await _run_legacy_engine(item, use_sqlite=use_sqlite)
        except Exception as exc:
            logger.exception("SearchEngine failed: %s", exc)
            result = {"status": "error", "message": str(exc), "products": []}

        if result.get("status") == "error":
            errors.append(str(result.get("message", "unknown error")))

        products = [_augment_product(p) for p in result.get("products", [])]
        result["products"] = products
        result["literal_text"] = result.get("literal_text") or literal_text
        per_search_results.append(result)
        all_products.extend(products)

    products = _dedup_products(all_products)
    has_success = any(r.get("status") == "success" for r in per_search_results)
    has_products = bool(products)
    status = "success" if has_success or has_products else ("error" if errors else "no_results")

    payload = {
        "status": status,
        "count": len(products),
        "products": products,
        "results": per_search_results,
    }
    if errors:
        payload["errors"] = errors

    return json.dumps(payload, ensure_ascii=False, default=str)
