"""Lightweight, GDPR-compliant analytics using SQLite.

No cookies, no personal data, no consent needed.
Visitor uniqueness is estimated via daily-salted IP hash (not stored raw).
Data lives in a separate analytics.db to keep the main DB clean.
"""

from __future__ import annotations

import hashlib
import sqlite3
import time
from collections import Counter, defaultdict
from datetime import date, datetime, timedelta
from pathlib import Path
from urllib.parse import urlparse

from flask import Flask, request, g

_SCHEMA = """
CREATE TABLE IF NOT EXISTS page_views (
    id        INTEGER PRIMARY KEY AUTOINCREMENT,
    ts        TEXT    NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now')),
    date      TEXT    NOT NULL DEFAULT (strftime('%Y-%m-%d', 'now')),
    path      TEXT    NOT NULL,
    referrer  TEXT,
    visitor   TEXT,
    ua_type   TEXT
);

CREATE INDEX IF NOT EXISTS idx_pv_date ON page_views(date);
CREATE INDEX IF NOT EXISTS idx_pv_path ON page_views(path);

CREATE TABLE IF NOT EXISTS downloads (
    id        INTEGER PRIMARY KEY AUTOINCREMENT,
    ts        TEXT    NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now')),
    date      TEXT    NOT NULL DEFAULT (strftime('%Y-%m-%d', 'now')),
    file_type TEXT    NOT NULL,
    visitor   TEXT
);

CREATE INDEX IF NOT EXISTS idx_dl_date ON downloads(date);
"""

# Daily salt rotates so yesterday's hashes can't be correlated with today's
_daily_salt: tuple[str, str] = ("", "")


def _get_salt() -> str:
    global _daily_salt
    today = date.today().isoformat()
    if _daily_salt[0] != today:
        _daily_salt = (today, hashlib.sha256(f"ietf-analytics-{today}".encode()).hexdigest()[:16])
    return _daily_salt[1]


def _hash_visitor(ip: str) -> str:
    """Create a daily-rotating hash from IP. Cannot be reversed or correlated across days."""
    salt = _get_salt()
    return hashlib.sha256(f"{salt}:{ip}".encode()).hexdigest()[:12]


def _classify_ua(ua: str) -> str:
    """Rough bot/browser classification."""
    ua_lower = ua.lower()
    if any(b in ua_lower for b in ("bot", "spider", "crawl", "slurp", "wget", "curl", "python-requests")):
        return "bot"
    if "mobile" in ua_lower:
        return "mobile"
    return "browser"


def _get_analytics_db() -> sqlite3.Connection:
    """Get or create the analytics DB connection for this request."""
    if "analytics_db" not in g:
        db_path = Path(request.app_root or ".").parent.parent.parent / "data" / "analytics.db"
        # Fall back to app config if available
        if hasattr(g, "_analytics_db_path"):
            db_path = g._analytics_db_path
        db_path.parent.mkdir(parents=True, exist_ok=True)
        conn = sqlite3.connect(str(db_path))
        conn.row_factory = sqlite3.Row
        conn.executescript(_SCHEMA)
        g.analytics_db = conn
    return g.analytics_db


# Paths to skip (static assets, API calls, etc.)
_SKIP_PREFIXES = ("/static/", "/api/", "/favicon", "/robots.txt", "/admin/")


def init_analytics(app: Flask, db_path: str | None = None):
    """Register analytics hooks on the Flask app."""

    _resolved_db_path = Path(db_path) if db_path else (
        Path(app.root_path).parent.parent / "data" / "analytics.db"
    )

    @app.before_request
    def track_pageview():
        path = request.path

        # Skip static/API/admin routes
        if any(path.startswith(p) for p in _SKIP_PREFIXES):
            return

        g._analytics_db_path = _resolved_db_path

        try:
            conn = _get_analytics_db()
            ip = request.remote_addr or "unknown"
            visitor = _hash_visitor(ip)
            ua = request.headers.get("User-Agent", "")
            ua_type = _classify_ua(ua)

            # Skip bots from page view counts (still track downloads)
            if ua_type == "bot" and path != "/export/obsidian":
                return

            referrer = request.headers.get("Referer", "")
            # Only keep the domain of referrer
            if referrer:
                try:
                    parsed = urlparse(referrer)
                    referrer = parsed.netloc or ""
                except Exception:
                    referrer = ""

            # Track downloads separately
            if path == "/export/obsidian":
                conn.execute(
                    "INSERT INTO downloads (file_type, visitor) VALUES (?, ?)",
                    ("obsidian", visitor),
                )
                conn.commit()

            conn.execute(
                "INSERT INTO page_views (path, referrer, visitor, ua_type) VALUES (?, ?, ?, ?)",
                (path, referrer, visitor, ua_type),
            )
            conn.commit()
        except Exception:
            pass  # Analytics should never break the app

    @app.teardown_appcontext
    def close_analytics_db(exception=None):
        conn = g.pop("analytics_db", None)
        if conn is not None:
            conn.close()


def get_analytics_data(db_path: str | Path) -> dict:
    """Query analytics data for the dashboard. Returns dicts ready for rendering."""
    conn = sqlite3.connect(str(db_path))
    conn.row_factory = sqlite3.Row
    conn.executescript(_SCHEMA)

    today = date.today()
    week_ago = (today - timedelta(days=7)).isoformat()
    month_ago = (today - timedelta(days=30)).isoformat()

    # --- Overall stats ---
    total_views = conn.execute("SELECT COUNT(*) FROM page_views").fetchone()[0]
    total_visitors = conn.execute("SELECT COUNT(DISTINCT visitor || date) FROM page_views").fetchone()[0]
    total_downloads = conn.execute("SELECT COUNT(*) FROM downloads").fetchone()[0]

    today_views = conn.execute(
        "SELECT COUNT(*) FROM page_views WHERE date = ?", (today.isoformat(),)
    ).fetchone()[0]
    today_visitors = conn.execute(
        "SELECT COUNT(DISTINCT visitor) FROM page_views WHERE date = ?", (today.isoformat(),)
    ).fetchone()[0]

    week_views = conn.execute(
        "SELECT COUNT(*) FROM page_views WHERE date >= ?", (week_ago,)
    ).fetchone()[0]
    month_views = conn.execute(
        "SELECT COUNT(*) FROM page_views WHERE date >= ?", (month_ago,)
    ).fetchone()[0]

    # --- Daily views (last 30 days) ---
    daily_rows = conn.execute(
        "SELECT date, COUNT(*) as views, COUNT(DISTINCT visitor) as visitors "
        "FROM page_views WHERE date >= ? GROUP BY date ORDER BY date",
        (month_ago,),
    ).fetchall()
    daily = {
        "dates": [r["date"] for r in daily_rows],
        "views": [r["views"] for r in daily_rows],
        "visitors": [r["visitors"] for r in daily_rows],
    }

    # --- Top pages (last 30 days) ---
    page_rows = conn.execute(
        "SELECT path, COUNT(*) as views, COUNT(DISTINCT visitor) as visitors "
        "FROM page_views WHERE date >= ? GROUP BY path ORDER BY views DESC LIMIT 20",
        (month_ago,),
    ).fetchall()
    top_pages = [{"path": r["path"], "views": r["views"], "visitors": r["visitors"]} for r in page_rows]

    # --- Top referrers (last 30 days) ---
    ref_rows = conn.execute(
        "SELECT referrer, COUNT(*) as count FROM page_views "
        "WHERE date >= ? AND referrer != '' GROUP BY referrer ORDER BY count DESC LIMIT 15",
        (month_ago,),
    ).fetchall()
    top_referrers = [{"referrer": r["referrer"], "count": r["count"]} for r in ref_rows]

    # --- Downloads over time ---
    dl_rows = conn.execute(
        "SELECT date, COUNT(*) as count FROM downloads GROUP BY date ORDER BY date"
    ).fetchall()
    downloads_daily = {
        "dates": [r["date"] for r in dl_rows],
        "counts": [r["count"] for r in dl_rows],
    }

    # --- Hourly pattern (last 7 days) ---
    hourly_rows = conn.execute(
        "SELECT CAST(strftime('%H', ts) AS INTEGER) as hour, COUNT(*) as views "
        "FROM page_views WHERE date >= ? GROUP BY hour ORDER BY hour",
        (week_ago,),
    ).fetchall()
    hourly = {r["hour"]: r["views"] for r in hourly_rows}
    hourly_full = {"hours": list(range(24)), "views": [hourly.get(h, 0) for h in range(24)]}

    conn.close()

    return {
        "stats": {
            "total_views": total_views,
            "total_visitors": total_visitors,
            "total_downloads": total_downloads,
            "today_views": today_views,
            "today_visitors": today_visitors,
            "week_views": week_views,
            "month_views": month_views,
        },
        "daily": daily,
        "top_pages": top_pages,
        "top_referrers": top_referrers,
        "downloads_daily": downloads_daily,
        "hourly": hourly_full,
    }
