from __future__ import annotations

import json
import sqlite3
import time
from pathlib import Path

from .profile_suggestions import IGNORED_DISCOVERY_FIELDS


class HistoryStore:
    def __init__(self, path: str) -> None:
        self.path = Path(path)
        self.path.parent.mkdir(parents=True, exist_ok=True)
        with sqlite3.connect(self.path) as connection:
            connection.execute("create table if not exists snapshots (at integer primary key, events integer, anomalies integer, critical integer, high integer, baseline_ready integer, mcp_events integer)")

    def record(self, status: dict[str, object]) -> None:
        summary = status.get("summary", {}) if isinstance(status.get("summary"), dict) else {}
        anomalies = status.get("anomaly_summary", {}) if isinstance(status.get("anomaly_summary"), dict) else {}
        baseline = status.get("baseline", {}) if isinstance(status.get("baseline"), dict) else {}
        capabilities = status.get("capabilities", {}) if isinstance(status.get("capabilities"), dict) else {}
        mcp = capabilities.get("graylog_mcp", {}) if isinstance(capabilities.get("graylog_mcp"), dict) else {}
        with sqlite3.connect(self.path) as connection:
            connection.execute("insert or replace into snapshots values (?, ?, ?, ?, ?, ?, ?)", (int(time.time()), int(summary.get("total", 0) or 0), int(anomalies.get("total", 0) or 0), int(anomalies.get("critical", 0) or 0), int(anomalies.get("high", 0) or 0), int(baseline.get("sources_ready", 0) or 0), int(mcp.get("events_fetched", 0) or 0)))
            connection.execute("delete from snapshots where at < ?", (int(time.time()) - 7 * 86400,))

    def recent(self, limit: int = 288) -> list[dict[str, int]]:
        with sqlite3.connect(self.path) as connection:
            rows = connection.execute("select at, events, anomalies, critical, high, baseline_ready, mcp_events from snapshots order by at desc limit ?", (limit,)).fetchall()
        return [dict(zip(("at", "events", "anomalies", "critical", "high", "baseline_ready", "mcp_events"), row)) for row in reversed(rows)]


class StatusSnapshotStore:
    def __init__(self, path: str) -> None:
        self.path = Path(path)
        self.path.parent.mkdir(parents=True, exist_ok=True)
        with sqlite3.connect(self.path) as connection:
            connection.execute("create table if not exists status_snapshots (name text primary key, at integer not null, payload text not null)")

    def save(self, name: str, status: dict[str, object]) -> None:
        with sqlite3.connect(self.path) as connection:
            connection.execute(
                "insert or replace into status_snapshots values (?, ?, ?)",
                (name, int(time.time()), json.dumps(status, sort_keys=True)),
            )

    def load(self, name: str) -> dict[str, object] | None:
        with sqlite3.connect(self.path) as connection:
            row = connection.execute("select at, payload from status_snapshots where name = ?", (name,)).fetchone()
        if not row:
            return None
        try:
            payload = json.loads(str(row[1]))
        except json.JSONDecodeError:
            return None
        if not isinstance(payload, dict):
            return None
        payload.setdefault("status_cache", {})
        if isinstance(payload["status_cache"], dict):
            payload["status_cache"]["stored_at"] = int(row[0])
        return payload


class FieldDiscoveryStore:
    def __init__(self, path: str) -> None:
        self.path = Path(path)
        self.path.parent.mkdir(parents=True, exist_ok=True)
        with sqlite3.connect(self.path) as connection:
            connection.execute(
                """create table if not exists field_discovery (
                    stream_id text not null, stream_name text not null, field text not null,
                    seen_count integer not null, numeric_count integer not null,
                    sample_values text not null, first_seen integer not null, last_seen integer not null,
                    primary key (stream_id, field)
                )"""
            )
            connection.execute(
                """create table if not exists field_catalog (
                    stream_id text not null, stream_name text not null, field text not null,
                    field_type text not null, properties text not null, first_seen integer not null, last_seen integer not null,
                    primary key (stream_id, field)
                )"""
            )

    def ingest_catalog(self, stream_id: str, stream_name: str, fields: list[dict[str, object]], *, retention_days: int = 30) -> int:
        now = int(time.time())
        rows = []
        for item in fields:
            if not isinstance(item, dict):
                continue
            field = str(item.get("name") or item.get("field") or "").strip()
            if not field or field in IGNORED_DISCOVERY_FIELDS:
                continue
            type_info = item.get("type", {})
            field_type = str(type_info.get("type", "") if isinstance(type_info, dict) else type_info)
            properties = type_info.get("properties", []) if isinstance(type_info, dict) else []
            if not isinstance(properties, list):
                properties = []
            rows.append((stream_id, stream_name, field, field_type, json.dumps([str(prop) for prop in properties]), now, now))
        with sqlite3.connect(self.path) as connection:
            for row in rows:
                connection.execute(
                    """insert into field_catalog values (?, ?, ?, ?, ?, ?, ?)
                       on conflict(stream_id, field) do update set
                       stream_name=excluded.stream_name,
                       field_type=excluded.field_type,
                       properties=excluded.properties,
                       last_seen=excluded.last_seen""",
                    row,
                )
            connection.execute("delete from field_catalog where last_seen < ?", (now - retention_days * 86400,))
        return len(rows)

    def ingest(self, events: list[object], *, max_values: int = 20, retention_days: int = 30) -> int:
        now = int(time.time())
        pending: dict[tuple[str, str, str], dict[str, object]] = {}
        for event in events:
            fields = getattr(event, "fields", {})
            if not isinstance(fields, dict):
                continue
            stream_id = str(fields.get("fgai_stream_id") or fields.get("fgai_stream") or "local")
            stream_name = str(fields.get("fgai_stream") or fields.get("fgai_stream_name") or stream_id)
            for field, value in fields.items():
                if field in IGNORED_DISCOVERY_FIELDS:
                    continue
                text = str(value).strip()
                if not text or text.lower() in {"-", "--", "unknown", "n/a", "none", "null", "nil", "undefined", "[]", "{}"}:
                    continue
                key = (stream_id, stream_name, str(field))
                row = pending.setdefault(key, {"seen_count": 0, "numeric_count": 0, "sample_values": set()})
                row["seen_count"] = int(row["seen_count"]) + 1
                try:
                    float(text)
                    row["numeric_count"] = int(row["numeric_count"]) + 1
                except (TypeError, ValueError):
                    pass
                values = row["sample_values"]
                if isinstance(values, set) and len(values) < max_values:
                    values.add(text)
        with sqlite3.connect(self.path) as connection:
            for (stream_id, stream_name, field), row in pending.items():
                existing = connection.execute("select sample_values from field_discovery where stream_id=? and field=?", (stream_id, field)).fetchone()
                values = set(row["sample_values"] if isinstance(row["sample_values"], set) else set())
                if existing:
                    try:
                        values.update(str(item) for item in json.loads(str(existing[0]))[:max_values])
                    except (json.JSONDecodeError, TypeError):
                        pass
                connection.execute(
                    """insert into field_discovery values (?, ?, ?, ?, ?, ?, ?, ?)
                       on conflict(stream_id, field) do update set
                       stream_name=excluded.stream_name,
                       seen_count=field_discovery.seen_count+excluded.seen_count,
                       numeric_count=field_discovery.numeric_count+excluded.numeric_count,
                       sample_values=excluded.sample_values,
                       last_seen=excluded.last_seen""",
                    (stream_id, stream_name, field, int(row["seen_count"]), int(row["numeric_count"]), json.dumps(sorted(values)[:max_values]), now, now),
                )
            connection.execute("delete from field_discovery where last_seen < ?", (now - retention_days * 86400,))
        return len(pending)

    def synthetic_events(self) -> list[object]:
        from .models import LogEvent

        events: list[LogEvent] = []
        with sqlite3.connect(self.path) as connection:
            rows = connection.execute("select stream_id, stream_name, field, seen_count, numeric_count, sample_values from field_discovery").fetchall()
            catalog_rows = connection.execute("select stream_id, stream_name, field, field_type, properties from field_catalog").fetchall()
        for stream_id, stream_name, field, seen_count, numeric_count, sample_values in rows:
            try:
                values = [str(item) for item in json.loads(str(sample_values))]
            except (json.JSONDecodeError, TypeError):
                values = []
            if not values:
                values = ["1" if int(numeric_count or 0) else "observed"]
            event_count = max(1, min(5, len(values)))
            for index in range(event_count):
                value = values[index % len(values)]
                fields = {"fgai_stream_id": str(stream_id), "fgai_stream": str(stream_name), str(field): value}
                events.append(LogEvent(raw=json.dumps(fields, sort_keys=True), fields=fields))
        existing = {(event.fields.get("fgai_stream_id", ""), field) for event in events for field in event.fields if field not in {"fgai_stream_id", "fgai_stream"}}
        for stream_id, stream_name, field, field_type, properties in catalog_rows:
            if (str(stream_id), str(field)) in existing:
                continue
            try:
                props = set(json.loads(str(properties)))
            except (json.JSONDecodeError, TypeError):
                props = set()
            if "numeric" in props or str(field_type).lower() in {"long", "double", "integer", "int", "float"}:
                value = "1"
            elif "enumerable" in props:
                value = "observed"
            else:
                value = "catalog"
            fields = {"fgai_stream_id": str(stream_id), "fgai_stream": str(stream_name), str(field): value}
            events.append(LogEvent(raw=json.dumps(fields, sort_keys=True), fields=fields))
        return events
