"""
Affiliate product catalog: product lookup, click logging, and stats queries.

All functions are plain async procedures — no classes, no state.

Design decisions:
- IP hashing uses a daily salt (date + SECRET_KEY[:16]) for GDPR compliance.
  Rotating salt prevents re-identification across days without storing PII.
- Products are fetched by (slug, language) with a graceful fallback to any
  language, so DE cards appear in EN articles rather than nothing.
- Stats are computed entirely in SQL — no Python aggregation.
"""
import hashlib
import json
import logging
from datetime import date

from .core import config, execute, fetch_all, fetch_one

logger = logging.getLogger(__name__)

VALID_CATEGORIES = ("racket", "ball", "shoe", "bag", "grip", "eyewear", "accessory")
VALID_STATUSES = ("draft", "active", "archived")
VALID_PROGRAM_STATUSES = ("active", "inactive")


def hash_ip(ip_address: str) -> str:
    """SHA256(ip + YYYY-MM-DD + SECRET_KEY[:16]) with daily salt rotation."""
    assert ip_address, "ip_address must not be empty"
    today = date.today().isoformat()
    salt = config.SECRET_KEY[:16]
    raw = f"{ip_address}:{today}:{salt}"
    return hashlib.sha256(raw.encode()).hexdigest()


async def get_all_programs(status: str | None = None) -> list[dict]:
    """Return all affiliate programs, optionally filtered by status."""
    if status:
        assert status in VALID_PROGRAM_STATUSES, f"unknown program status: {status}"
        rows = await fetch_all(
            "SELECT ap.*, ("
            "  SELECT COUNT(*) FROM affiliate_products WHERE program_id = ap.id"
            ") AS product_count"
            " FROM affiliate_programs ap WHERE ap.status = ?"
            " ORDER BY ap.name ASC",
            (status,),
        )
    else:
        rows = await fetch_all(
            "SELECT ap.*, ("
            "  SELECT COUNT(*) FROM affiliate_products WHERE program_id = ap.id"
            ") AS product_count"
            " FROM affiliate_programs ap ORDER BY ap.name ASC"
        )
    return [dict(r) for r in rows]


async def get_program(program_id: int) -> dict | None:
    """Return a single affiliate program by id."""
    assert program_id > 0, "program_id must be positive"
    row = await fetch_one(
        "SELECT * FROM affiliate_programs WHERE id = ?", (program_id,)
    )
    return dict(row) if row else None


async def get_program_by_slug(slug: str) -> dict | None:
    """Return a single affiliate program by slug."""
    assert slug, "slug must not be empty"
    row = await fetch_one(
        "SELECT * FROM affiliate_programs WHERE slug = ?", (slug,)
    )
    return dict(row) if row else None


def build_affiliate_url(product: dict, program: dict | None = None) -> str:
    """Assemble the final affiliate URL from program template + product identifier.

    Falls back to the baked product["affiliate_url"] when no program is set,
    preserving backward compatibility with products created before programs existed.
    """
    if not product.get("program_id") or not program:
        return product["affiliate_url"]
    return program["url_template"].format(
        product_id=product["product_identifier"],
        tag=program["tracking_tag"],
    )


async def get_product(slug: str, language: str = "de") -> dict | None:
    """Return active product by slug+language, falling back to any language.

    JOINs affiliate_programs so the returned dict includes program fields
    (prefixed with _program_*) for use in build_affiliate_url().
    """
    assert slug, "slug must not be empty"
    row = await fetch_one(
        "SELECT p.*, pg.url_template AS _program_url_template,"
        "       pg.tracking_tag AS _program_tracking_tag,"
        "       pg.name AS _program_name"
        " FROM affiliate_products p"
        " LEFT JOIN affiliate_programs pg ON pg.id = p.program_id"
        " WHERE p.slug = ? AND p.language = ? AND p.status = 'active'",
        (slug, language),
    )
    if row:
        return _parse_product(row)
    # Graceful fallback: show any language rather than nothing
    row = await fetch_one(
        "SELECT p.*, pg.url_template AS _program_url_template,"
        "       pg.tracking_tag AS _program_tracking_tag,"
        "       pg.name AS _program_name"
        " FROM affiliate_products p"
        " LEFT JOIN affiliate_programs pg ON pg.id = p.program_id"
        " WHERE p.slug = ? AND p.status = 'active' LIMIT 1",
        (slug,),
    )
    return _parse_product(row) if row else None


async def get_products_by_category(category: str, language: str = "de") -> list[dict]:
    """Return active products in category sorted by sort_order, with fallback."""
    assert category in VALID_CATEGORIES, f"unknown category: {category}"
    rows = await fetch_all(
        "SELECT * FROM affiliate_products"
        " WHERE category = ? AND language = ? AND status = 'active'"
        " ORDER BY sort_order ASC, id ASC",
        (category, language),
    )
    if rows:
        return [_parse_product(r) for r in rows]
    # Fallback: any language for this category
    rows = await fetch_all(
        "SELECT * FROM affiliate_products"
        " WHERE category = ? AND status = 'active'"
        " ORDER BY sort_order ASC, id ASC",
        (category,),
    )
    return [_parse_product(r) for r in rows]


async def get_all_products(
    status: str | None = None,
    retailer: str | None = None,
) -> list[dict]:
    """Admin listing — all products, optionally filtered by status and/or retailer."""
    conditions = []
    params: list = []
    if status:
        assert status in VALID_STATUSES, f"unknown status: {status}"
        conditions.append("status = ?")
        params.append(status)
    if retailer:
        conditions.append("retailer = ?")
        params.append(retailer)

    where = ("WHERE " + " AND ".join(conditions)) if conditions else ""
    rows = await fetch_all(
        f"SELECT * FROM affiliate_products {where} ORDER BY sort_order ASC, id ASC",
        tuple(params),
    )
    return [_parse_product(r) for r in rows]


async def get_click_counts() -> dict[int, int]:
    """Return {product_id: click_count} for all products (used in admin list)."""
    rows = await fetch_all(
        "SELECT product_id, COUNT(*) AS cnt FROM affiliate_clicks GROUP BY product_id"
    )
    return {r["product_id"]: r["cnt"] for r in rows}


async def log_click(
    product_id: int,
    ip_address: str,
    article_slug: str | None,
    referrer: str | None,
) -> None:
    """Insert a click event. Hashes IP for GDPR compliance."""
    assert product_id > 0, "product_id must be positive"
    assert ip_address, "ip_address must not be empty"
    ip = hash_ip(ip_address)
    await execute(
        "INSERT INTO affiliate_clicks (product_id, article_slug, referrer, ip_hash)"
        " VALUES (?, ?, ?, ?)",
        (product_id, article_slug, referrer, ip),
    )


async def get_click_stats(days_count: int = 30) -> dict:
    """Compute click statistics over the last N days, entirely in SQL."""
    assert 1 <= days_count <= 365, f"days must be 1-365, got {days_count}"

    # Total clicks in window
    total_row = await fetch_one(
        "SELECT COUNT(*) AS cnt FROM affiliate_clicks"
        " WHERE clicked_at >= datetime('now', ?)",
        (f"-{days_count} days",),
    )
    total = total_row["cnt"] if total_row else 0

    # Active product count
    product_counts = await fetch_one(
        "SELECT"
        "  SUM(CASE WHEN status='active' THEN 1 ELSE 0 END) AS active_count,"
        "  SUM(CASE WHEN status='draft'  THEN 1 ELSE 0 END) AS draft_count"
        " FROM affiliate_products"
    )

    # Top products by clicks
    top_products = await fetch_all(
        "SELECT p.id, p.name, p.slug, p.retailer, COUNT(c.id) AS click_count"
        " FROM affiliate_products p"
        " LEFT JOIN affiliate_clicks c"
        "   ON c.product_id = p.id"
        "   AND c.clicked_at >= datetime('now', ?)"
        " GROUP BY p.id"
        " ORDER BY click_count DESC"
        " LIMIT 10",
        (f"-{days_count} days",),
    )

    # Top articles by clicks
    top_articles = await fetch_all(
        "SELECT article_slug, COUNT(*) AS click_count"
        " FROM affiliate_clicks"
        " WHERE clicked_at >= datetime('now', ?)"
        "   AND article_slug IS NOT NULL"
        " GROUP BY article_slug"
        " ORDER BY click_count DESC"
        " LIMIT 10",
        (f"-{days_count} days",),
    )

    # Clicks by retailer
    by_retailer = await fetch_all(
        "SELECT p.retailer, COUNT(c.id) AS click_count"
        " FROM affiliate_products p"
        " LEFT JOIN affiliate_clicks c"
        "   ON c.product_id = p.id"
        "   AND c.clicked_at >= datetime('now', ?)"
        " GROUP BY p.retailer"
        " ORDER BY click_count DESC",
        (f"-{days_count} days",),
    )

    # Daily click counts for bar chart
    daily = await fetch_all(
        "SELECT date(clicked_at) AS day, COUNT(*) AS click_count"
        " FROM affiliate_clicks"
        " WHERE clicked_at >= datetime('now', ?)"
        " GROUP BY day"
        " ORDER BY day ASC",
        (f"-{days_count} days",),
    )

    # Normalize daily to percentage heights for CSS bar chart
    max_daily = max((r["click_count"] for r in daily), default=1)
    daily_bars = [
        {"day": r["day"], "click_count": r["click_count"],
         "pct": round(r["click_count"] / max_daily * 100)}
        for r in daily
    ]

    return {
        "total_clicks": total,
        "active_products": product_counts["active_count"] if product_counts else 0,
        "draft_products": product_counts["draft_count"] if product_counts else 0,
        "top_products": [dict(r) for r in top_products],
        "top_articles": [dict(r) for r in top_articles],
        "by_retailer": [dict(r) for r in by_retailer],
        "daily_bars": daily_bars,
        "days": days_count,
    }


async def get_distinct_retailers() -> list[str]:
    """Return sorted list of distinct retailer names for form datalist."""
    rows = await fetch_all(
        "SELECT DISTINCT retailer FROM affiliate_products"
        " WHERE retailer != '' ORDER BY retailer"
    )
    return [r["retailer"] for r in rows]


def _parse_product(row) -> dict:
    """Convert aiosqlite Row to plain dict, parsing JSON pros/cons arrays.

    If the row includes _program_* columns (from a JOIN), extracts them into
    a nested "_program" dict so build_affiliate_url() can use them directly.
    """
    d = dict(row)
    d["pros"] = json.loads(d.get("pros") or "[]")
    d["cons"] = json.loads(d.get("cons") or "[]")
    # Extract program fields added by get_product()'s JOIN
    if "_program_url_template" in d:
        if d.get("program_id") and d["_program_url_template"]:
            d["_program"] = {
                "url_template": d.pop("_program_url_template"),
                "tracking_tag": d.pop("_program_tracking_tag", ""),
                "name": d.pop("_program_name", ""),
            }
        else:
            d.pop("_program_url_template", None)
            d.pop("_program_tracking_tag", None)
            d.pop("_program_name", None)
    return d
