"""SQLite schema + queries."""

from __future__ import annotations

import json
import sqlite3
from datetime import UTC, datetime
from pathlib import Path

from uuid_extensions import uuid7

from syke.models import Event, UserProfile

SCHEMA = """
CREATE TABLE IF NOT EXISTS events (
    id TEXT PRIMARY KEY,
    user_id TEXT NOT NULL,
    source TEXT NOT NULL,
    timestamp TEXT NOT NULL,
    event_type TEXT NOT NULL,
    title TEXT,
    content TEXT NOT NULL,
    metadata TEXT DEFAULT '{}',
    ingested_at TEXT NOT NULL DEFAULT (datetime('now')),
    UNIQUE(source, user_id, timestamp, title)
);

CREATE INDEX IF NOT EXISTS idx_events_user_time ON events(user_id, timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_events_user_source ON events(user_id, source);

CREATE TABLE IF NOT EXISTS ingestion_runs (
    id TEXT PRIMARY KEY,
    user_id TEXT NOT NULL,
    source TEXT NOT NULL,
    started_at TEXT NOT NULL DEFAULT (datetime('now')),
    completed_at TEXT,
    status TEXT NOT NULL DEFAULT 'running',
    events_count INTEGER DEFAULT 0,
    error TEXT,
    metadata TEXT DEFAULT '{}'
);

CREATE TABLE IF NOT EXISTS profiles (
    id TEXT PRIMARY KEY,
    user_id TEXT NOT NULL,
    created_at TEXT NOT NULL DEFAULT (datetime('now')),
    profile_json TEXT NOT NULL,
    events_count INTEGER NOT NULL,
    sources TEXT NOT NULL,
    model TEXT NOT NULL DEFAULT 'claude-opus-4-6',
    cost_usd REAL,
    thinking_tokens INTEGER
);

"""

# Migrations applied after initial schema creation
_MIGRATIONS = [
    # Add external_id column for push-based dedup
    ("ALTER TABLE events ADD COLUMN external_id TEXT", "events_external_id_col"),
    # Partial unique index: dedup on (source, user_id, external_id) when external_id is set
    (
        "CREATE UNIQUE INDEX IF NOT EXISTS idx_events_external_id "
        "ON events(source, user_id, external_id) WHERE external_id IS NOT NULL",
        "events_external_id_idx",
    ),
]


class SykeDB:
    """SQLite wrapper for the Syke timeline database."""

    def __init__(self, db_path: str | Path, *, auto_initialize: bool = True):
        self.db_path = str(db_path)
        self._conn = sqlite3.connect(self.db_path)
        self._conn.row_factory = sqlite3.Row
        self._conn.execute("PRAGMA journal_mode=WAL")
        self._conn.execute("PRAGMA foreign_keys=ON")
        if auto_initialize:
            self.initialize()

    # Keep .conn as a read-only property for backward compatibility
    @property
    def conn(self) -> sqlite3.Connection:
        return self._conn

    def __enter__(self) -> SykeDB:
        return self

    def __exit__(self, *exc) -> None:
        self.close()

    def initialize(self) -> None:
        """Create tables and indexes, then apply migrations."""
        self._conn.executescript(SCHEMA)
        self._conn.commit()
        self._migrate()

    def _migrate(self) -> None:
        """Apply schema migrations safely (idempotent)."""
        for sql, label in _MIGRATIONS:
            try:
                self._conn.execute(sql)
                self._conn.commit()
            except sqlite3.OperationalError as e:
                if "already exists" in str(e).lower() or "duplicate column" in str(e).lower():
                    pass  # Expected: column/index already present
                else:
                    raise

    def insert_event(self, event: Event) -> bool:
        """Insert an event, returning True if inserted (not duplicate)."""
        if event.id is None:
            event.id = str(uuid7())
        ingested_at = datetime.now(UTC).isoformat()
        try:
            self._conn.execute(
                """INSERT INTO events (id, user_id, source, timestamp, event_type, title, content, metadata, external_id, ingested_at)
                   VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
                (
                    event.id,
                    event.user_id,
                    event.source,
                    event.timestamp.isoformat(),
                    event.event_type,
                    event.title,
                    event.content,
                    json.dumps(event.metadata),
                    event.external_id,
                    ingested_at,
                ),
            )
            self._conn.commit()
            return True
        except sqlite3.IntegrityError:
            return False

    def insert_events(self, events: list[Event]) -> int:
        """Bulk insert events, returning count of newly inserted."""
        count = 0
        for event in events:
            if self.insert_event(event):
                count += 1
        return count

    def event_exists_by_external_id(self, source: str, user_id: str, external_id: str) -> bool:
        """Check whether an event with this external_id already exists for the source+user."""
        row = self._conn.execute(
            "SELECT 1 FROM events WHERE source = ? AND user_id = ? AND external_id = ? LIMIT 1",
            (source, user_id, external_id),
        ).fetchone()
        return row is not None

    def get_events(
        self,
        user_id: str,
        source: str | None = None,
        since: str | None = None,
        before: str | None = None,
        limit: int = 1000,
    ) -> list[dict]:
        """Query events for a user."""
        query = "SELECT * FROM events WHERE user_id = ?"
        params: list = [user_id]

        if source:
            query += " AND source = ?"
            params.append(source)
        if since:
            query += " AND timestamp >= ?"
            params.append(since)
        if before:
            query += " AND timestamp < ?"
            params.append(before)

        query += " ORDER BY timestamp DESC LIMIT ?"
        params.append(limit)

        rows = self._conn.execute(query, params).fetchall()
        return [dict(row) for row in rows]

    def get_events_since_ingestion(
        self,
        user_id: str,
        since_ingested: str,
        limit: int = 500,
    ) -> list[dict]:
        """Get events ingested after a given timestamp.

        Filters on ingested_at — when Syke actually received the event.
        Critical for pushed events whose timestamp may be days/weeks in the past.
        """
        rows = self._conn.execute(
            "SELECT * FROM events WHERE user_id = ? AND ingested_at > ? "
            "ORDER BY timestamp DESC LIMIT ?",
            (user_id, since_ingested, limit),
        ).fetchall()
        return [dict(row) for row in rows]

    def get_event_by_id(self, user_id: str, event_id: str) -> dict | None:
        """Fetch a single event by ID for a user."""
        row = self._conn.execute(
            "SELECT * FROM events WHERE user_id = ? AND id = ?",
            (user_id, event_id),
        ).fetchone()
        return dict(row) if row else None

    def search_events(self, user_id: str, query: str, limit: int = 20) -> list[dict]:
        """Keyword search across event content and titles.

        Splits query into individual keywords and matches with OR logic.
        """
        keywords = query.strip().split()
        if not keywords:
            return []

        conditions = []
        params: list = [user_id]
        for kw in keywords[:8]:
            conditions.append("(content LIKE ? OR title LIKE ?)")
            params.extend([f"%{kw}%", f"%{kw}%"])

        where = " OR ".join(conditions)
        params.append(limit)
        rows = self._conn.execute(
            f"""SELECT * FROM events
                WHERE user_id = ? AND ({where})
                ORDER BY timestamp DESC LIMIT ?""",
            params,
        ).fetchall()
        return [dict(row) for row in rows]

    def count_events(self, user_id: str, source: str | None = None) -> int:
        """Count events for a user, optionally filtered by source."""
        query = "SELECT COUNT(*) FROM events WHERE user_id = ?"
        params: list = [user_id]
        if source:
            query += " AND source = ?"
            params.append(source)
        return self._conn.execute(query, params).fetchone()[0]

    def count_events_since(self, user_id: str, since: str) -> int:
        """Count events ingested after a given timestamp."""
        return self._conn.execute(
            "SELECT COUNT(*) FROM events WHERE user_id = ? AND ingested_at > ?",
            (user_id, since),
        ).fetchone()[0]

    def get_source_date_range(self, user_id: str, source: str) -> tuple[str | None, str | None]:
        """Return (oldest, newest) event timestamps for a source."""
        row = self._conn.execute(
            "SELECT MIN(timestamp), MAX(timestamp) FROM events WHERE user_id = ? AND source = ?",
            (user_id, source),
        ).fetchone()
        return (row[0], row[1]) if row else (None, None)

    def get_sources(self, user_id: str) -> list[str]:
        """Get distinct sources for a user."""
        rows = self._conn.execute(
            "SELECT DISTINCT source FROM events WHERE user_id = ?", (user_id,)
        ).fetchall()
        return [row[0] for row in rows]

    def start_ingestion_run(self, user_id: str, source: str) -> str:
        """Start an ingestion run, return run ID."""
        run_id = str(uuid7())
        self._conn.execute(
            "INSERT INTO ingestion_runs (id, user_id, source) VALUES (?, ?, ?)",
            (run_id, user_id, source),
        )
        self._conn.commit()
        return run_id

    def complete_ingestion_run(
        self, run_id: str, events_count: int, error: str | None = None
    ) -> None:
        """Mark an ingestion run as completed or failed."""
        status = "failed" if error else "completed"
        self._conn.execute(
            """UPDATE ingestion_runs
               SET completed_at = datetime('now'), status = ?, events_count = ?, error = ?
               WHERE id = ?""",
            (status, events_count, error, run_id),
        )
        self._conn.commit()

    def get_last_sync_timestamp(self, user_id: str, source: str) -> str | None:
        """Return ISO timestamp of most recent successful ingestion for a source."""
        row = self._conn.execute(
            """SELECT completed_at FROM ingestion_runs
               WHERE user_id = ? AND source = ? AND status = 'completed'
               ORDER BY completed_at DESC LIMIT 1""",
            (user_id, source),
        ).fetchone()
        return row[0] if row else None

    def get_last_profile_timestamp(self, user_id: str) -> str | None:
        """Return created_at of most recent profile, or None."""
        row = self._conn.execute(
            "SELECT created_at FROM profiles WHERE user_id = ? ORDER BY created_at DESC LIMIT 1",
            (user_id,),
        ).fetchone()
        return row[0] if row else None

    def save_profile(self, profile: UserProfile) -> str:
        """Save a perception profile."""
        profile_id = str(uuid7())
        created_at = datetime.now(UTC).isoformat()
        self._conn.execute(
            """INSERT INTO profiles (id, user_id, created_at, profile_json, events_count, sources, model, cost_usd, thinking_tokens)
               VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
            (
                profile_id,
                profile.user_id,
                created_at,
                profile.model_dump_json(),
                profile.events_count,
                json.dumps(profile.sources),
                profile.model,
                profile.cost_usd,
                profile.thinking_tokens,
            ),
        )
        self._conn.commit()
        return profile_id

    def get_latest_profile(self, user_id: str) -> UserProfile | None:
        """Get the most recent profile for a user."""
        row = self._conn.execute(
            "SELECT profile_json FROM profiles WHERE user_id = ? ORDER BY created_at DESC LIMIT 1",
            (user_id,),
        ).fetchone()
        if row:
            return UserProfile.model_validate_json(row[0])
        return None

    def get_status(self, user_id: str) -> dict:
        """Get a summary of data for a user."""
        total = self.count_events(user_id)
        sources = self.get_sources(user_id)
        source_counts = {s: self.count_events(user_id, s) for s in sources}

        runs = self._conn.execute(
            """SELECT source, status, events_count, started_at, completed_at
               FROM ingestion_runs WHERE user_id = ?
               ORDER BY started_at DESC LIMIT 10""",
            (user_id,),
        ).fetchall()

        profile_row = self._conn.execute(
            "SELECT created_at, events_count, sources, model FROM profiles WHERE user_id = ? ORDER BY created_at DESC LIMIT 1",
            (user_id,),
        ).fetchone()

        return {
            "user_id": user_id,
            "total_events": total,
            "sources": source_counts,
            "recent_runs": [dict(r) for r in runs],
            "latest_profile": dict(profile_row) if profile_row else None,
        }

    def close(self) -> None:
        self._conn.close()
