import sqlite3
import threading
import json
from abc import ABC, abstractmethod
from pathlib import Path
from typing import List, Dict, Any, Optional
from dataclasses import dataclass

ENABLE_INTERNAL_MESSAGING = True  # Hardcoded toggle
POLL_INTERVAL_SECONDS = 0.5


@dataclass
class InternalMessage:
    """Message envelope - inspired by codex-weave's WeaveMessage"""
    id: str
    agent_id: str          # Destination agent
    payload: Dict[str, Any]
    src: Optional[str] = None  # Source identifier (tool name, etc.)


class MessagingBackend(ABC):
    """Abstract backend - swap SQLite for UDS later"""

    @abstractmethod
    def send(self, agent_id: str, payload: dict, src: Optional[str] = None) -> None:
        """Send a message to an agent"""
        pass

    @abstractmethod
    def receive(self, agent_id: str) -> List[InternalMessage]:
        """Receive pending messages for an agent"""
        pass

    @abstractmethod
    def start(self) -> None:
        """Initialize the backend"""
        pass

    @abstractmethod
    def stop(self) -> None:
        """Cleanup the backend"""
        pass


class SQLiteBackend(MessagingBackend):
    """SQLite-based polling backend (Phase 1)"""

    DB_PATH = Path("/tmp/blocks_internal.db")

    def __init__(self):
        self._lock = threading.Lock()

    def start(self) -> None:
        conn = sqlite3.connect(str(self.DB_PATH))
        conn.execute("""
            CREATE TABLE IF NOT EXISTS internal_messages (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                agent_id TEXT NOT NULL,
                src TEXT,
                message TEXT NOT NULL,
                created_at REAL DEFAULT (julianday('now')),
                processed INTEGER DEFAULT 0
            )
        """)
        conn.execute("CREATE INDEX IF NOT EXISTS idx_agent_processed ON internal_messages(agent_id, processed)")
        conn.commit()
        conn.close()

    def stop(self) -> None:
        pass  # No cleanup needed for SQLite

    def send(self, agent_id: str, payload: dict, src: Optional[str] = None) -> None:
        with self._lock:
            conn = sqlite3.connect(str(self.DB_PATH))
            conn.execute(
                "INSERT INTO internal_messages (agent_id, src, message) VALUES (?, ?, ?)",
                (agent_id, src, json.dumps(payload))
            )
            conn.commit()
            conn.close()

    def receive(self, agent_id: str) -> List[InternalMessage]:
        with self._lock:
            conn = sqlite3.connect(str(self.DB_PATH))
            cursor = conn.execute(
                "SELECT id, src, message FROM internal_messages WHERE agent_id = ? AND processed = 0",
                (agent_id,)
            )
            rows = cursor.fetchall()

            if rows:
                ids = [row[0] for row in rows]
                placeholders = ','.join('?' * len(ids))
                conn.execute(f"UPDATE internal_messages SET processed = 1 WHERE id IN ({placeholders})", ids)
                conn.commit()

            conn.close()

            return [
                InternalMessage(
                    id=str(row[0]),
                    agent_id=agent_id,
                    src=row[1],
                    payload=json.loads(row[2])
                )
                for row in rows
            ]


# Future: UDS Backend (Phase 2)
# class UDSBackend(MessagingBackend):
#     """Unix Domain Socket backend for lower latency"""
#     SOCKET_PATH = Path.home() / ".blocks" / "agent.sock"
#     ...

class InternalMessaging:
    """Singleton facade for internal messaging"""
    _instance = None
    _lock = threading.Lock()

    def __init__(self, backend: MessagingBackend = None):
        self._backend = backend or SQLiteBackend()
        self._backend.start()

    @classmethod
    def get_instance(cls) -> "InternalMessaging":
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = cls()
        return cls._instance

    def send_message(self, agent_id: str, payload: dict, src: Optional[str] = None) -> None:
        self._backend.send(agent_id, payload, src)

    def poll_messages(self, agent_id: str) -> List[InternalMessage]:
        return self._backend.receive(agent_id)

    def stop(self):
        self._backend.stop()
