"""Analytics query layer over SQLiteStore.

Provides filtered queries, aggregation, and time-series rollups
for DAG snapshots, audit events, and KV data.
Stdlib-only (Tier 0 compliant).
"""

from __future__ import annotations

import json
import statistics
from dataclasses import dataclass, field
from typing import Any


@dataclass
class QueryFilter:
    """Declarative filter for analytics queries."""

    start_date: str | None = None
    end_date: str | None = None
    event_types: list[str] = field(default_factory=list)
    actors: list[str] = field(default_factory=list)
    name_pattern: str | None = None
    metadata_tags: dict[str, str] = field(default_factory=dict)
    limit: int = 100
    offset: int = 0


@dataclass
class AggregateResult:
    """Result of an aggregation query."""

    count: int = 0
    groups: dict[str, int] = field(default_factory=dict)
    numeric_stats: dict[str, float] = field(default_factory=dict)

    def to_dict(self) -> dict:
        """Serialize to plain dict."""
        d: dict[str, Any] = {"count": self.count}
        if self.groups:
            d["groups"] = self.groups
        if self.numeric_stats:
            d["stats"] = self.numeric_stats
        return d


@dataclass
class TimeSeriesBucket:
    """Single bucket in a time-series rollup."""

    period: str
    count: int = 0
    values: list[float] = field(default_factory=list)

    @property
    def total(self) -> float:
        return sum(self.values)

    @property
    def mean(self) -> float:
        return statistics.mean(self.values) if self.values else 0.0

    def to_dict(self) -> dict:
        return {
            "period": self.period,
            "count": self.count,
            "total": round(self.total, 4),
            "mean": round(self.mean, 4),
        }


class AnalyticsEngine:
    """Query engine that wraps a SQLiteStore for analytics.

    Usage::

        from orxaq.fabric.persistence import SQLiteStore
        from orxaq.fabric.analytics import AnalyticsEngine, QueryFilter

        store = SQLiteStore("orxaq.db")
        engine = AnalyticsEngine(store)
        events = engine.query_events(QueryFilter(actors=["system"]))
    """

    def __init__(self, store: object) -> None:
        """Wrap a SQLiteStore for analytics queries."""
        self._store = store
        self._conn = store._conn  # type: ignore[attr-defined]

    # ── Snapshot queries ────────────────────────────────────

    def query_snapshots(self, filt: QueryFilter | None = None) -> list[dict]:
        """Query DAG snapshots with filtering."""
        filt = filt or QueryFilter()
        clauses: list[str] = []
        params: list[Any] = []

        if filt.start_date:
            clauses.append("created_at >= ?")
            params.append(filt.start_date)
        if filt.end_date:
            clauses.append("created_at <= ?")
            params.append(filt.end_date)
        if filt.name_pattern:
            clauses.append("name LIKE ?")
            params.append(f"%{filt.name_pattern}%")

        where = f" WHERE {' AND '.join(clauses)}" if clauses else ""
        sql = (
            f"SELECT id, name, dag_json, metadata, created_at "
            f"FROM dag_snapshots{where} "
            f"ORDER BY id DESC LIMIT ? OFFSET ?"
        )
        params.extend([filt.limit, filt.offset])
        rows = self._conn.execute(sql, params).fetchall()
        results = []
        for r in rows:
            meta = json.loads(r["metadata"]) if r["metadata"] else None
            entry = {
                "id": r["id"],
                "name": r["name"],
                "created_at": r["created_at"],
                "metadata": meta,
            }
            if filt.metadata_tags and meta:
                if not all(meta.get(k) == v for k, v in filt.metadata_tags.items()):
                    continue
            results.append(entry)
        return results

    def snapshot_count(self) -> int:
        """Total number of DAG snapshots."""
        row = self._conn.execute("SELECT COUNT(*) AS cnt FROM dag_snapshots").fetchone()
        return int(row["cnt"])

    def snapshot_names(self) -> list[str]:
        """Distinct snapshot names."""
        rows = self._conn.execute(
            "SELECT DISTINCT name FROM dag_snapshots ORDER BY name"
        ).fetchall()
        return [r["name"] for r in rows]

    # ── Event queries ───────────────────────────────────────

    def query_events(self, filt: QueryFilter | None = None) -> list[dict]:
        """Query audit events with multi-field filtering."""
        filt = filt or QueryFilter()
        clauses: list[str] = []
        params: list[Any] = []

        if filt.start_date:
            clauses.append("created_at >= ?")
            params.append(filt.start_date)
        if filt.end_date:
            clauses.append("created_at <= ?")
            params.append(filt.end_date)
        if filt.event_types:
            placeholders = ",".join("?" for _ in filt.event_types)
            clauses.append(f"event_type IN ({placeholders})")
            params.extend(filt.event_types)
        if filt.actors:
            placeholders = ",".join("?" for _ in filt.actors)
            clauses.append(f"actor IN ({placeholders})")
            params.extend(filt.actors)

        where = f" WHERE {' AND '.join(clauses)}" if clauses else ""
        sql = (
            f"SELECT id, event_type, actor, details, created_at "
            f"FROM audit_log{where} "
            f"ORDER BY id DESC LIMIT ? OFFSET ?"
        )
        params.extend([filt.limit, filt.offset])
        rows = self._conn.execute(sql, params).fetchall()
        return [
            {
                "id": r["id"],
                "event_type": r["event_type"],
                "actor": r["actor"],
                "details": r["details"],
                "created_at": r["created_at"],
            }
            for r in rows
        ]

    def event_count(self, event_type: str | None = None) -> int:
        """Count audit events, optionally by type."""
        if event_type:
            row = self._conn.execute(
                "SELECT COUNT(*) AS cnt FROM audit_log WHERE event_type = ?",
                (event_type,),
            ).fetchone()
        else:
            row = self._conn.execute("SELECT COUNT(*) AS cnt FROM audit_log").fetchone()
        return int(row["cnt"])

    # ── Aggregation ─────────────────────────────────────────

    def aggregate_events(
        self,
        group_by: str = "event_type",
        filt: QueryFilter | None = None,
    ) -> AggregateResult:
        """Aggregate audit events by a column.

        group_by must be 'event_type' or 'actor'.
        """
        if group_by not in ("event_type", "actor"):
            msg = f"group_by must be 'event_type' or 'actor', got '{group_by}'"
            raise ValueError(msg)

        filt = filt or QueryFilter()
        clauses: list[str] = []
        params: list[Any] = []

        if filt.start_date:
            clauses.append("created_at >= ?")
            params.append(filt.start_date)
        if filt.end_date:
            clauses.append("created_at <= ?")
            params.append(filt.end_date)

        where = f" WHERE {' AND '.join(clauses)}" if clauses else ""
        sql = (
            f"SELECT {group_by} AS grp, COUNT(*) AS cnt "
            f"FROM audit_log{where} "
            f"GROUP BY {group_by} ORDER BY cnt DESC"
        )
        rows = self._conn.execute(sql, params).fetchall()
        groups = {r["grp"]: r["cnt"] for r in rows}
        total = sum(groups.values())
        return AggregateResult(count=total, groups=groups)

    def aggregate_snapshots(
        self,
        filt: QueryFilter | None = None,
    ) -> AggregateResult:
        """Aggregate snapshot counts by name."""
        filt = filt or QueryFilter()
        clauses: list[str] = []
        params: list[Any] = []

        if filt.start_date:
            clauses.append("created_at >= ?")
            params.append(filt.start_date)
        if filt.end_date:
            clauses.append("created_at <= ?")
            params.append(filt.end_date)

        where = f" WHERE {' AND '.join(clauses)}" if clauses else ""
        sql = (
            f"SELECT name AS grp, COUNT(*) AS cnt "
            f"FROM dag_snapshots{where} "
            f"GROUP BY name ORDER BY cnt DESC"
        )
        rows = self._conn.execute(sql, params).fetchall()
        groups = {r["grp"]: r["cnt"] for r in rows}
        total = sum(groups.values())
        return AggregateResult(count=total, groups=groups)

    # ── Time-series rollups ─────────────────────────────────

    def event_timeline(
        self,
        granularity: str = "day",
        filt: QueryFilter | None = None,
    ) -> list[TimeSeriesBucket]:
        """Roll up audit events into time buckets.

        granularity: 'day' (YYYY-MM-DD) or 'hour' (YYYY-MM-DDTHH).
        """
        if granularity not in ("day", "hour"):
            msg = f"granularity must be 'day' or 'hour', got '{granularity}'"
            raise ValueError(msg)

        filt = filt or QueryFilter()
        clauses: list[str] = []
        params: list[Any] = []

        if filt.start_date:
            clauses.append("created_at >= ?")
            params.append(filt.start_date)
        if filt.end_date:
            clauses.append("created_at <= ?")
            params.append(filt.end_date)
        if filt.event_types:
            placeholders = ",".join("?" for _ in filt.event_types)
            clauses.append(f"event_type IN ({placeholders})")
            params.extend(filt.event_types)

        where = f" WHERE {' AND '.join(clauses)}" if clauses else ""

        if granularity == "day":
            trunc = "SUBSTR(created_at, 1, 10)"
        else:
            trunc = "SUBSTR(created_at, 1, 13)"

        sql = (
            f"SELECT {trunc} AS period, COUNT(*) AS cnt "
            f"FROM audit_log{where} "
            f"GROUP BY period ORDER BY period"
        )
        rows = self._conn.execute(sql, params).fetchall()
        return [TimeSeriesBucket(period=r["period"], count=r["cnt"]) for r in rows]

    def snapshot_timeline(
        self,
        granularity: str = "day",
    ) -> list[TimeSeriesBucket]:
        """Roll up DAG snapshots into time buckets."""
        if granularity not in ("day", "hour"):
            msg = f"granularity must be 'day' or 'hour', got '{granularity}'"
            raise ValueError(msg)

        if granularity == "day":
            trunc = "SUBSTR(created_at, 1, 10)"
        else:
            trunc = "SUBSTR(created_at, 1, 13)"

        sql = (
            f"SELECT {trunc} AS period, COUNT(*) AS cnt "
            f"FROM dag_snapshots "
            f"GROUP BY period ORDER BY period"
        )
        rows = self._conn.execute(sql).fetchall()
        return [TimeSeriesBucket(period=r["period"], count=r["cnt"]) for r in rows]

    # ── KV analytics ────────────────────────────────────────

    def kv_stats(self) -> dict:
        """Statistics about the key-value store."""
        row = self._conn.execute("SELECT COUNT(*) AS cnt FROM kv_store").fetchone()
        total = row["cnt"]

        row_size = self._conn.execute(
            "SELECT COALESCE(SUM(LENGTH(value)), 0) AS total_bytes FROM kv_store"
        ).fetchone()
        total_bytes = row_size["total_bytes"]

        prefixes: dict[str, int] = {}
        rows = self._conn.execute("SELECT key FROM kv_store ORDER BY key").fetchall()
        for r in rows:
            prefix = r["key"].split(".")[0] if "." in r["key"] else r["key"]
            prefixes[prefix] = prefixes.get(prefix, 0) + 1

        return {
            "total_keys": total,
            "total_bytes": total_bytes,
            "prefix_distribution": prefixes,
        }

    # ── Full text search (details field) ────────────────────

    def search_events(
        self,
        query: str,
        limit: int = 50,
    ) -> list[dict]:
        """Search audit events by substring in details."""
        rows = self._conn.execute(
            "SELECT id, event_type, actor, details, created_at "
            "FROM audit_log "
            "WHERE details LIKE ? "
            "ORDER BY id DESC LIMIT ?",
            (f"%{query}%", limit),
        ).fetchall()
        return [
            {
                "id": r["id"],
                "event_type": r["event_type"],
                "actor": r["actor"],
                "details": r["details"],
                "created_at": r["created_at"],
            }
            for r in rows
        ]

    # ── Summary ─────────────────────────────────────────────

    def summary(self) -> dict:
        """High-level summary across all stored data."""
        return {
            "snapshots": self.snapshot_count(),
            "snapshot_names": self.snapshot_names(),
            "events": self.event_count(),
            "kv": self.kv_stats(),
        }


__all__ = [
    "AggregateResult",
    "AnalyticsEngine",
    "QueryFilter",
    "TimeSeriesBucket",
]
