"""Scraper de saturacion de la busqueda de Amazon via Chrome DevTools Protocol (CDP).

Funcion IMPURA. Mide cuanta OFERTA existe ya en Amazon para un termino de
busqueda — el numero de resultados que Amazon declara, cuantos de los primeros
cards son anuncios patrocinados y cuantos cards monta la primera pagina. Es una
senal de SATURACION de mercado: muchos resultados + muchos sponsored = nicho
disputado, mala apuesta para dropshipping; pocos resultados = hueco.

La pagina ``/s?k=<query>`` de Amazon monta sus cards por JavaScript (el GET HTTP
puro devuelve un esqueleto), asi que esta funcion renderiza la pagina en un
Chrome con remote debugging (el navegador diario residential en 9222 pasa el
anti-bot mejor que ``requests``), espera al render y extrae las metricas con un
unico ``Runtime.evaluate``.

Compone DOS funciones del registry (no reescribe transporte CDP):
  1. ``cdp_open_url_and_wait`` (pipeline) — crea tab nuevo en el Chrome remoto,
     navega a la URL de busqueda y espera ``Page.loadEventFired``.
  2. ``cdp_eval`` (browser) — evalua el JS de extraccion en la pestana cuya URL
     contiene un substring unico del query.

Devuelve SIEMPRE un dict autosuficiente (estilo del grupo market-intel): nunca
lanza. NUNCA inventa datos: si Amazon sirve un captcha devuelve
``status="captcha"``; si no hay resultados ni cards, ``status="error"``.
"""

import json
import os
import sys
import time
from datetime import datetime, timezone

sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

from browser.cdp_eval import cdp_eval
from pipelines.cdp_open_url_and_wait import cdp_open_url_and_wait

# JS de extraccion YA VALIDADO a mano contra amazon.es. Devuelve un JSON string
# con: captcha (bool), total_raw (str|None = numero de resultados tal cual lo
# escribe Amazon), n_cards (int), sponsored_top16 (int). Cubre el header tanto
# en ingles ("over 50,000 results") como en espanol ("mas de 50.000 resultados")
# porque Amazon a veces sirve el bar en ingles aun en amazon.es.
_EXTRACT_JS = r"""
(() => {
  const r={};
  r.captcha=/captcha|robot check|introduce los caracteres/i.test(document.body.innerText.slice(0,400));
  const bar=document.querySelector('[data-component-type="s-result-info-bar"]');
  const ht=(bar?bar.innerText:document.body.innerText.slice(0,400));
  const m=ht.match(/over\s+([\d.,]+)\s+results/i)||ht.match(/m[aá]s de\s+([\d.,]+)\s+resultados/i)||ht.match(/([\d.,]+)\s+results/i)||ht.match(/([\d.,]+)\s+resultados/i);
  r.total_raw=m?m[1]:null;
  const cards=[...document.querySelectorAll('div[data-component-type="s-search-result"]')];
  r.n_cards=cards.length;
  let sp=0;
  cards.slice(0,16).forEach(c=>{ if(/sponsored|patrocinado/i.test(c.innerText.slice(0,50)))sp++; });
  r.sponsored_top16=sp;
  return JSON.stringify(r);
})()
"""


def _build_url(marketplace: str, query: str) -> str:
    """URL de la busqueda de Amazon para un marketplace y query.

    Base ``https://www.<marketplace>/s?k=<query>`` con los espacios del query
    convertidos a ``+`` (formato de busqueda de Amazon).
    """
    k = "+".join(query.split())
    return f"https://www.{marketplace}/s?k={k}"


def _parse_total(total_raw: str | None) -> int | None:
    """Convierte el numero de resultados de Amazon a int.

    Amazon escribe el numero con separadores de miles que varian por locale
    (``"50,000"`` en ingles, ``"50.000"`` en espanol). Se quitan ``,`` y ``.``
    porque son separadores de millar, no decimales (el conteo de resultados es
    siempre entero). Devuelve None si no hay numero o no se puede parsear.
    """
    if not total_raw:
        return None
    digits = total_raw.replace(",", "").replace(".", "").replace(" ", "")
    if not digits.isdigit():
        return None
    return int(digits)


def scrape_amazon_search_saturation_cdp(
    query: str,
    marketplace: str = "amazon.es",
    port: int = 9222,
    timeout_s: float = 25.0,
) -> dict:
    """Mide la saturacion de la busqueda de Amazon para un termino, via CDP.

    Funcion IMPURA: necesita un Chrome con remote debugging escuchando en
    ``port`` (el chromium-personal residential en 9222 pasa el anti-bot mejor
    que ``requests``). Navega a ``/s?k=<query>``, espera al render, y extrae con
    un solo ``Runtime.evaluate`` el numero de resultados que Amazon declara,
    cuantos cards monta la primera pagina y cuantos de los primeros 16 son
    anuncios patrocinados. Nunca lanza: cualquier fallo devuelve
    ``{"status": "error"|"captcha", ...}``. NUNCA inventa datos.

    Args:
        query: Termino de busqueda (ej. ``"cepillo gato"``). Los espacios se
            convierten a ``+`` en la URL.
        marketplace: Dominio Amazon objetivo (``"amazon.es"``, ``"amazon.com"``,
            ...). Determina la URL.
        port: Puerto de remote debugging del Chrome a usar. Default 9222 (el
            chromium-personal residential de produccion). Para un Chrome aislado
            apunta a 9333 (el del browser_mcp).
        timeout_s: Timeout (segundos) para la navegacion. Default 25.0. Tras el
            load event la funcion espera ~2s extra para que el grid monte async.

    Returns:
        dict autosuficiente. En exito::

            {
                "status": "ok",
                "source": "amazon_saturation",
                "query": <query>,
                "marketplace": <marketplace>,
                "total_results": <int|None>,   # nº de resultados que declara Amazon
                "sponsored_top": <int>,        # anuncios entre los primeros 16 cards
                "n_cards": <int>,              # cards en la primera pagina
                "scraped_at": <ISO8601 UTC>,
            }

        ``total_results`` puede ser None aunque el resto sea valido (Amazon a
        veces oculta el contador de resultados para algunas busquedas). En ese
        caso, si hay cards (``n_cards > 0``) el status sigue siendo ``"ok"``.

        En captcha::

            {"status": "captcha", "source": "amazon_saturation", "query": ...,
             "marketplace": ..., "total_results": None, "sponsored_top": 0,
             "n_cards": 0, "scraped_at": ..., "error": <msg>}

        En error (sin captcha pero sin resultados ni cards)::

            {"status": "error", ... , "error": <msg>}
    """
    scraped_at = datetime.now(timezone.utc).isoformat()

    def _fail(status: str, error: str) -> dict:
        return {
            "status": status,
            "source": "amazon_saturation",
            "query": query,
            "marketplace": marketplace,
            "total_results": None,
            "sponsored_top": 0,
            "n_cards": 0,
            "scraped_at": scraped_at,
            "error": error,
        }

    url = _build_url(marketplace, query)
    # Substring unico del query en la URL para localizar la pestana en cdp_eval.
    first_word = (query.split() or [""])[0]
    target_substr = "k=" + first_word

    # 1. Navegar: crea tab nuevo en el Chrome remoto y espera el load event.
    try:
        cdp_open_url_and_wait(port, url, int(timeout_s))
    except Exception as e:  # noqa: BLE001 — RuntimeError de cdp_open_url_and_wait
        msg = str(e)
        if (
            "no se pudo crear tab" in msg
            or "URLError" in msg
            or "Connection refused" in msg
            or "timeout" in msg.lower()
        ):
            msg = (
                f"no hay Chrome usable en el puerto {port} "
                f"(¿remote debugging activo?): {e}"
            )
        return _fail("error", msg)

    # 2. Esperar al render async del grid antes de extraer.
    time.sleep(2.0)

    # 3. Extraer las metricas con un unico Runtime.evaluate.
    r = cdp_eval(
        _EXTRACT_JS,
        port=port,
        target_url_substr=target_substr,
        timeout_s=max(10.0, timeout_s),
    )
    if not r.get("ok"):
        return _fail("error", r.get("error") or "cdp_eval fallo sin mensaje")

    raw = r.get("value")
    try:
        data = json.loads(raw) if isinstance(raw, str) else (raw or {})
    except (TypeError, ValueError) as e:
        return _fail("error", f"extraccion no devolvio JSON valido: {e}")

    if data.get("captcha"):
        return _fail(
            "captcha", "Amazon sirvio un captcha / interstitial anti-bot"
        )

    total_results = _parse_total(data.get("total_raw"))
    n_cards = int(data.get("n_cards") or 0)
    sponsored_top = int(data.get("sponsored_top16") or 0)

    # Sin resultados declarados Y sin cards = pagina degradada / sin ofertas.
    if total_results is None and n_cards == 0:
        return _fail(
            "error",
            "no hay resultados ni cards (¿query sin oferta, pagina degradada "
            "o chromium no logueado?)",
        )

    return {
        "status": "ok",
        "source": "amazon_saturation",
        "query": query,
        "marketplace": marketplace,
        "total_results": total_results,
        "sponsored_top": sponsored_top,
        "n_cards": n_cards,
        "scraped_at": scraped_at,
    }


if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(
        description="Scraper de saturacion de la busqueda de Amazon via CDP."
    )
    parser.add_argument("query", nargs="?", default="cepillo gato")
    parser.add_argument("--marketplace", default="amazon.es")
    parser.add_argument("--port", type=int, default=9222)
    parser.add_argument("--timeout-s", type=float, default=25.0)
    args = parser.parse_args()

    out = scrape_amazon_search_saturation_cdp(
        query=args.query,
        marketplace=args.marketplace,
        port=args.port,
        timeout_s=args.timeout_s,
    )
    print(json.dumps(out, ensure_ascii=False, indent=2))
