"""Topology store -- SQLite-backed persistence for topology state.

Stores per-backend latency history and health snapshots for the topology
mapper to query and for the dashboard to display historical trends.
"""

from __future__ import annotations

import logging
from datetime import datetime, timezone
from typing import TYPE_CHECKING

import aiosqlite

if TYPE_CHECKING:
    from pathlib import Path

logger = logging.getLogger(__name__)

_CREATE_TABLE = """
CREATE TABLE IF NOT EXISTS topology_nodes (
    backend_id      TEXT PRIMARY KEY,
    backend_type    TEXT NOT NULL,
    name            TEXT NOT NULL,
    url             TEXT NOT NULL,
    is_local        INTEGER NOT NULL DEFAULT 0,
    status          TEXT NOT NULL DEFAULT 'offline',
    avg_latency_ms  REAL NOT NULL DEFAULT 0.0,
    p50_ms          REAL NOT NULL DEFAULT 0.0,
    p95_ms          REAL NOT NULL DEFAULT 0.0,
    p99_ms          REAL NOT NULL DEFAULT 0.0,
    models          TEXT NOT NULL DEFAULT '[]',
    last_seen       TEXT,
    last_check      TEXT,
    consecutive_failures INTEGER NOT NULL DEFAULT 0,
    uptime_percent  REAL NOT NULL DEFAULT 100.0,
    trend           TEXT NOT NULL DEFAULT 'stable',
    updated_at      TEXT NOT NULL
);
"""

_CREATE_LATENCY_TABLE = """
CREATE TABLE IF NOT EXISTS topology_latency_samples (
    id          INTEGER PRIMARY KEY AUTOINCREMENT,
    backend_id  TEXT NOT NULL,
    latency_ms  REAL NOT NULL,
    healthy     INTEGER NOT NULL DEFAULT 1,
    recorded_at TEXT NOT NULL,
    FOREIGN KEY (backend_id) REFERENCES topology_nodes(backend_id)
);
"""

_CREATE_INDEXES = [
    "CREATE INDEX IF NOT EXISTS idx_topo_latency_backend ON topology_latency_samples(backend_id);",
    "CREATE INDEX IF NOT EXISTS idx_topo_latency_time ON topology_latency_samples(recorded_at);",
]

_UPSERT_NODE = """
INSERT INTO topology_nodes (
    backend_id, backend_type, name, url, is_local, status,
    avg_latency_ms, p50_ms, p95_ms, p99_ms, models,
    last_seen, last_check, consecutive_failures, uptime_percent, trend, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(backend_id) DO UPDATE SET
    backend_type=excluded.backend_type,
    name=excluded.name,
    url=excluded.url,
    is_local=excluded.is_local,
    status=excluded.status,
    avg_latency_ms=excluded.avg_latency_ms,
    p50_ms=excluded.p50_ms,
    p95_ms=excluded.p95_ms,
    p99_ms=excluded.p99_ms,
    models=excluded.models,
    last_seen=excluded.last_seen,
    last_check=excluded.last_check,
    consecutive_failures=excluded.consecutive_failures,
    uptime_percent=excluded.uptime_percent,
    trend=excluded.trend,
    updated_at=excluded.updated_at;
"""

_INSERT_SAMPLE = """
INSERT INTO topology_latency_samples (backend_id, latency_ms, healthy, recorded_at)
VALUES (?, ?, ?, ?);
"""

_PRUNE_SAMPLES = """
DELETE FROM topology_latency_samples
WHERE recorded_at < ?;
"""


class TopologyStore:
    """Async SQLite store for topology state and latency history."""

    def __init__(self, db_path: Path) -> None:
        self._db_path = db_path
        self._db: aiosqlite.Connection | None = None

    async def initialize(self) -> None:
        """Create tables and indexes."""
        self._db_path.parent.mkdir(parents=True, exist_ok=True)
        self._db = await aiosqlite.connect(str(self._db_path))
        await self._db.execute("PRAGMA journal_mode=WAL;")
        await self._db.execute("PRAGMA synchronous=NORMAL;")
        await self._db.execute(_CREATE_TABLE)
        await self._db.execute(_CREATE_LATENCY_TABLE)
        for idx_sql in _CREATE_INDEXES:
            await self._db.execute(idx_sql)
        await self._db.commit()
        logger.info("TopologyStore initialised (db=%s)", self._db_path)

    async def close(self) -> None:
        """Close the database connection."""
        if self._db is not None:
            await self._db.close()
            self._db = None

    def _ensure_db(self) -> aiosqlite.Connection:
        if self._db is None:
            raise RuntimeError("TopologyStore not initialised -- call initialize() first")
        return self._db

    async def upsert_node(
        self,
        backend_id: str,
        backend_type: str,
        name: str,
        url: str,
        is_local: bool,
        status: str,
        avg_latency_ms: float,
        p50_ms: float,
        p95_ms: float,
        p99_ms: float,
        models_json: str,
        last_seen: str | None,
        last_check: str | None,
        consecutive_failures: int,
        uptime_percent: float,
        trend: str,
    ) -> None:
        """Insert or update a topology node."""
        db = self._ensure_db()
        now = datetime.now(tz=timezone.utc).isoformat()
        await db.execute(
            _UPSERT_NODE,
            (
                backend_id,
                backend_type,
                name,
                url,
                1 if is_local else 0,
                status,
                avg_latency_ms,
                p50_ms,
                p95_ms,
                p99_ms,
                models_json,
                last_seen,
                last_check,
                consecutive_failures,
                uptime_percent,
                trend,
                now,
            ),
        )
        await db.commit()

    async def record_sample(
        self,
        backend_id: str,
        latency_ms: float,
        healthy: bool,
    ) -> None:
        """Record a latency sample for a backend."""
        db = self._ensure_db()
        now = datetime.now(tz=timezone.utc).isoformat()
        await db.execute(_INSERT_SAMPLE, (backend_id, latency_ms, 1 if healthy else 0, now))
        await db.commit()

    async def get_all_nodes(self) -> list[dict]:
        """Return all stored topology nodes as dicts."""
        db = self._ensure_db()
        sql = (
            "SELECT backend_id, backend_type, name, url, is_local, status, "
            "avg_latency_ms, p50_ms, p95_ms, p99_ms, models, "
            "last_seen, last_check, consecutive_failures, uptime_percent, trend "
            "FROM topology_nodes ORDER BY is_local DESC, avg_latency_ms ASC"
        )
        rows: list[dict] = []
        async with db.execute(sql) as cursor:
            async for row in cursor:
                rows.append(
                    {
                        "backend_id": row[0],
                        "backend_type": row[1],
                        "name": row[2],
                        "url": row[3],
                        "is_local": bool(row[4]),
                        "status": row[5],
                        "avg_latency_ms": row[6],
                        "p50_ms": row[7],
                        "p95_ms": row[8],
                        "p99_ms": row[9],
                        "models": row[10],
                        "last_seen": row[11],
                        "last_check": row[12],
                        "consecutive_failures": row[13],
                        "uptime_percent": row[14],
                        "trend": row[15],
                    }
                )
        return rows

    async def prune_old_samples(self, before_iso: str) -> int:
        """Delete latency samples older than the given ISO timestamp. Returns count deleted."""
        db = self._ensure_db()
        cursor = await db.execute(_PRUNE_SAMPLES, (before_iso,))
        count = cursor.rowcount
        await db.commit()
        return count
