"""
Padelnomics - Application factory and entry point.
"""
import json
import time
from pathlib import Path

from quart import Quart, Response, abort, g, redirect, render_template, request, session, url_for

from .analytics import close_analytics_db, open_analytics_db
from .core import (
    close_db,
    config,
    get_csrf_token,
    init_db,
    is_flag_enabled,
    setup_logging,
    setup_request_id,
)

setup_logging()
from .i18n import LANG_BLUEPRINTS, SUPPORTED_LANGS, get_country_name, get_translations  # noqa: E402

_ASSET_VERSION = str(int(time.time()))


def _detect_lang() -> str:
    """Detect preferred language from cookie then Accept-Language header."""
    cookie_lang = request.cookies.get("lang", "")
    if cookie_lang in SUPPORTED_LANGS:
        return cookie_lang
    accept = request.headers.get("Accept-Language", "")
    for part in accept.split(","):
        tag = part.split(";")[0].strip()[:2].lower()
        if tag in SUPPORTED_LANGS:
            return tag
    return "en"


def _fmt_currency(n) -> str:
    """Format currency using request-context symbol and locale style."""
    sym = getattr(g, "currency_sym", "\u20ac")
    eu_style = getattr(g, "currency_eu_style", True)
    n = round(float(n))
    s = f"{abs(n):,}"
    if eu_style:
        s = s.replace(",", ".")
    return f"-{sym}{s}" if n < 0 else f"{sym}{s}"


def _fmt_k(n) -> str:
    """Short currency: €50K, €1.2M, or full _fmt_currency."""
    sym = getattr(g, "currency_sym", "\u20ac")
    n = float(n)
    if abs(n) >= 1_000_000:
        return f"{sym}{n/1_000_000:.1f}M"
    if abs(n) >= 1_000:
        return f"{sym}{n/1_000:.0f}K"
    return _fmt_currency(n)


def _fmt_pct(n) -> str:
    """Format fraction as percentage: 0.152 → '15.2%'."""
    return f"{float(n) * 100:.1f}%"


def _fmt_x(n) -> str:
    """Format as MOIC multiple: 2.30x."""
    return f"{float(n):.2f}x"


def _fmt_n(n) -> str:
    """Format integer with locale-aware thousands separator: 1.234 or 1,234."""
    eu_style = getattr(g, "currency_eu_style", True)
    s = f"{round(float(n)):,}"
    return s.replace(",", ".") if eu_style else s


def _tformat(s: str, **kwargs) -> str:
    """Format a translation string with named placeholders.

    Usage: {{ t.some_key | tformat(count=total, name=supplier.name) }}
    JSON value: "Browse {count}+ suppliers from {name}"
    """
    return s.format_map(kwargs)


def create_app() -> Quart:
    """Create and configure the Quart application."""

    pkg_dir = Path(__file__).parent

    app = Quart(
        __name__,
        template_folder=str(pkg_dir / "templates"),
        static_folder=str(pkg_dir / "static"),
    )

    app.secret_key = config.SECRET_KEY

    # Jinja2 filters
    app.jinja_env.filters["fmt_currency"] = _fmt_currency
    app.jinja_env.filters["fmt_k"] = _fmt_k
    app.jinja_env.filters["fmt_pct"] = _fmt_pct
    app.jinja_env.filters["fmt_x"] = _fmt_x
    app.jinja_env.filters["fmt_n"] = _fmt_n
    app.jinja_env.filters["tformat"] = _tformat  # translate with placeholders: {{ t.key | tformat(count=n) }}
    app.jinja_env.filters["country_name"] = get_country_name  # {{ article.country | country_name(lang) }}
    app.jinja_env.filters["fromjson"] = json.loads  # {{ job.payload | fromjson }}

    # Session config
    app.config["SESSION_COOKIE_SECURE"] = not config.DEBUG
    app.config["SESSION_COOKIE_HTTPONLY"] = True
    app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
    app.config["PERMANENT_SESSION_LIFETIME"] = 60 * 60 * 24 * config.SESSION_LIFETIME_DAYS

    # -------------------------------------------------------------------------
    # Language URL routing
    # -------------------------------------------------------------------------

    @app.url_value_preprocessor
    def pull_lang(endpoint, values):
        """Pop <lang> from URL values and stash in g.lang."""
        if values and "lang" in values:
            g.lang = values.pop("lang")

    @app.url_defaults
    def inject_lang(endpoint, values):
        """Auto-inject g.lang into url_for() calls on lang-prefixed blueprints."""
        if endpoint and endpoint.partition(".")[0] in LANG_BLUEPRINTS:
            values.setdefault("lang", g.get("lang", "en"))

    # -------------------------------------------------------------------------
    # Database lifecycle
    # -------------------------------------------------------------------------

    @app.before_serving
    async def startup():
        await init_db()
        open_analytics_db()

    @app.after_serving
    async def shutdown():
        await close_db()
        close_analytics_db()

    # -------------------------------------------------------------------------
    # Per-request hooks
    # -------------------------------------------------------------------------

    @app.before_request
    async def set_user_geo():
        """Stash Cloudflare geo headers in g for proximity sorting.

        Requires nginx: proxy_set_header CF-IPCountry $http_cf_ipcountry;
                        proxy_set_header CF-RegionCode $http_cf_regioncode;
                        proxy_set_header CF-IPCity $http_cf_ipcity;
        """
        g.user_country = request.headers.get("CF-IPCountry", "").upper() or ""
        g.user_region = request.headers.get("CF-RegionCode", "") or ""
        g.user_city = request.headers.get("CF-IPCity", "") or ""

    @app.before_request
    async def validate_lang():
        """404 unsupported language prefixes (e.g. /fr/terms)."""
        lang = g.get("lang")
        if lang is not None and lang not in SUPPORTED_LANGS:
            abort(404)

    @app.before_request
    async def load_user():
        """Load current user + subscription + roles before each request."""
        g.user = None
        g.subscription = None
        user_id = session.get("user_id")
        if user_id:
            from .core import fetch_one as _fetch_one
            row = await _fetch_one(
                """SELECT u.*,
                          bc.provider_customer_id,
                          (SELECT GROUP_CONCAT(role) FROM user_roles WHERE user_id = u.id) AS roles_csv,
                          s.id AS sub_id, s.plan, s.status AS sub_status,
                          s.provider_subscription_id, s.current_period_end
                   FROM users u
                   LEFT JOIN billing_customers bc ON bc.user_id = u.id
                   LEFT JOIN subscriptions s ON s.id = (
                       SELECT id FROM subscriptions
                       WHERE user_id = u.id
                       ORDER BY created_at DESC LIMIT 1
                   )
                   WHERE u.id = ? AND u.deleted_at IS NULL""",
                (user_id,),
            )
            if row:
                g.user = dict(row)
                g.user["roles"] = row["roles_csv"].split(",") if row["roles_csv"] else []
                if row["sub_id"]:
                    g.subscription = {
                        "id": row["sub_id"], "plan": row["plan"],
                        "status": row["sub_status"],
                        "provider_subscription_id": row["provider_subscription_id"],
                        "current_period_end": row["current_period_end"],
                    }

    @app.after_request
    async def add_security_headers(response):
        response.headers["X-Content-Type-Options"] = "nosniff"
        response.headers["X-Frame-Options"] = "DENY"
        response.headers["X-XSS-Protection"] = "1; mode=block"
        if not config.DEBUG:
            response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
        return response

    @app.after_request
    async def set_lang_cookie(response):
        """Persist detected/current language in a long-lived cookie."""
        lang = g.get("lang")
        if lang and lang in SUPPORTED_LANGS:
            current_cookie = request.cookies.get("lang", "")
            if current_cookie != lang:
                response.set_cookie(
                    "lang", lang,
                    max_age=60 * 60 * 24 * 365,  # 1 year
                    samesite="Lax",
                    secure=not config.DEBUG,
                    httponly=False,  # JS may read for analytics
                )
        return response

    # -------------------------------------------------------------------------
    # Template context globals
    # -------------------------------------------------------------------------

    @app.context_processor
    def inject_globals():
        from .core import utcnow as _utcnow
        lang = g.get("lang") or _detect_lang()
        g.lang = lang  # ensure g.lang is always set (e.g. for dashboard/billing routes)
        effective_lang = lang if lang in SUPPORTED_LANGS else "en"
        return {
            "config": config,
            "user": g.get("user"),
            "subscription": g.get("subscription"),
            "is_admin": "admin" in (g.get("user") or {}).get("roles", []),
            "now": _utcnow(),
            "csrf_token": get_csrf_token,
            "ab_variant": getattr(g, "ab_variant", None),
            "ab_tag": getattr(g, "ab_tag", None),
            "user_country": g.get("user_country", ""),
            "user_city": g.get("user_city", ""),
            "lang": effective_lang,
            "t": get_translations(effective_lang),
            "v": _ASSET_VERSION,
            "flag": is_flag_enabled,
        }

    # -------------------------------------------------------------------------
    # App-level routes (no lang prefix)
    # -------------------------------------------------------------------------

    # Root: detect language and redirect
    @app.route("/")
    async def root():
        lang = _detect_lang()
        return redirect(url_for("public.landing", lang=lang), 301)

    # robots.txt must live at root (not under /<lang>)
    @app.route("/robots.txt")
    async def robots_txt():
        base = config.BASE_URL.rstrip("/")
        body = (
            "User-agent: *\n"
            "Disallow: /admin/\n"
            "Disallow: /auth/\n"
            "Disallow: /dashboard/\n"
            "Disallow: /billing/\n"
            "Disallow: /directory/results\n"
            f"Sitemap: {base}/sitemap.xml\n"
        )
        return Response(body, content_type="text/plain")

    @app.route("/sitemap.xml")
    async def sitemap():
        from .sitemap import sitemap_response
        return await sitemap_response(config.BASE_URL)

    # -------------------------------------------------------------------------
    # Error pages
    # -------------------------------------------------------------------------

    def _error_lang() -> str:
        """Best-effort language from URL path prefix (no g.lang in error handlers)."""
        path = request.path
        if path.startswith("/de/"):
            return "de"
        return "en"

    @app.errorhandler(404)
    async def handle_404(error):
        import re
        lang = _error_lang()
        t = get_translations(lang)
        country_slug = None
        country_name = None
        m = re.match(r"^/(?:en|de)/markets/([^/]+)/[^/]+/?$", request.path)
        if m:
            country_slug = m.group(1)
            country_name = country_slug.replace("-", " ").title()
        return await render_template(
            "404.html", lang=lang, t=t, country_slug=country_slug,
            country_name=country_name or "",
        ), 404

    @app.errorhandler(500)
    async def handle_500(error):
        app.logger.exception("Unhandled 500 error: %s", error)
        lang = _error_lang()
        t = get_translations(lang)
        return await render_template("500.html", lang=lang, t=t), 500

    # Health check
    @app.route("/health")
    async def health():
        from .core import fetch_one
        try:
            await fetch_one("SELECT 1")
            return {"status": "healthy", "db": "ok"}
        except Exception as e:
            return {"status": "unhealthy", "db": str(e)}, 500

    # -------------------------------------------------------------------------
    # Affiliate click redirect — language-agnostic, no blueprint prefix
    # -------------------------------------------------------------------------

    @app.route("/go/<slug>")
    async def affiliate_redirect(slug: str):
        """302 redirect to affiliate URL, logging the click.

        Uses 302 (not 301) so every hit is tracked — browsers don't cache 302s.
        Extracts article_slug and lang from Referer header best-effort.
        """
        from .affiliate import build_affiliate_url, get_product, log_click
        from .core import check_rate_limit

        # Extract lang from Referer path (e.g. /de/blog/... → "de"), default de
        referer = request.headers.get("Referer", "")
        lang = "de"
        article_slug = None
        if referer:
            try:
                from urllib.parse import urlparse
                ref_path = urlparse(referer).path
                parts = ref_path.strip("/").split("/")
                if parts and len(parts[0]) == 2:
                    lang = parts[0]
                    if len(parts) > 1:
                        article_slug = parts[-1] or None
            except Exception:
                pass

        product = await get_product(slug, lang)
        if not product:
            abort(404)

        # Assemble URL from program template; falls back to baked affiliate_url
        url = build_affiliate_url(product, product.get("_program"))

        ip = request.remote_addr or "unknown"
        allowed, _info = await check_rate_limit(f"aff:{ip}", limit=60, window=60)
        if not allowed:
            # Still redirect even if rate-limited; just don't log the click
            return redirect(url, 302)

        await log_click(product["id"], ip, article_slug, referer or None)
        return redirect(url, 302)

    # Legacy 301 redirects — bookmarked/cached URLs before lang prefixes existed
    @app.route("/terms")
    async def legacy_terms():
        return redirect("/en/terms", 301)

    @app.route("/privacy")
    async def legacy_privacy():
        return redirect("/en/privacy", 301)

    @app.route("/imprint")
    async def legacy_imprint():
        return redirect("/en/imprint", 301)

    @app.route("/about")
    async def legacy_about():
        return redirect("/en/about", 301)

    @app.route("/features")
    async def legacy_features():
        return redirect("/en/features", 301)

    @app.route("/suppliers")
    async def legacy_suppliers():
        return redirect("/en/suppliers", 301)

    @app.route("/market-score")
    async def legacy_market_score():
        return redirect("/en/market-score", 301)

    # -------------------------------------------------------------------------
    # Blueprint registration
    # -------------------------------------------------------------------------

    from .admin.pipeline_routes import bp as pipeline_bp
    from .admin.pseo_routes import bp as pseo_bp
    from .admin.routes import bp as admin_bp
    from .api import bp as api_bp
    from .auth.routes import bp as auth_bp
    from .billing.routes import bp as billing_bp
    from .content.routes import bp as content_bp
    from .dashboard.routes import bp as dashboard_bp
    from .directory.routes import bp as directory_bp
    from .leads.routes import bp as leads_bp
    from .planner.routes import bp as planner_bp
    from .public.routes import bp as public_bp
    from .reports.routes import bp as reports_bp
    from .suppliers.routes import bp as suppliers_bp
    from .webhooks import bp as webhooks_bp

    # Lang-prefixed blueprints (SEO-relevant, public-facing)
    app.register_blueprint(public_bp, url_prefix="/<lang>")
    app.register_blueprint(planner_bp, url_prefix="/<lang>/planner")
    app.register_blueprint(directory_bp, url_prefix="/<lang>/directory")
    app.register_blueprint(leads_bp, url_prefix="/<lang>/leads")
    app.register_blueprint(suppliers_bp, url_prefix="/<lang>/suppliers")
    app.register_blueprint(reports_bp, url_prefix="/<lang>/reports")

    # Non-prefixed blueprints (internal / behind auth)
    app.register_blueprint(auth_bp)
    app.register_blueprint(dashboard_bp)
    app.register_blueprint(billing_bp)
    app.register_blueprint(admin_bp)
    app.register_blueprint(pseo_bp)
    app.register_blueprint(pipeline_bp)
    app.register_blueprint(webhooks_bp)

    # JSON API for interactive maps (no lang prefix)
    app.register_blueprint(api_bp, url_prefix="/api")

    # Content catch-all LAST — lives under /<lang> too
    app.register_blueprint(content_bp, url_prefix="/<lang>")

    # Request ID tracking
    setup_request_id(app)

    return app


app = create_app()


if __name__ == "__main__":
    import os
    port = int(os.environ.get("PORT", 5000))
    app.run(debug=config.DEBUG, port=port)
