"""Structured store — hybrid FileStore (persistent) + SQLite (ephemeral) data.

Persistent tables (git-tracked via state/ JSON files):
  - knowledge_entries
  - tasks
  - council_sessions
  - delegations

Ephemeral tables (local SQLite, regenerated per machine):
  - events
  - conversations
"""

from __future__ import annotations

import json
import logging
import uuid
from datetime import UTC, datetime
from pathlib import Path

import aiosqlite

from vastian.storage.file_store import FileStore

logger = logging.getLogger(__name__)


def _json_serial(obj: object) -> str:
    """JSON serializer for objects not serializable by default json code."""
    if isinstance(obj, datetime):
        return obj.isoformat()
    raise TypeError(f"Type {type(obj)} not serializable")


def _dumps(obj: object) -> str:
    """json.dumps with datetime support."""
    return json.dumps(obj, default=_json_serial)


class StructuredStore:
    """Hybrid store: FileStore for persistent data, SQLite for ephemeral data."""

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

        # FileStore instances for persistent (git-tracked) tables
        sd = state_dir or Path("./state")
        self._knowledge = FileStore(sd / "knowledge.json", id_field="id")
        self._tasks = FileStore(sd / "tasks.json", id_field="id")
        self._councils = FileStore(sd / "council-sessions.json", id_field="id")
        self._delegations = FileStore(sd / "delegations.json", id_field="id")

    async def initialize(self) -> None:
        """Create ephemeral SQLite tables (events, conversations)."""
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._db = await aiosqlite.connect(str(self.db_path))
        self._db.row_factory = aiosqlite.Row

        await self._db.executescript("""
            CREATE TABLE IF NOT EXISTS events (
                id TEXT PRIMARY KEY,
                event_type TEXT NOT NULL,
                agent_id TEXT NOT NULL,
                content TEXT NOT NULL,
                metadata TEXT DEFAULT '{}',
                timestamp TEXT NOT NULL
            );

            CREATE TABLE IF NOT EXISTS conversations (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                agent_id TEXT NOT NULL,
                role TEXT NOT NULL,
                content TEXT NOT NULL,
                timestamp TEXT NOT NULL
            );

            CREATE INDEX IF NOT EXISTS idx_events_agent ON events(agent_id);
            CREATE INDEX IF NOT EXISTS idx_events_type ON events(event_type);
            CREATE INDEX IF NOT EXISTS idx_conversations_agent ON conversations(agent_id);
        """)
        await self._db.commit()
        logger.info(
            "StructuredStore initialized (ephemeral: %s, persistent: %s)",
            self.db_path,
            self._knowledge.path.parent,
        )

    async def close(self) -> None:
        if self._db:
            await self._db.close()

    # ── Knowledge Entries (FileStore) ─────────────────

    async def insert_entry(
        self,
        entry_id: str,
        content: str,
        author: str,
        tags: list[str],
        metadata: dict,
    ) -> None:
        now = datetime.now(UTC).isoformat()
        record = {
            "id": entry_id,
            "content": content,
            "author": author,
            "tags": ",".join(tags),
            "metadata": metadata,
            "created_at": now,
            "updated_at": now,
        }
        self._knowledge.upsert(record)

    async def get_by_tags(self, tags: list[str], limit: int = 50) -> list[dict]:
        def _matches(r: dict) -> bool:
            rtags = r.get("tags", "")
            return any(tag in rtags for tag in tags)

        results = self._knowledge.query(_matches)
        # Sort by updated_at descending
        results.sort(key=lambda r: r.get("updated_at", ""), reverse=True)
        return results[:limit]

    # ── Events (SQLite — ephemeral) ───────────────────

    async def log_event(
        self,
        event_type: str,
        agent_id: str,
        content: str,
        metadata: dict,
    ) -> str:
        assert self._db is not None, "StructuredStore not initialized"
        event_id = uuid.uuid4().hex[:12]
        now = datetime.now(UTC).isoformat()
        await self._db.execute(
            """INSERT INTO events (id, event_type, agent_id, content, metadata, timestamp)
               VALUES (?, ?, ?, ?, ?, ?)""",
            (event_id, event_type, agent_id, content, _dumps(metadata), now),
        )
        await self._db.commit()
        return event_id

    async def get_events(
        self,
        agent_id: str | None = None,
        event_type: str | None = None,
        limit: int = 100,
    ) -> list[dict]:
        assert self._db is not None, "StructuredStore not initialized"
        query = "SELECT * FROM events WHERE 1=1"
        params: list[str] = []
        if agent_id:
            query += " AND agent_id = ?"
            params.append(agent_id)
        if event_type:
            query += " AND event_type = ?"
            params.append(event_type)
        query += " ORDER BY timestamp DESC LIMIT ?"
        params.append(str(limit))

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

    # ── Tasks (FileStore) ─────────────────────────────

    async def create_task(
        self,
        title: str,
        assigned_to: str,
        created_by: str,
        description: str = "",
        priority: str = "normal",
        tags: list[str] | None = None,
    ) -> str:
        task_id = uuid.uuid4().hex[:12]
        now = datetime.now(UTC).isoformat()
        record = {
            "id": task_id,
            "title": title,
            "description": description,
            "assigned_to": assigned_to,
            "created_by": created_by,
            "status": "pending",
            "priority": priority,
            "due_date": None,
            "tags": ",".join(tags or []),
            "metadata": {},
            "created_at": now,
            "updated_at": now,
        }
        self._tasks.insert(record)
        return task_id

    async def update_task_status(self, task_id: str, status: str) -> None:
        now = datetime.now(UTC).isoformat()
        self._tasks.update(task_id, {"status": status, "updated_at": now})

    async def get_tasks(
        self,
        assigned_to: str | None = None,
        status: str | None = None,
        limit: int = 50,
    ) -> list[dict]:
        def _matches(r: dict) -> bool:
            if assigned_to and r.get("assigned_to") != assigned_to:
                return False
            return not (status and r.get("status") != status)

        results = self._tasks.query(_matches)
        results.sort(key=lambda r: r.get("updated_at", ""), reverse=True)
        return results[:limit]

    # ── Council Sessions (FileStore) ──────────────────

    async def create_council_session(
        self,
        session_id: str,
        council_type: str,
        topic: str,
        members: list[str],
    ) -> None:
        now = datetime.now(UTC).isoformat()
        record = {
            "id": session_id,
            "council_type": council_type,
            "topic": topic,
            "members": members,
            "status": "active",
            "transcript": [],
            "synthesis": "",
            "action_items": [],
            "created_at": now,
            "completed_at": None,
        }
        self._councils.insert(record)

    async def update_council_session(
        self,
        session_id: str,
        transcript: list[dict] | None = None,
        synthesis: str | None = None,
        action_items: list[dict] | None = None,
        status: str | None = None,
    ) -> None:
        fields: dict = {}
        if transcript is not None:
            fields["transcript"] = transcript
        if synthesis is not None:
            fields["synthesis"] = synthesis
        if action_items is not None:
            fields["action_items"] = action_items
        if status is not None:
            fields["status"] = status
            if status == "completed":
                fields["completed_at"] = datetime.now(UTC).isoformat()
        if fields:
            self._councils.update(session_id, fields)

    async def get_council_session(self, session_id: str) -> dict | None:
        return self._councils.get(session_id)

    async def get_council_sessions(self, limit: int = 10) -> list[dict]:
        """Return the most recent council sessions."""
        all_sessions = self._councils.query(lambda _: True)
        all_sessions.sort(key=lambda r: r.get("created_at", ""), reverse=True)
        return all_sessions[:limit]

    # ── Conversation Persistence (SQLite — ephemeral) ─

    async def save_conversation_message(
        self,
        agent_id: str,
        role: str,
        content: str,
    ) -> None:
        """Save a single conversation message for an agent."""
        assert self._db is not None, "StructuredStore not initialized"
        now = datetime.now(UTC).isoformat()
        await self._db.execute(
            """INSERT INTO conversations (agent_id, role, content, timestamp)
               VALUES (?, ?, ?, ?)""",
            (agent_id, role, content, now),
        )
        await self._db.commit()

    async def save_conversation_batch(
        self,
        agent_id: str,
        messages: list[dict[str, str]],
    ) -> None:
        """Save multiple conversation messages at once (for session save)."""
        assert self._db is not None, "StructuredStore not initialized"
        now = datetime.now(UTC).isoformat()
        rows = [(agent_id, m["role"], m["content"], now) for m in messages]
        await self._db.executemany(
            """INSERT INTO conversations (agent_id, role, content, timestamp)
               VALUES (?, ?, ?, ?)""",
            rows,
        )
        await self._db.commit()

    async def load_conversation(
        self,
        agent_id: str,
        limit: int = 30,
    ) -> list[dict[str, str]]:
        """Load the most recent conversation messages for an agent."""
        assert self._db is not None, "StructuredStore not initialized"
        cursor = await self._db.execute(
            """SELECT role, content, timestamp FROM conversations
               WHERE agent_id = ?
               ORDER BY id DESC LIMIT ?""",
            (agent_id, limit),
        )
        rows = await cursor.fetchall()
        # Reverse because we fetched DESC
        return [
            {"role": row["role"], "content": row["content"], "timestamp": row["timestamp"]}
            for row in reversed(rows)
        ]

    async def list_conversation_agents(self) -> list[dict]:
        """Return agents with recent conversation activity, most recent first."""
        assert self._db is not None, "StructuredStore not initialized"
        cursor = await self._db.execute(
            """SELECT agent_id,
                      MAX(timestamp) as last_ts,
                      COUNT(*) as msg_count
               FROM conversations
               GROUP BY agent_id
               ORDER BY last_ts DESC"""
        )
        rows = await cursor.fetchall()
        result = []
        for row in rows:
            # Fetch the last message for preview
            preview_cur = await self._db.execute(
                """SELECT content FROM conversations
                   WHERE agent_id = ? ORDER BY id DESC LIMIT 1""",
                (row["agent_id"],),
            )
            preview_row = await preview_cur.fetchone()
            preview = (
                (preview_row["content"][:80] + "...")
                if preview_row and len(preview_row["content"]) > 80
                else (preview_row["content"] if preview_row else "")
            )
            result.append(
                {
                    "agent_id": row["agent_id"],
                    "last_timestamp": row["last_ts"],
                    "message_count": row["msg_count"],
                    "preview": preview,
                }
            )
        return result

    async def clear_conversation(self, agent_id: str) -> None:
        """Clear all conversation history for an agent."""
        assert self._db is not None, "StructuredStore not initialized"
        await self._db.execute("DELETE FROM conversations WHERE agent_id = ?", (agent_id,))
        await self._db.commit()

    # ── Delegation Tracking (FileStore) ───────────────

    async def save_delegation(
        self,
        delegation_id: str,
        from_agent: str,
        to_agent: str,
        task_description: str,
        response: str = "",
        status: str = "pending",
    ) -> None:
        """Save a delegation record."""
        now = datetime.now(UTC).isoformat()
        completed = now if status == "completed" else None
        record = {
            "id": delegation_id,
            "from_agent": from_agent,
            "to_agent": to_agent,
            "task_description": task_description,
            "response": response,
            "status": status,
            "created_at": now,
            "completed_at": completed,
        }
        self._delegations.upsert(record)

    async def get_delegations(
        self,
        agent_id: str | None = None,
        status: str | None = None,
        limit: int = 50,
    ) -> list[dict]:
        """Get delegations, optionally filtered by target agent or status."""

        def _matches(r: dict) -> bool:
            if agent_id and r.get("to_agent") != agent_id and r.get("from_agent") != agent_id:
                return False
            return not (status and r.get("status") != status)

        results = self._delegations.query(_matches)
        results.sort(key=lambda r: r.get("created_at", ""), reverse=True)
        return results[:limit]
