from __future__ import annotations

import json
import os
import re
from dataclasses import dataclass
from typing import Any, Dict, List, Optional

from .context_utils import max_budget_amount


@dataclass
class InventoryItem:
    sku: str
    name: str
    price: Optional[float]
    in_stock: bool
    tags: List[str]
    facts: List[str]

    @classmethod
    def from_dict(cls, raw: Dict[str, Any]) -> "InventoryItem":
        sku = str(raw.get("sku") or raw.get("id") or "").strip()
        name = str(raw.get("name") or raw.get("title") or "").strip()
        price_val = raw.get("price")
        try:
            price = float(price_val) if price_val is not None else None
        except (TypeError, ValueError):
            price = None

        stock_val = raw.get("in_stock", raw.get("stock", raw.get("available")))
        in_stock = _to_bool(stock_val)

        tags = raw.get("tags") or raw.get("categories") or []
        if isinstance(tags, str):
            tags = [t.strip() for t in tags.split(",") if t.strip()]
        tags = [str(t).strip() for t in tags if str(t).strip()]

        facts = raw.get("facts") or raw.get("highlights") or []
        if isinstance(facts, str):
            facts = [f.strip() for f in facts.split(";") if f.strip()]
        facts = [str(f).strip() for f in facts if str(f).strip()]

        return cls(
            sku=sku,
            name=name,
            price=price,
            in_stock=in_stock,
            tags=tags,
            facts=facts,
        )


def _to_bool(value: Any) -> bool:
    if isinstance(value, bool):
        return value
    if isinstance(value, (int, float)):
        return value > 0
    if isinstance(value, str):
        return value.strip().lower() in ("true", "yes", "1", "in_stock", "available", "instock")
    return False


def _tokenize(text: str) -> List[str]:
    return [t for t in re.findall(r"[a-z0-9]+", text.lower()) if len(t) > 2]


class InventoryRAG:
    def __init__(self, items: List[InventoryItem]):
        self.items = items

    @classmethod
    def from_env(cls) -> Optional["InventoryRAG"]:
        path = os.getenv("INVENTORY_FEED_PATH")
        if not path:
            return None
        try:
            with open(path, "r", encoding="utf-8") as handle:
                data = json.load(handle)
        except (OSError, ValueError):
            return None

        if isinstance(data, dict):
            data = data.get("items", [])
        if not isinstance(data, list):
            return None

        items = [InventoryItem.from_dict(row) for row in data]
        items = [item for item in items if item.sku and item.name]
        return cls(items)

    def build_rag_facts(self, memory: Dict[str, Any], user_message: str) -> Optional[str]:
        memory = memory or {}
        use_case = str(memory.get("use_case") or "")
        product = str(memory.get("product") or "")
        if not use_case and not product:
            return None

        budget_amount = max_budget_amount([user_message, str(memory.get("budget_range") or "")])

        matches = self.query(use_case=use_case, product=product, budget_amount=budget_amount, limit=3)
        if not matches:
            return None
        lines = []
        for item in matches:
            line = f"SKU {item.sku}: {item.name}"
            if item.price is not None:
                line += f" | ${item.price:,.0f}"
            if item.in_stock:
                line += " | In stock"
            if item.facts:
                line += f" | {item.facts[0]}"
            lines.append(line)
        return "\n".join(lines)

    def query(
        self,
        use_case: str,
        product: str,
        budget_amount: Optional[int],
        limit: int = 3,
    ) -> List[InventoryItem]:
        tokens = _tokenize(f"{use_case} {product}")

        candidates = [item for item in self.items if item.in_stock]
        if budget_amount:
            candidates = [item for item in candidates if item.price is None or item.price <= budget_amount]

        scored: List[tuple[float, InventoryItem]] = []
        for item in candidates:
            item_text = " ".join([item.name] + item.tags).lower()
            score = 0.0
            if tokens:
                score += sum(1.0 for t in tokens if t in item_text)
            if budget_amount and item.price:
                ratio = item.price / max(budget_amount, 1)
                if ratio <= 1:
                    score += max(0.0, 1.5 - ratio)
                else:
                    score -= min(1.0, ratio - 1)
            scored.append((score, item))

        scored.sort(key=lambda pair: (pair[0], pair[1].price or 0.0), reverse=True)
        return [item for _, item in scored[:limit]]
