"""
Content domain: public article serving, markets hub, scenario widget rendering.
"""
import json
import re
from pathlib import Path

from jinja2 import Environment, FileSystemLoader
from markupsafe import Markup
from quart import Blueprint, abort, g, redirect, render_template, request

from ..analytics import fetch_analytics
from ..core import (
    REPO_ROOT,
    capture_waitlist_email,
    csrf_protect,
    feature_gate,
    fetch_all,
    fetch_one,
)
from ..i18n import get_country_name, get_translations

bp = Blueprint(
    "content",
    __name__,
    template_folder=str(Path(__file__).parent / "templates"),
)

BUILD_DIR = REPO_ROOT / "data" / "content" / "_build"

RESERVED_PREFIXES = (
    "/admin", "/auth", "/planner", "/billing", "/dashboard",
    "/directory", "/leads", "/suppliers", "/reports", "/health",
    "/sitemap", "/static", "/features", "/feedback",
)

SCENARIO_RE = re.compile(r'\[scenario:([a-z0-9_-]+)(?::([a-z]+))?\]')
PRODUCT_RE = re.compile(r'\[product:([a-z0-9_-]+)\]')
PRODUCT_GROUP_RE = re.compile(r'\[product-group:([a-z0-9_-]+)\]')

SECTION_TEMPLATES = {
    None: "partials/scenario_summary.html",
    "capex": "partials/scenario_capex.html",
    "operating": "partials/scenario_operating.html",
    "cashflow": "partials/scenario_cashflow.html",
    "returns": "partials/scenario_returns.html",
    "full": "partials/scenario_full.html",
}

# Standalone Jinja2 env for baking scenario cards into static HTML.
# Does not use a Quart request context, so url_for and t are injected
# explicitly. Baked content is always EN (admin operation).
_TEMPLATE_DIR = Path(__file__).parent / "templates"
_bake_env = Environment(loader=FileSystemLoader(str(_TEMPLATE_DIR)), autoescape=True)
_bake_env.filters["tformat"] = lambda s, **kw: s.format_map(kw)

# Hardcoded EN URL stubs — the bake env has no request context so Quart's
# url_for cannot be used. Only endpoints referenced by scenario card templates
# need to be listed here.
_BAKE_URLS: dict[str, str] = {
    "planner.index": "/en/planner/",
    "directory.index": "/en/directory/",
}
_bake_env.globals["url_for"] = lambda endpoint, **kw: _BAKE_URLS.get(endpoint, f"/{endpoint}")


def is_reserved_path(url_path: str) -> bool:
    """Check if a url_path starts with a reserved prefix."""
    clean = "/" + url_path.strip("/")
    return any(clean.startswith(p) for p in RESERVED_PREFIXES)


async def bake_scenario_cards(
    html: str,
    lang: str = "en",
    scenario_overrides: dict[str, dict] | None = None,
) -> str:
    """Replace [scenario:slug] and [scenario:slug:section] markers with rendered HTML.

    scenario_overrides: optional {slug: {calc_json, state_json, ...}} dict
    that bypasses the DB lookup (used by preview_article).
    """
    matches = list(SCENARIO_RE.finditer(html))
    if not matches:
        return html

    # Batch-fetch all referenced scenarios from DB
    slugs = list({m.group(1) for m in matches})
    placeholders = ",".join("?" * len(slugs))
    rows = await fetch_all(
        f"SELECT * FROM published_scenarios WHERE slug IN ({placeholders})",
        tuple(slugs),
    )
    scenarios = {row["slug"]: row for row in rows}

    # Merge in any overrides (preview mode — no DB row exists yet)
    if scenario_overrides:
        scenarios.update(scenario_overrides)

    for match in reversed(matches):
        slug = match.group(1)
        section = match.group(2)

        scenario = scenarios.get(slug)
        if not scenario:
            continue

        template_name = SECTION_TEMPLATES.get(section)
        if not template_name:
            continue

        calc_data = json.loads(scenario["calc_json"])
        state_data = json.loads(scenario["state_json"])

        tmpl = _bake_env.get_template(template_name)
        card_html = tmpl.render(
            scenario=scenario, d=calc_data, s=state_data,
            lang=lang, t=get_translations(lang),
        )
        html = html[:match.start()] + card_html + html[match.end():]

    return html


async def bake_product_cards(html: str, lang: str = "de") -> str:
    """Replace [product:slug] and [product-group:category] markers with rendered HTML.

    Processes markers in two passes (product first, then groups) to keep logic
    clear. Reverse iteration preserves string offsets when splicing.
    """
    from ..affiliate import get_product, get_products_by_category

    t = get_translations(lang)

    # ── Pass 1: [product:slug] ────────────────────────────────────────────────
    product_matches = list(PRODUCT_RE.finditer(html))
    if product_matches:
        slugs = list({m.group(1) for m in product_matches})
        products: dict[str, dict | None] = {}
        for slug in slugs:
            products[slug] = await get_product(slug, lang)

        tmpl = _bake_env.get_template("partials/product_card.html")
        for match in reversed(product_matches):
            slug = match.group(1)
            product = products.get(slug)
            if not product:
                continue
            card_html = tmpl.render(product=product, lang=lang, t=t)
            html = html[:match.start()] + card_html + html[match.end():]

    # ── Pass 2: [product-group:category] ─────────────────────────────────────
    group_matches = list(PRODUCT_GROUP_RE.finditer(html))
    if group_matches:
        categories = list({m.group(1) for m in group_matches})
        groups: dict[str, list] = {}
        for cat in categories:
            groups[cat] = await get_products_by_category(cat, lang)

        tmpl = _bake_env.get_template("partials/product_group.html")
        for match in reversed(group_matches):
            cat = match.group(1)
            group_products = groups.get(cat, [])
            if not group_products:
                continue
            grid_html = tmpl.render(products=group_products, category=cat, lang=lang, t=t)
            html = html[:match.start()] + grid_html + html[match.end():]

    return html


# =============================================================================
# Markets Hub
# =============================================================================

@bp.route("/markets", methods=["GET", "POST"])
@csrf_protect
@feature_gate("markets", "markets_waitlist.html")
async def markets():
    """Hub page: search + country/region filter for articles."""
    from ..core import is_flag_enabled
    if not await is_flag_enabled("markets") and request.method == "POST":
        form = await request.form
        email = form.get("email", "").strip().lower()
        if email and "@" in email:
            await capture_waitlist_email(email, intent="markets")
        return await render_template("markets_waitlist.html", confirmed=True)

    q = request.args.get("q", "").strip()
    country = request.args.get("country", "")
    region = request.args.get("region", "")

    countries = await fetch_all(
        """SELECT DISTINCT country FROM articles
           WHERE country IS NOT NULL AND country != ''
           AND status = 'published' AND published_at <= datetime('now')
           ORDER BY country"""
    )
    regions = await fetch_all(
        """SELECT DISTINCT region FROM articles
           WHERE region IS NOT NULL AND region != ''
           AND status = 'published' AND published_at <= datetime('now')
           ORDER BY region"""
    )

    articles = await _filter_articles(q, country, region)
    map_countries = await fetch_analytics("""
        SELECT country_code, country_name_en, country_slug,
               city_count, total_venues,
               avg_market_score, avg_opportunity_score,
               lat, lon
        FROM serving.pseo_country_overview
        ORDER BY total_venues DESC
    """)
    lang = g.get("lang", "en")
    for row in map_countries:
        row["country_name"] = get_country_name(row["country_name_en"], lang)
    # Sort so user's country renders last (on top in Leaflet z-order)
    user_country = g.get("user_country", "")
    if user_country and map_countries:
        map_countries = sorted(
            map_countries,
            key=lambda c: 1 if c["country_code"] == user_country else 0,
        )

    return await render_template(
        "markets.html",
        articles=articles,
        countries=[c["country"] for c in countries],
        regions=[r["region"] for r in regions],
        current_q=q,
        current_country=country,
        current_region=region,
        map_countries=map_countries,
    )


@bp.route("/markets/results")
@feature_gate("markets", "markets_waitlist.html")
async def market_results():
    """HTMX partial: filtered article cards."""
    q = request.args.get("q", "").strip()
    country = request.args.get("country", "")
    region = request.args.get("region", "")

    articles = await _filter_articles(q, country, region)
    return await render_template("partials/market_results.html", articles=articles)


_NEARBY_COUNTRIES: dict[str, tuple[str, ...]] = {
    "DE": ("AT", "CH"), "AT": ("DE", "CH"), "CH": ("DE", "AT"),
    "ES": ("PT",), "PT": ("ES",),
    "GB": ("IE",), "IE": ("GB",),
    "US": ("CA",), "CA": ("US",),
    "IT": ("CH",), "FR": ("BE", "CH"), "BE": ("FR", "NL", "DE"),
    "NL": ("BE", "DE"), "SE": ("NO", "DK", "FI"), "NO": ("SE", "DK"),
    "DK": ("SE", "NO", "DE"), "FI": ("SE",),
    "MX": ("US",), "BR": ("AR",), "AR": ("BR",),
}


def _geo_order_clause(user_country: str) -> tuple[str, list]:
    """Build ORDER BY clause that sorts user's country first, nearby second.

    Returns (order_sql, params) where order_sql starts with the geo CASE
    followed by published_at DESC.  Caller prepends 'ORDER BY'.
    """
    if not user_country:
        return "published_at DESC", []

    nearby = _NEARBY_COUNTRIES.get(user_country, ())
    if nearby:
        placeholders = ",".join("?" * len(nearby))
        geo_case = f"""CASE WHEN country = ? THEN 0
             WHEN country IN ({placeholders}) THEN 1
             ELSE 2 END,
        published_at DESC"""
        return geo_case, [user_country, *nearby]

    return """CASE WHEN country = ? THEN 0 ELSE 1 END,
        published_at DESC""", [user_country]


async def _filter_articles(q: str, country: str, region: str) -> list[dict]:
    """Query published articles for the current language, geo-sorted."""
    lang = g.get("lang", "en")
    user_country = g.get("user_country", "")
    geo_order, geo_params = _geo_order_clause(user_country)

    if q:
        # FTS query
        wheres = ["articles_fts MATCH ?"]
        params: list = [q]
        wheres.append("a.language = ?")
        params.append(lang)
        if country:
            wheres.append("a.country = ?")
            params.append(country)
        if region:
            wheres.append("a.region = ?")
            params.append(region)
        where = " AND ".join(wheres)
        # Geo-sort references a.country
        order = geo_order.replace("country", "a.country")
        return await fetch_all(
            f"""SELECT a.* FROM articles a
                JOIN articles_fts ON articles_fts.rowid = a.id
                WHERE {where}
                AND a.status = 'published' AND a.published_at <= datetime('now')
                ORDER BY {order}
                LIMIT 100""",
            tuple(params + geo_params),
        )
    else:
        wheres = ["status = 'published'", "published_at <= datetime('now')", "language = ?"]
        params = [lang]
        if country:
            wheres.append("country = ?")
            params.append(country)
        if region:
            wheres.append("region = ?")
            params.append(region)
        where = " AND ".join(wheres)
        return await fetch_all(
            f"""SELECT * FROM articles WHERE {where}
                ORDER BY {geo_order} LIMIT 100""",
            tuple(params + geo_params),
        )


# =============================================================================
# Catch-all Article Serving (must be registered LAST)
# =============================================================================

@bp.route("/<path:url_path>")
async def article_page(url_path: str):
    """Serve a published article by its url_path."""
    clean_path = "/" + url_path.strip("/")
    lang = g.get("lang", "en")

    article = await fetch_one(
        """SELECT * FROM articles
           WHERE url_path = ? AND language = ? AND status = 'published'
           AND published_at <= datetime('now')""",
        (clean_path, lang),
    )
    if not article:
        # If a scheduled (not yet live) article exists at this URL, redirect to
        # the nearest parent path rather than showing a bare 404.
        scheduled = await fetch_one(
            "SELECT 1 FROM articles WHERE url_path = ? AND language = ?",
            (clean_path, lang),
        )
        if scheduled:
            parent = clean_path.rsplit("/", 1)[0] or f"/{lang}/markets"
            return redirect(parent, 302)
        abort(404)

    # SSG articles: language-prefixed build path
    lang = article["language"] if article.get("language") else "en"
    build_path = BUILD_DIR / lang / f"{article['slug']}.html"
    if not build_path.exists():
        # Fallback: flat build path (legacy manual articles)
        build_path = BUILD_DIR / f"{article['slug']}.html"
    if not build_path.exists():
        abort(404)

    body_html = build_path.read_text()

    # Detect country-overview pages and inline map data (no JSON API fetch)
    map_locations: list[dict] = []
    parts = clean_path.strip("/").split("/")
    if len(parts) == 2 and parts[0] == "markets":
        country_slug = parts[1]
        map_locations = await fetch_analytics(
            """
            SELECT location_name AS city_name, city_slug, lat, lon,
                   COALESCE(city_padel_venue_count, 0) AS padel_venue_count,
                   market_score, opportunity_score, population,
                   nearest_padel_court_km
            FROM serving.location_profiles
            WHERE country_slug = ? AND opportunity_score > 0
            ORDER BY opportunity_score DESC
            LIMIT 300
            """,
            [country_slug],
        )
        article_rows = await fetch_all(
            """SELECT url_path FROM articles
               WHERE url_path LIKE ? AND status = 'published'
               AND published_at <= datetime('now')""",
            (f"/markets/{country_slug}/%",),
        )
        article_slugs: set[str] = set()
        for a in article_rows:
            a_parts = a["url_path"].rstrip("/").split("/")
            if len(a_parts) >= 4:
                article_slugs.add(a_parts[3])
        for row in map_locations:
            slug = row.get("city_slug")
            row["has_article"] = slug in article_slugs if slug else False

    return await render_template(
        "article_detail.html",
        article=article,
        body_html=Markup(body_html),
        map_locations=map_locations,
    )
