import hashlib
import sqlite3
from dataclasses import dataclass
from datetime import datetime

import sqlite_vec
from sqlite_vec import serialize_float32

from home_agent.contracts.types import (
    ItemKind,
    UrgencyLabel,
    VectorDocument,
    VectorQuery,
    VectorSearchResult,
)
from home_agent.core.config import SqliteVecConfig
from home_agent.storage.sqlite_store import SqliteStore


@dataclass
class SqliteVecBackend:
    store: SqliteStore
    config: SqliteVecConfig

    @property
    def backend_name(self) -> str:
        return "sqlite_vec"

    def upsert_documents(self, documents: list[VectorDocument]) -> None:
        if not documents:
            return
        conn = self._connect()
        try:
            self._ensure_tables(conn, len(documents[0].vector))
            with conn:
                for document in documents:
                    self._upsert_document(conn, document)
        finally:
            conn.close()

    def query_similar(self, query: VectorQuery) -> list[VectorSearchResult]:
        conn = self._connect()
        try:
            if not self._vec_table_exists(conn):
                return []
            sql = f"""
                SELECT provider, external_id, kind, summary_text, received_at, urgency_label, distance
                FROM {self.config.table_name}
                WHERE embedding MATCH ?
                  AND k = ?
                """
            params: list[object] = [serialize_float32(query.vector), query.top_k]
            if query.include_item_kinds:
                placeholders = ", ".join("?" for _ in query.include_item_kinds)
                sql += f" AND kind IN ({placeholders})"
                params.extend(sorted(query.include_item_kinds))
            sql += " ORDER BY distance"
            rows = conn.execute(sql, tuple(params)).fetchall()
        finally:
            conn.close()

        results: list[VectorSearchResult] = []
        for row in rows:
            provider = str(row["provider"])
            external_id = str(row["external_id"])
            if (provider, external_id) in query.exclude_keys:
                continue
            similarity = max(0.0, 1.0 - float(row["distance"]))
            if similarity < query.min_similarity:
                continue
            label_raw = row["urgency_label"]
            urgency_label = UrgencyLabel(str(label_raw)) if label_raw not in (None, "") else None
            results.append(
                VectorSearchResult(
                    provider=provider,
                    external_id=external_id,
                    kind=ItemKind(str(row["kind"])),
                    summary_text=str(row["summary_text"]),
                    received_at=datetime.fromisoformat(str(row["received_at"])),
                    urgency_label=urgency_label,
                    similarity=similarity,
                )
            )
        results.sort(
            key=lambda result: (
                -result.similarity,
                0
                if query.prioritize_labels and result.urgency_label in query.prioritize_labels
                else 1,
                -result.received_at.timestamp(),
            )
        )
        return results[: query.top_k]

    def healthcheck(self) -> None:
        conn = self._connect()
        conn.close()

    def indexed_count(self) -> int:
        conn = self._connect()
        try:
            if not self._vec_table_exists(conn):
                return 0
            row = conn.execute(f"SELECT COUNT(*) AS count FROM {self.config.table_name}").fetchone()
            if row is None:
                return 0
            return int(row["count"])
        finally:
            conn.close()

    def rebuild(self, documents: list[VectorDocument]) -> None:
        conn = self._connect()
        try:
            with conn:
                conn.execute(f"DROP TABLE IF EXISTS {self.config.table_name}")
                conn.execute(f"DROP TABLE IF EXISTS {self._meta_table_name}")
        finally:
            conn.close()
        self.upsert_documents(documents)

    def has_document(self, key: object) -> bool:
        provider = getattr(key, "provider", None)
        external_id = getattr(key, "external_id", None)
        embedding_provider = getattr(key, "embedding_provider", None)
        embedding_model = getattr(key, "embedding_model", None)
        if not all(
            isinstance(value, str)
            for value in (provider, external_id, embedding_provider, embedding_model)
        ):
            return False
        provider_str = str(provider)
        external_id_str = str(external_id)
        embedding_provider_str = str(embedding_provider)
        embedding_model_str = str(embedding_model)
        document_id = _document_id_from_parts(
            provider_str,
            external_id_str,
            embedding_provider_str,
            embedding_model_str,
        )
        conn = self._connect()
        try:
            row = conn.execute(
                f"SELECT 1 FROM {self._meta_table_name} WHERE document_id = ? LIMIT 1",
                (document_id,),
            ).fetchone()
            return row is not None
        except sqlite3.OperationalError:
            return False
        finally:
            conn.close()

    @property
    def _meta_table_name(self) -> str:
        return f"{self.config.table_name}_meta"

    def _connect(self) -> sqlite3.Connection:
        conn = self.store._connect()
        conn.enable_load_extension(True)
        sqlite_vec.load(conn)
        conn.enable_load_extension(False)
        return conn

    def _ensure_tables(self, conn: sqlite3.Connection, dimensions: int) -> None:
        conn.execute(
            f"""
            CREATE TABLE IF NOT EXISTS {self._meta_table_name} (
                document_id INTEGER PRIMARY KEY,
                provider TEXT NOT NULL,
                external_id TEXT NOT NULL,
                kind TEXT NOT NULL,
                received_at TEXT NOT NULL,
                urgency_label TEXT,
                summary_text TEXT NOT NULL,
                embedding_provider TEXT NOT NULL,
                embedding_model TEXT NOT NULL,
                source_version TEXT NOT NULL
            )
            """
        )
        if self._vec_table_exists(conn):
            return
        conn.execute(
            f"""
            CREATE VIRTUAL TABLE {self.config.table_name} USING vec0(
                document_id integer primary key,
                kind text,
                urgency_label text,
                received_at text,
                embedding_provider text partition key,
                embedding_model text partition key,
                embedding float[{dimensions}] distance_metric={self.config.distance_metric},
                +provider text,
                +external_id text,
                +summary_text text,
                +source_version text
            )
            """
        )

    def _vec_table_exists(self, conn: sqlite3.Connection) -> bool:
        row = conn.execute(
            "SELECT name FROM sqlite_master WHERE name=?",
            (self.config.table_name,),
        ).fetchone()
        return row is not None

    def _upsert_document(self, conn: sqlite3.Connection, document: VectorDocument) -> None:
        document_id = _document_id(document)
        conn.execute(
            f"""
            INSERT OR REPLACE INTO {self._meta_table_name}(
                document_id, provider, external_id, kind, received_at,
                urgency_label, summary_text, embedding_provider, embedding_model, source_version
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                document_id,
                document.key.provider,
                document.key.external_id,
                document.kind.value,
                document.received_at.isoformat(),
                document.urgency_label.value if document.urgency_label is not None else None,
                document.summary_text,
                document.key.embedding_provider,
                document.key.embedding_model,
                document.source_version,
            ),
        )
        conn.execute(
            f"DELETE FROM {self.config.table_name} WHERE document_id = ?",
            (document_id,),
        )
        conn.execute(
            f"""
            INSERT INTO {self.config.table_name}(
                document_id, kind, urgency_label, received_at,
                embedding_provider, embedding_model, embedding,
                provider, external_id, summary_text, source_version
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """,
            (
                document_id,
                document.kind.value,
                document.urgency_label.value if document.urgency_label is not None else "",
                document.received_at.isoformat(),
                document.key.embedding_provider,
                document.key.embedding_model,
                serialize_float32(document.vector),
                document.key.provider,
                document.key.external_id,
                document.summary_text,
                document.source_version,
            ),
        )


def _document_id(document: VectorDocument) -> int:
    return _document_id_from_parts(
        document.key.provider,
        document.key.external_id,
        document.key.embedding_provider,
        document.key.embedding_model,
    )


def _document_id_from_parts(
    provider: str,
    external_id: str,
    embedding_provider: str,
    embedding_model: str,
) -> int:
    raw = f"{provider}:{external_id}:{embedding_provider}:{embedding_model}"
    digest = hashlib.sha256(raw.encode("utf-8")).digest()
    return int.from_bytes(digest[:8], "big") & 0x7FFF_FFFF_FFFF_FFFF
