"""ingest_market_trends — scrapea una fuente de tendencias y la aterriza en Postgres `trends`.

Pipeline dispatcher del proyecto captacion_clientes. Compone un scraper del registry
(según `--source`) con `pg_insert_rows` para insertar la foto (snapshot) del día en la
tabla correspondiente de la base de datos `trends`.

Pensado para invocarse desde dag_engine con un step `function:` (un step por fuente),
o a mano: `fn run ingest_market_trends_py_pipelines --source amazon`.

Resolución del DSN de Postgres (en este orden):
  1. --dsn <dsn>
  2. env CAPTACION_DSN
  3. projects/captacion_clientes/.env  (clave CAPTACION_DSN, gitignored)
  4. pass captacion/postgres (construye el DSN; requiere gpg-agent desbloqueado)

Configuración de fuentes (keywords, categorías, ...) en
projects/captacion_clientes/config/sources.json (sin secretos).
"""

import argparse
import json
import os
import subprocess
import sys

ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
sys.path.insert(0, os.path.join(ROOT, "python", "functions"))

from datascience import (  # noqa: E402
    scrape_amazon_bestsellers,
    scrape_google_trends,
    scrape_tiktok_creative,
    scrape_aliexpress_trending,
    scrape_competitor_prices,
)
from infra import pg_insert_rows  # noqa: E402
from browser.scrape_aliexpress_cdp import scrape_aliexpress_cdp  # noqa: E402
from browser.scrape_amazon_movers_cdp import scrape_amazon_movers_cdp  # noqa: E402
from browser.scrape_amazon_search_saturation_cdp import scrape_amazon_search_saturation_cdp  # noqa: E402

PROJECT_DIR = os.path.join(ROOT, "projects", "captacion_clientes")
DEFAULT_CONFIG = os.path.join(PROJECT_DIR, "config", "sources.json")
DEFAULT_ENV = os.path.join(PROJECT_DIR, ".env")

SOURCES = ("amazon", "google_trends", "tiktok", "aliexpress",
           "aliexpress_cdp", "amazon_movers_cdp", "amazon_saturation_cdp", "competitor")


def resolve_dsn(cli_dsn: str | None) -> str:
    """Resuelve el DSN de Postgres según la precedencia documentada."""
    if cli_dsn:
        return cli_dsn
    if os.environ.get("CAPTACION_DSN"):
        return os.environ["CAPTACION_DSN"]
    if os.path.exists(DEFAULT_ENV):
        with open(DEFAULT_ENV) as fh:
            for line in fh:
                line = line.strip()
                if line.startswith("CAPTACION_DSN="):
                    return line.split("=", 1)[1].strip()
    # Fallback: construir desde pass
    try:
        pw = subprocess.check_output(
            ["pass", "show", "captacion/postgres"], text=True
        ).splitlines()[0].strip()
        return f"postgresql://captacion:{pw}@localhost:5433/trends"
    except Exception as exc:  # noqa: BLE001
        raise RuntimeError(
            "No se pudo resolver el DSN de Postgres (--dsn / CAPTACION_DSN / .env / pass)."
        ) from exc


def load_config(path: str) -> dict:
    with open(path) as fh:
        return json.load(fh)


def _read_competitor_targets(dsn: str) -> list[dict]:
    """Lee los objetivos activos de la tabla competitor_targets."""
    import psycopg2

    cols = ["competitor", "product_key", "product_name", "url", "price_selector", "currency"]
    conn = psycopg2.connect(dsn)
    try:
        cur = conn.cursor()
        cur.execute(
            "SELECT competitor, product_key, product_name, url, price_selector, currency "
            "FROM competitor_targets WHERE active = TRUE"
        )
        return [dict(zip(cols, row)) for row in cur.fetchall()]
    finally:
        conn.close()


def _dispatch(source: str, config: dict, dsn: str) -> dict:
    """Scrapea la fuente indicada y aterriza las filas en su tabla. Devuelve un resumen."""
    if source == "amazon":
        cfg = config.get("amazon", {})
        rows: list[dict] = []
        for category in cfg.get("categories", [None]):
            for list_type in cfg.get("list_types", ["bestsellers"]):
                batch = scrape_amazon_bestsellers(
                    marketplace=cfg.get("marketplace", "amazon.es"),
                    categories=[category] if category else None,
                    list_type=list_type,
                    max_items=cfg.get("max_items", 50),
                )
                niche_map = cfg.get("niche_map", {})
                for r in batch:
                    if not r.get("category"):
                        r["category"] = category or "general"
                    if category and category in niche_map:
                        r["niche"] = niche_map[category]
                rows += batch
        inserted = pg_insert_rows(dsn, "amazon_bestsellers", rows)
        return {"source": source, "scraped": len(rows), "inserted": inserted}

    if source == "google_trends":
        cfg = config.get("google_trends", {})
        niches = cfg.get("niches")
        if niches:
            # Modo por nicho: un grupo de keywords por nicho, etiquetando cada fila.
            rows = []
            for niche, kws in niches.items():
                batch = scrape_google_trends(
                    keywords=kws,
                    geo=cfg.get("geo", "ES"),
                    timeframe=cfg.get("timeframe", "now 7-d"),
                    include_related=cfg.get("include_related", True),
                )
                for r in batch:
                    r["niche"] = niche
                rows += batch
        else:
            rows = scrape_google_trends(
                keywords=cfg.get("keywords", []),
                geo=cfg.get("geo", "ES"),
                timeframe=cfg.get("timeframe", "now 7-d"),
                include_related=cfg.get("include_related", True),
            )
        inserted = pg_insert_rows(dsn, "google_trends", rows)
        return {"source": source, "scraped": len(rows), "inserted": inserted}

    if source == "tiktok":
        cfg = config.get("tiktok", {})
        rows = []
        for kind in cfg.get("kinds", ["hashtag"]):
            rows += scrape_tiktok_creative(
                country=cfg.get("country", "ES"),
                kind=kind,
                limit=cfg.get("limit", 50),
                period=cfg.get("period", 7),
            )
        inserted = pg_insert_rows(dsn, "tiktok_trends", rows)
        return {"source": source, "scraped": len(rows), "inserted": inserted}

    if source == "aliexpress":
        cfg = config.get("aliexpress", {})
        rows = []
        for query in cfg.get("queries", [None]):
            rows += scrape_aliexpress_trending(
                query=query,
                category=cfg.get("category"),
                limit=cfg.get("limit", 40),
                ship_to=cfg.get("ship_to", "ES"),
            )
        inserted = pg_insert_rows(dsn, "aliexpress_trends", rows)
        return {"source": source, "scraped": len(rows), "inserted": inserted}

    if source == "aliexpress_cdp":
        # Coste en China + nº de pedidos por nicho/producto, vía navegador (CDP 9222).
        cfg = config.get("aliexpress_cdp", {})
        rows = []
        for niche, queries in cfg.get("niches", {}).items():
            for query in queries:
                res = scrape_aliexpress_cdp(
                    query,
                    sort=cfg.get("sort", "total_tranpro_desc"),
                    limit=cfg.get("limit", 20),
                    port=cfg.get("port", 9222),
                )
                if res.get("status") != "ok":
                    continue
                for p in res.get("products", []):
                    if not p.get("price"):
                        continue
                    rows.append({
                        "category": niche, "niche": niche, "query": query,
                        "product_id": p.get("item_id"), "title": p.get("title"),
                        "price": p.get("price"), "price_orig": p.get("price_orig"),
                        "orders": p.get("orders_num"), "orders_raw": p.get("orders"),
                        "rating": p.get("rating"), "url": p.get("url"), "currency": "EUR",
                    })
        inserted = pg_insert_rows(dsn, "aliexpress_trends", rows) if rows else 0
        return {"source": source, "scraped": len(rows), "inserted": inserted}

    if source == "amazon_movers_cdp":
        # Productos que más suben en ranking de ventas (señal emergente), vía CDP.
        cfg = config.get("amazon_movers_cdp", {})
        rows = []
        for niche, cats in cfg.get("niches", {}).items():
            res = scrape_amazon_movers_cdp(
                marketplace=cfg.get("marketplace", "amazon.es"),
                categories=cats,
                port=cfg.get("port", 9222),
                max_items=cfg.get("max_items", 30),
            )
            if res.get("status") != "ok":
                continue
            for p in res.get("products", []):
                p["niche"] = niche
                p["list_type"] = "movers_shakers"
                rows.append(p)
        inserted = pg_insert_rows(dsn, "amazon_bestsellers", rows) if rows else 0
        return {"source": source, "scraped": len(rows), "inserted": inserted}

    if source == "amazon_saturation_cdp":
        # Oferta de mercado por producto: nº de resultados en Amazon.es (saturación), vía CDP.
        cfg = config.get("amazon_saturation_cdp", {})
        rows = []
        for niche, queries in cfg.get("niches", {}).items():
            for query in queries:
                res = scrape_amazon_search_saturation_cdp(
                    query,
                    marketplace=cfg.get("marketplace", "amazon.es"),
                    port=cfg.get("port", 9222),
                )
                if res.get("status") != "ok":
                    continue
                rows.append({
                    "niche": niche, "query": query,
                    "marketplace": res.get("marketplace", "amazon.es"),
                    "total_results": res.get("total_results"),
                    "sponsored_top": res.get("sponsored_top"),
                    "n_cards": res.get("n_cards"),
                })
        inserted = pg_insert_rows(dsn, "amazon_saturation", rows) if rows else 0
        return {"source": source, "scraped": len(rows), "inserted": inserted}

    if source == "competitor":
        targets = _read_competitor_targets(dsn)
        if not targets:
            return {"source": source, "scraped": 0, "inserted": 0,
                    "note": "competitor_targets vacía — inserta objetivos para vigilar precios"}
        rows = scrape_competitor_prices(targets)
        inserted = pg_insert_rows(dsn, "competitor_prices", rows)
        return {"source": source, "scraped": len(rows), "inserted": inserted}

    raise ValueError(f"source desconocida: {source!r}. Válidas: {', '.join(SOURCES)}")


def ingest_market_trends(source: str, config: str | None = None, dsn: str | None = None) -> dict:
    """Punto de entrada del pipeline (lo invoca `fn run` y dag_engine con `source` posicional).

    Resuelve la configuración y el DSN internamente, scrapea la fuente y aterriza la foto
    en Postgres. Imprime el resumen JSON por stdout y lo devuelve.
    """
    config_data = load_config(config or DEFAULT_CONFIG)
    resolved_dsn = resolve_dsn(dsn)
    summary = _dispatch(source, config_data, resolved_dsn)
    print(json.dumps(summary, ensure_ascii=False))
    return summary


def main() -> int:
    ap = argparse.ArgumentParser(description="Ingest de tendencias de mercado a Postgres trends")
    ap.add_argument("--source", required=True, choices=SOURCES, help="Fuente a scrapear")
    ap.add_argument("--config", default=DEFAULT_CONFIG, help="Ruta del JSON de configuración")
    ap.add_argument("--dsn", default=None, help="DSN Postgres (override)")
    args = ap.parse_args()

    ingest_market_trends(args.source, config=args.config, dsn=args.dsn)
    return 0


if __name__ == "__main__":
    sys.exit(main())
