#!/usr/bin/env python3
import argparse
import json
import sys
import re
from typing import Any, Optional, Tuple

import requests

DEFAULT_CURRENCY = "SEK"


def get_minmax_num(x: Any) -> Optional[Tuple[float, float]]:
    if not isinstance(x, dict):
        return None
    mn = x.get("min")
    mx = x.get("max")
    if isinstance(mn, (int, float)) and isinstance(mx, (int, float)):
        return float(mn), float(mx)
    return None


def get_num(x: Any) -> Optional[float]:
    if isinstance(x, (int, float)):
        return float(x)
    if isinstance(x, str):
        # handle commas
        t = x.strip().replace(" ", "")
        t = t.replace("\u00a0", "")
        # Swedish often uses comma as decimal
        if re.fullmatch(r"\d{1,3}(?:\.\d{3})*,\d+", t):
            t = t.replace(".", "").replace(",", ".")
        else:
            t = t.replace(",", ".")
        try:
            return float(t)
        except Exception:
            return None
    return None


def find_currency(data: Any) -> Optional[str]:
    # Try to detect currency-like keys/strings
    needles = {"SEK", "kr", "kronor", "currency"}

    def walk(x: Any):
        if isinstance(x, dict):
            for k, v in x.items():
                lk = str(k)
                if lk in needles and lk != "currency":
                    # key itself is a currency
                    return lk if lk != "kr" else DEFAULT_CURRENCY
                if isinstance(v, str) and v.upper() in {"SEK", "USD", "EUR", "GBP"}:
                    return v.upper()
                r = walk(v)
                if r:
                    return r
        elif isinstance(x, list):
            for v in x:
                r = walk(v)
                if r:
                    return r
        else:
            if isinstance(x, str) and x.upper() == "SEK":
                return "SEK"
        return None

    return walk(data)


def extract_price_from_json(data: Any) -> Tuple[Optional[float], Optional[str], Optional[str]]:
    """Return (priceValue, currency, evidence) or (None, None, None)."""

    pageProps = None
    if isinstance(data, dict):
        pageProps = data.get("pageProps")

    def choose_price_from_dict(d: dict, path: str, prefer: Optional[str] = None) -> Optional[Tuple[float, str]]:
        # prefer:
        #  - "listPrice" => listPrice first
        #  - "sellingPrice" => sellingPrice first
        #  - None => sellingPrice first (deal price)
        keys = ("sellingPrice", "listPrice")
        if prefer == "listPrice":
            keys = ("listPrice", "sellingPrice")
        elif prefer == "sellingPrice":
            keys = ("sellingPrice", "listPrice")

        for key in keys:
            if key in d:
                sp = d.get(key)
                mm = get_minmax_num(sp)
                if mm:
                    mn, mx = mm
                    if abs(mn - mx) < 1e-9:
                        return float(mn), f"{path}.{key}.min=max"
                    # if range, pick the lower bound as "current deal" best-effort
                    return float(mn), f"{path}.{key}.min({mn})<=price<=max({mx})"
                n = get_num(sp)
                if n is not None:
                    return float(n), f"{path}.{key}"
        return None

    if pageProps and isinstance(pageProps, dict):
        # Most common Ahlens/Next structure (from what we observed)
        prod = pageProps.get("product")
        if isinstance(prod, dict):
            cand = choose_price_from_dict(prod, "pageProps.product")
            if cand:
                val, evidence = cand
                return val, find_currency(data) or DEFAULT_CURRENCY, evidence

        init = pageProps.get("initialVariant")
        if isinstance(init, dict):
            cand = choose_price_from_dict(init, "pageProps.initialVariant")
            if cand:
                val, evidence = cand
                return val, find_currency(data) or DEFAULT_CURRENCY, evidence

        pg = pageProps.get("productGroup")
        if isinstance(pg, dict):
            # productGroup.products is often an array of products/variants
            products = pg.get("products")
            if isinstance(products, list) and products:
                for idx, p in enumerate(products[:10]):
                    if isinstance(p, dict):
                        cand = choose_price_from_dict(p, f"pageProps.productGroup.products[{idx}]")
                        if cand:
                            val, evidence = cand
                            return val, find_currency(data) or DEFAULT_CURRENCY, evidence

    # Generic fallback: recursive search for sellingPrice/listPrice dicts with min/max
    # Choose the smallest min among candidates (best-effort for current deal)
    candidates = []  # (value, evidence)

    def is_ignored_path(path: str) -> bool:
        pl = (path or "").lower()
        # Avoid “recommended” / “customers also bought” style modules.
        if "serversidereco" in pl or "recommendationlists" in pl:
            return True
        if "customersalsobought" in pl or "also" in pl and "bought" in pl:
            return True
        # Avoid shipping/delivery prices.
        if "shipping" in pl or "delivery" in pl:
            return True
        return False

    def walk(x: Any, path: str):
        if isinstance(x, dict):
            # detect dicts shaped like sellingPrice/listPrice
            for key in ("sellingPrice", "listPrice"):
                if key in x:
                    if is_ignored_path(path):
                        continue
                    mm = get_minmax_num(x.get(key))
                    if mm:
                        mn, mx = mm
                        if isinstance(mn, float) and isinstance(mx, float):
                            if mn == mx:
                                candidates.append((mn, f"{path}.{key}.min=max"))
                            else:
                                candidates.append((mn, f"{path}.{key}.min({mn})<=max({mx})"))
            for k, v in x.items():
                walk(v, f"{path}.{k}" if path else str(k))
        elif isinstance(x, list):
            for i, v in enumerate(x[:300]):
                walk(v, f"{path}[{i}]")

    walk(data if pageProps is None else data, "")

    if candidates:
        # pick smallest candidate min (deal)
        val, evidence = sorted(candidates, key=lambda t: t[0])[0]
        return val, find_currency(data) or DEFAULT_CURRENCY, evidence

    return None, None, None


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--url", required=True)
    ap.add_argument("--pricehint", default="")
    ap.add_argument("--timeout", type=int, default=30)
    args = ap.parse_args()

    url = args.url

    headers = {
        "User-Agent": "OpenClaw price-monitor/1.0 (+https://openclaw.ai)"
    }

    r = requests.get(url, headers=headers, timeout=args.timeout)
    r.raise_for_status()

    content_type = r.headers.get("content-type", "")
    text = r.text

    data = None
    # Try JSON first
    try:
        data = r.json()
    except Exception:
        try:
            data = json.loads(text)
        except Exception:
            data = None

    if data is None:
        print(json.dumps({"ok": False, "error": "not-json", "url": url}))
        return

    # Prefer listPrice when the caller asks for it.
    prefer = None
    ph = (args.pricehint or "").lower()
    if "listprice" in ph:
        prefer = "listPrice"
    elif "sellingprice" in ph:
        prefer = "sellingPrice"

    # Re-run extraction with preference.
    # For simplicity, we pass preference by temporarily patching choose order
    # inside extract_price_from_json: implement by selecting from same keys.
    # (We keep extract_price_from_json as-is for generic fallback.)

    # Implement preference-aware selection for common Ahlens structure.
    pageProps = data.get("pageProps") if isinstance(data, dict) else None
    price = None
    currency = None
    evidence = None
    if isinstance(pageProps, dict):
        for container_name in ("product", "initialVariant"):
            container = pageProps.get(container_name)
            if isinstance(container, dict):
                # look for sellingPrice/listPrice dicts
                for key in (["listPrice", "sellingPrice"] if prefer == "listPrice" else ["sellingPrice", "listPrice"]):
                    if key in container:
                        mm = get_minmax_num(container.get(key))
                        if mm:
                            mn, mx = mm
                            if mn == mx:
                                price = mn
                                evidence = f"pageProps.{container_name}.{key}.min=max"
                            else:
                                price = mn
                                evidence = f"pageProps.{container_name}.{key}.min({mn})<=price<=max({mx})"
                            currency = find_currency(data) or DEFAULT_CURRENCY
                            break
                if price is not None:
                    break
    if price is None:
        price, currency, evidence = extract_price_from_json(data)
    if price is None:
        print(json.dumps({"ok": False, "error": "price-not-found", "url": url}))
        return

    out = {
        "ok": True,
        "url": url,
        "priceValue": price,
        "currency": currency,
        "evidence": evidence,
        "source": "json-script",
        "contentType": content_type,
    }
    print(json.dumps(out))


if __name__ == "__main__":
    main()
