"""
Cell model for Lazarus Pit v2.0.

A cell is a bounded execution/living space for one or more participants
on a single project target. Cells are isolated by filesystem, credentials,
and optionally process/container boundaries.
"""

import json
import shutil
import uuid
from dataclasses import dataclass, field, asdict
from datetime import datetime, timedelta
from enum import Enum, auto
from pathlib import Path
from typing import Dict, List, Optional, Set


class CellState(Enum):
    PROPOSED = "proposed"
    ACTIVE = "active"
    IDLE = "idle"
    CLOSING = "closing"
    ARCHIVED = "archived"
    DESTROYED = "destroyed"


class CellRole(Enum):
    EXECUTOR = "executor"    # Can read/write code, run tools, push commits
    OBSERVER = "observer"    # Read-only cell access
    DIRECTOR = "director"    # Can assign, close, publish back to Gitea


@dataclass
class CellMember:
    identity: str           # e.g. "agent:allegro", "bot:kimi", "human:Alexander"
    role: CellRole
    joined_at: str


@dataclass
class Cell:
    cell_id: str
    project: str            # e.g. "Timmy_Foundation/timmy-config#262"
    owner: str              # e.g. "human:Alexander"
    backend: str            # "process", "venv", "docker", "remote"
    state: CellState
    created_at: str
    ttl_minutes: int
    members: List[CellMember] = field(default_factory=list)
    hermes_home: Optional[str] = None
    workspace_path: Optional[str] = None
    shared_notes_path: Optional[str] = None
    shared_artifacts_path: Optional[str] = None
    decisions_path: Optional[str] = None
    archived_at: Optional[str] = None
    destroyed_at: Optional[str] = None

    def is_expired(self) -> bool:
        if self.state in (CellState.CLOSING, CellState.ARCHIVED, CellState.DESTROYED):
            return False
        created = datetime.fromisoformat(self.created_at.replace("Z", "+00:00"))
        return datetime.now().astimezone() > created + timedelta(minutes=self.ttl_minutes)

    def to_dict(self) -> dict:
        d = asdict(self)
        d["state"] = self.state.value
        d["members"] = [
            {**asdict(m), "role": m.role.value} for m in self.members
        ]
        return d

    @classmethod
    def from_dict(cls, d: dict) -> "Cell":
        d = dict(d)
        d["state"] = CellState(d["state"])
        d["members"] = [
            CellMember(
                identity=m["identity"],
                role=CellRole(m["role"]),
                joined_at=m["joined_at"],
            )
            for m in d.get("members", [])
        ]
        return cls(**d)


class CellRegistry:
    """SQLite-backed registry of all cells."""

    def __init__(self, db_path: Optional[str] = None):
        self.db_path = Path(db_path or "/root/.lazarus_registry.sqlite")
        self.db_path.parent.mkdir(parents=True, exist_ok=True)
        self._init_db()

    def _init_db(self):
        import sqlite3
        with sqlite3.connect(str(self.db_path)) as conn:
            conn.execute(
                """
                CREATE TABLE IF NOT EXISTS cells (
                    cell_id TEXT PRIMARY KEY,
                    project TEXT NOT NULL,
                    owner TEXT NOT NULL,
                    backend TEXT NOT NULL,
                    state TEXT NOT NULL,
                    created_at TEXT NOT NULL,
                    ttl_minutes INTEGER NOT NULL,
                    members TEXT,
                    hermes_home TEXT,
                    workspace_path TEXT,
                    shared_notes_path TEXT,
                    shared_artifacts_path TEXT,
                    decisions_path TEXT,
                    archived_at TEXT,
                    destroyed_at TEXT
                )
                """
            )

    def save(self, cell: Cell):
        import sqlite3
        with sqlite3.connect(str(self.db_path)) as conn:
            conn.execute(
                """
                INSERT OR REPLACE INTO cells (
                    cell_id, project, owner, backend, state, created_at, ttl_minutes,
                    members, hermes_home, workspace_path, shared_notes_path,
                    shared_artifacts_path, decisions_path, archived_at, destroyed_at
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    cell.cell_id,
                    cell.project,
                    cell.owner,
                    cell.backend,
                    cell.state.value,
                    cell.created_at,
                    cell.ttl_minutes,
                    json.dumps(cell.to_dict()["members"]),
                    cell.hermes_home,
                    cell.workspace_path,
                    cell.shared_notes_path,
                    cell.shared_artifacts_path,
                    cell.decisions_path,
                    cell.archived_at,
                    cell.destroyed_at,
                ),
            )

    def load(self, cell_id: str) -> Optional[Cell]:
        import sqlite3
        with sqlite3.connect(str(self.db_path)) as conn:
            row = conn.execute(
                "SELECT * FROM cells WHERE cell_id = ?", (cell_id,)
            ).fetchone()
        if not row:
            return None
        keys = [
            "cell_id", "project", "owner", "backend", "state", "created_at",
            "ttl_minutes", "members", "hermes_home", "workspace_path",
            "shared_notes_path", "shared_artifacts_path", "decisions_path",
            "archived_at", "destroyed_at",
        ]
        d = dict(zip(keys, row))
        d["members"] = json.loads(d["members"])
        return Cell.from_dict(d)

    def list_active(self) -> List[Cell]:
        import sqlite3
        with sqlite3.connect(str(self.db_path)) as conn:
            rows = conn.execute(
                "SELECT * FROM cells WHERE state NOT IN ('destroyed', 'archived')"
            ).fetchall()
        keys = [
            "cell_id", "project", "owner", "backend", "state", "created_at",
            "ttl_minutes", "members", "hermes_home", "workspace_path",
            "shared_notes_path", "shared_artifacts_path", "decisions_path",
            "archived_at", "destroyed_at",
        ]
        cells = []
        for row in rows:
            d = dict(zip(keys, row))
            d["members"] = json.loads(d["members"])
            cells.append(Cell.from_dict(d))
        return cells

    def delete(self, cell_id: str):
        import sqlite3
        with sqlite3.connect(str(self.db_path)) as conn:
            conn.execute("DELETE FROM cells WHERE cell_id = ?", (cell_id,))


class CellPackager:
    """Creates and destroys per-cell filesystem isolation."""

    ROOT = Path("/tmp/lazarus-cells")

    def __init__(self, root: Optional[str] = None):
        self.root = Path(root or self.ROOT)

    def create(self, cell_id: str, backend: str = "process") -> Cell:
        cell_home = self.root / cell_id
        workspace = cell_home / "workspace"
        shared_notes = cell_home / "shared" / "notes.md"
        shared_artifacts = cell_home / "shared" / "artifacts"
        decisions = cell_home / "shared" / "decisions.jsonl"
        hermes_home = cell_home / ".hermes"

        for p in [workspace, shared_artifacts, hermes_home]:
            p.mkdir(parents=True, exist_ok=True)

        if not shared_notes.exists():
            shared_notes.write_text(f"# Cell {cell_id} — Shared Notes\n\n")

        if not decisions.exists():
            decisions.write_text("")

        now = datetime.now().astimezone().isoformat()
        return Cell(
            cell_id=cell_id,
            project="",
            owner="",
            backend=backend,
            state=CellState.PROPOSED,
            created_at=now,
            ttl_minutes=60,
            hermes_home=str(hermes_home),
            workspace_path=str(workspace),
            shared_notes_path=str(shared_notes),
            shared_artifacts_path=str(shared_artifacts),
            decisions_path=str(decisions),
        )

    def destroy(self, cell_id: str) -> bool:
        cell_home = self.root / cell_id
        if cell_home.exists():
            shutil.rmtree(cell_home)
            return True
        return False
