from typing import List, Dict, Tuple
from rank_bm25 import BM25Okapi
from qdrant_client.http.models import PointStruct
from ..repositories import repo
from ..llm_utils import LLM_UTILS
from ..config import Config
from ..logger import log

# ------------------------------
# Helpers / Business Logic
# ------------------------------


def build_bm25(products: List[Dict]) -> BM25Okapi:
    """Build a BM25 index from product titles + tags."""
    corpus = [
        (p.get("title", "") + " " + " ".join(p.get("tags", []))) for p in products
    ]
    tokenized = [c.lower().split() for c in corpus]
    return BM25Okapi(tokenized)


def encode_product(text: str, bm25: BM25Okapi) -> Tuple[List[float], Dict]:
    """Generate dense + sparse embeddings for a product."""
    dense_vector = LLM_UTILS.embeddings.embed_query(text,output_dimensionality=768)
    scores = bm25.get_scores(text.lower().split())
    indices = [i for i, v in enumerate(scores) if v > 0]
    values = [scores[i] for i in indices]
    return dense_vector, {"indices": indices, "values": values}


def index_products(store_id: str) -> int:
    """
    Fetch products, generate embeddings, and upsert into Qdrant.
    Returns number of products indexed.
    """
    try:
        products = repo.get_all_products(store_id)
        if not products:
            raise ValueError("No products found in MongoDB")

        bm25 = build_bm25(products)
        points: List[PointStruct] = []

        for product in products:
            text = product.get("title", "") + " " + " ".join(product.get("tags", []))
            dense, sparse = encode_product(text, bm25)

            points.append(
                PointStruct(
                    id=int(product["product_id"]),
                    vector={"dense": dense, "sparse": sparse},
                    payload={
                        "title": text,
                        "store_id": product["store_id"],
                        "product_idx": product["product_id"],
                    },
                )
            )

        LLM_UTILS.qdrant.upsert(
            collection_name=Config.PRODUCT_QDRANT_COLLECTION,
            points=points,
        )
        return len(points)
    except Exception as e:
        log.info(f"error during indexing: {e}")


def add_product(product, store_id: str) -> bool:
    """
    Add a single new product to Qdrant.
    Mirrors index_products() structure, for webhook-based insertion.
    """
    try:
        product_id = str(product.get("product_id"))
        title = product.get("title", "")
        tags = product.get("tags", "")

        if not product_id or not title:
            raise ValueError("Missing product id or title")

        # 🧠 Prepare text (same structure as batch version)
        text = title + " " + " ".join(tags.split(",") if tags else [])

        # Build temporary BM25 model for sparse encoding
        bm25 = build_bm25([{"title": title, "tags": tags.split(",") if tags else []}])
        dense, sparse = encode_product(text, bm25)

        # 🧩 Construct Qdrant point
        point = PointStruct(
            id=int(product_id),
            vector={"dense": dense, "sparse": sparse},
            payload={
                "title": text,
                "store_id": store_id,
                "product_idx": product_id,
            },
        )

        # 🚀 Upsert single product into Qdrant
        LLM_UTILS.qdrant.upsert(
            collection_name=Config.PRODUCT_QDRANT_COLLECTION,
            points=[point],
        )

        log.info(f"[Qdrant] ✅ Added product {product_id} ({title}) to index.")
        return True

    except Exception as e:
        log.error(f"[Qdrant] ❌ Failed to add product {product.get('id')}: {e}")
        return False


def delete_product(product_id: int, store_id: str) -> bool:
    """
    Delete a single product from Qdrant.
    """
    try:
        # 🚀 Delete the point from Qdrant
        LLM_UTILS.qdrant.delete(
            collection_name=Config.PRODUCT_QDRANT_COLLECTION,
            points_selector={
                "filter": {
                    "must": [{"key": "product_idx", "match": {"value": product_id}}]
                }
            },
        )

        log.info(f"[Qdrant] 🗑️ Deleted product {product_id} from store {store_id}.")
        return True

    except Exception as e:
        log.error(f"[Qdrant] ❌ Failed to delete product {product_id}: {e}")
        return False
