"""
Repository implementations for space composability.

Provides storage for space references and access control.
"""

import json
import sqlite3
from abc import ABC, abstractmethod
from pathlib import Path
from typing import List, Optional
from datetime import datetime

from .models import (
    SpaceReference,
    SpaceReferenceType,
    SpacePermission,
    AccessLevel,
    SpaceRole,
)


class ISpaceReferenceRepository(ABC):
    """Interface for space reference storage."""

    @abstractmethod
    def add_reference(self, reference: SpaceReference) -> SpaceReference:
        """Add a space reference."""
        pass

    @abstractmethod
    def get_reference(self, reference_id: str) -> Optional[SpaceReference]:
        """Get a reference by ID."""
        pass

    @abstractmethod
    def get_references_from(
        self, source_space_id: str, ref_type: Optional[SpaceReferenceType] = None
    ) -> List[SpaceReference]:
        """Get all references from a source space."""
        pass

    @abstractmethod
    def get_references_to(
        self, target_space_id: str, ref_type: Optional[SpaceReferenceType] = None
    ) -> List[SpaceReference]:
        """Get all references to a target space."""
        pass

    @abstractmethod
    def remove_reference(self, reference_id: str) -> bool:
        """Remove a reference."""
        pass

    @abstractmethod
    def remove_references_from(self, source_space_id: str) -> int:
        """Remove all references from a source space."""
        pass

    @abstractmethod
    def reference_exists(
        self, source_space_id: str, target_space_id: str, ref_type: SpaceReferenceType
    ) -> bool:
        """Check if a specific reference exists."""
        pass


class IAccessControlRepository(ABC):
    """Interface for access control storage."""

    @abstractmethod
    def grant_permission(self, permission: SpacePermission) -> SpacePermission:
        """Grant a permission."""
        pass

    @abstractmethod
    def revoke_permission(
        self, space_id: str, principal_type: str, principal_id: str
    ) -> bool:
        """Revoke a permission."""
        pass

    @abstractmethod
    def get_permissions_for_space(self, space_id: str) -> List[SpacePermission]:
        """Get all permissions for a space."""
        pass

    @abstractmethod
    def get_permissions_for_principal(
        self, principal_type: str, principal_id: str
    ) -> List[SpacePermission]:
        """Get all permissions for a principal."""
        pass

    @abstractmethod
    def get_permission(
        self, space_id: str, principal_type: str, principal_id: str
    ) -> Optional[SpacePermission]:
        """Get a specific permission."""
        pass

    @abstractmethod
    def clear_space_permissions(self, space_id: str) -> int:
        """Clear all permissions for a space."""
        pass


class SqliteSpaceReferenceRepository(ISpaceReferenceRepository):
    """SQLite implementation of space reference repository."""

    def __init__(self, db_path: Path):
        """Initialize the repository.

        Args:
            db_path: Path to the SQLite database file
        """
        self._db_path = db_path
        self._init_tables()

    def _get_connection(self) -> sqlite3.Connection:
        """Get a database connection."""
        conn = sqlite3.connect(str(self._db_path))
        conn.row_factory = sqlite3.Row
        return conn

    def _init_tables(self) -> None:
        """Initialize database tables."""
        with self._get_connection() as conn:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS space_references (
                    id TEXT PRIMARY KEY,
                    source_space_id TEXT NOT NULL,
                    target_space_id TEXT NOT NULL,
                    reference_type TEXT NOT NULL,
                    alias TEXT,
                    metadata TEXT,
                    created_at TEXT NOT NULL,
                    UNIQUE(source_space_id, target_space_id, reference_type)
                )
            """)
            conn.execute("""
                CREATE INDEX IF NOT EXISTS idx_space_ref_source
                ON space_references(source_space_id)
            """)
            conn.execute("""
                CREATE INDEX IF NOT EXISTS idx_space_ref_target
                ON space_references(target_space_id)
            """)
            conn.commit()

    def add_reference(self, reference: SpaceReference) -> SpaceReference:
        """Add a space reference."""
        with self._get_connection() as conn:
            conn.execute(
                """
                INSERT OR REPLACE INTO space_references
                (id, source_space_id, target_space_id, reference_type, alias, metadata, created_at)
                VALUES (?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    reference.id,
                    reference.source_space_id,
                    reference.target_space_id,
                    reference.reference_type.value,
                    reference.alias,
                    json.dumps(reference.metadata),
                    reference.created_at.isoformat(),
                ),
            )
            conn.commit()
        return reference

    def get_reference(self, reference_id: str) -> Optional[SpaceReference]:
        """Get a reference by ID."""
        with self._get_connection() as conn:
            row = conn.execute(
                "SELECT * FROM space_references WHERE id = ?", (reference_id,)
            ).fetchone()
            if row:
                return self._row_to_reference(row)
            return None

    def get_references_from(
        self, source_space_id: str, ref_type: Optional[SpaceReferenceType] = None
    ) -> List[SpaceReference]:
        """Get all references from a source space."""
        with self._get_connection() as conn:
            if ref_type:
                rows = conn.execute(
                    """
                    SELECT * FROM space_references
                    WHERE source_space_id = ? AND reference_type = ?
                    ORDER BY created_at
                    """,
                    (source_space_id, ref_type.value),
                ).fetchall()
            else:
                rows = conn.execute(
                    """
                    SELECT * FROM space_references
                    WHERE source_space_id = ?
                    ORDER BY created_at
                    """,
                    (source_space_id,),
                ).fetchall()
            return [self._row_to_reference(row) for row in rows]

    def get_references_to(
        self, target_space_id: str, ref_type: Optional[SpaceReferenceType] = None
    ) -> List[SpaceReference]:
        """Get all references to a target space."""
        with self._get_connection() as conn:
            if ref_type:
                rows = conn.execute(
                    """
                    SELECT * FROM space_references
                    WHERE target_space_id = ? AND reference_type = ?
                    ORDER BY created_at
                    """,
                    (target_space_id, ref_type.value),
                ).fetchall()
            else:
                rows = conn.execute(
                    """
                    SELECT * FROM space_references
                    WHERE target_space_id = ?
                    ORDER BY created_at
                    """,
                    (target_space_id,),
                ).fetchall()
            return [self._row_to_reference(row) for row in rows]

    def remove_reference(self, reference_id: str) -> bool:
        """Remove a reference."""
        with self._get_connection() as conn:
            cursor = conn.execute(
                "DELETE FROM space_references WHERE id = ?", (reference_id,)
            )
            conn.commit()
            return cursor.rowcount > 0

    def remove_references_from(self, source_space_id: str) -> int:
        """Remove all references from a source space."""
        with self._get_connection() as conn:
            cursor = conn.execute(
                "DELETE FROM space_references WHERE source_space_id = ?",
                (source_space_id,),
            )
            conn.commit()
            return cursor.rowcount

    def reference_exists(
        self, source_space_id: str, target_space_id: str, ref_type: SpaceReferenceType
    ) -> bool:
        """Check if a specific reference exists."""
        with self._get_connection() as conn:
            row = conn.execute(
                """
                SELECT 1 FROM space_references
                WHERE source_space_id = ? AND target_space_id = ? AND reference_type = ?
                """,
                (source_space_id, target_space_id, ref_type.value),
            ).fetchone()
            return row is not None

    def _row_to_reference(self, row: sqlite3.Row) -> SpaceReference:
        """Convert a database row to a SpaceReference."""
        return SpaceReference(
            id=row["id"],
            source_space_id=row["source_space_id"],
            target_space_id=row["target_space_id"],
            reference_type=SpaceReferenceType(row["reference_type"]),
            alias=row["alias"],
            metadata=json.loads(row["metadata"]) if row["metadata"] else {},
            created_at=datetime.fromisoformat(row["created_at"]),
        )


class SqliteAccessControlRepository(IAccessControlRepository):
    """SQLite implementation of access control repository."""

    def __init__(self, db_path: Path):
        """Initialize the repository.

        Args:
            db_path: Path to the SQLite database file
        """
        self._db_path = db_path
        self._init_tables()

    def _get_connection(self) -> sqlite3.Connection:
        """Get a database connection."""
        conn = sqlite3.connect(str(self._db_path))
        conn.row_factory = sqlite3.Row
        return conn

    def _init_tables(self) -> None:
        """Initialize database tables."""
        with self._get_connection() as conn:
            conn.execute("""
                CREATE TABLE IF NOT EXISTS space_permissions (
                    space_id TEXT NOT NULL,
                    principal_type TEXT NOT NULL,
                    principal_id TEXT NOT NULL,
                    access_level TEXT NOT NULL,
                    role TEXT,
                    granted_by TEXT,
                    granted_at TEXT NOT NULL,
                    expires_at TEXT,
                    PRIMARY KEY(space_id, principal_type, principal_id)
                )
            """)
            conn.execute("""
                CREATE INDEX IF NOT EXISTS idx_perm_principal
                ON space_permissions(principal_type, principal_id)
            """)
            conn.commit()

    def grant_permission(self, permission: SpacePermission) -> SpacePermission:
        """Grant a permission."""
        with self._get_connection() as conn:
            conn.execute(
                """
                INSERT OR REPLACE INTO space_permissions
                (space_id, principal_type, principal_id, access_level, role,
                 granted_by, granted_at, expires_at)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?)
                """,
                (
                    permission.space_id,
                    permission.principal_type,
                    permission.principal_id,
                    permission.access_level.value,
                    permission.role.value if permission.role else None,
                    permission.granted_by,
                    permission.granted_at.isoformat(),
                    permission.expires_at.isoformat() if permission.expires_at else None,
                ),
            )
            conn.commit()
        return permission

    def revoke_permission(
        self, space_id: str, principal_type: str, principal_id: str
    ) -> bool:
        """Revoke a permission."""
        with self._get_connection() as conn:
            cursor = conn.execute(
                """
                DELETE FROM space_permissions
                WHERE space_id = ? AND principal_type = ? AND principal_id = ?
                """,
                (space_id, principal_type, principal_id),
            )
            conn.commit()
            return cursor.rowcount > 0

    def get_permissions_for_space(self, space_id: str) -> List[SpacePermission]:
        """Get all permissions for a space."""
        with self._get_connection() as conn:
            rows = conn.execute(
                "SELECT * FROM space_permissions WHERE space_id = ?", (space_id,)
            ).fetchall()
            return [self._row_to_permission(row) for row in rows]

    def get_permissions_for_principal(
        self, principal_type: str, principal_id: str
    ) -> List[SpacePermission]:
        """Get all permissions for a principal."""
        with self._get_connection() as conn:
            rows = conn.execute(
                """
                SELECT * FROM space_permissions
                WHERE principal_type = ? AND principal_id = ?
                """,
                (principal_type, principal_id),
            ).fetchall()
            return [self._row_to_permission(row) for row in rows]

    def get_permission(
        self, space_id: str, principal_type: str, principal_id: str
    ) -> Optional[SpacePermission]:
        """Get a specific permission."""
        with self._get_connection() as conn:
            row = conn.execute(
                """
                SELECT * FROM space_permissions
                WHERE space_id = ? AND principal_type = ? AND principal_id = ?
                """,
                (space_id, principal_type, principal_id),
            ).fetchone()
            if row:
                return self._row_to_permission(row)
            return None

    def clear_space_permissions(self, space_id: str) -> int:
        """Clear all permissions for a space."""
        with self._get_connection() as conn:
            cursor = conn.execute(
                "DELETE FROM space_permissions WHERE space_id = ?", (space_id,)
            )
            conn.commit()
            return cursor.rowcount

    def _row_to_permission(self, row: sqlite3.Row) -> SpacePermission:
        """Convert a database row to a SpacePermission."""
        expires_at = row["expires_at"]
        if expires_at:
            expires_at = datetime.fromisoformat(expires_at)

        role = row["role"]
        if role:
            role = SpaceRole(role)

        return SpacePermission(
            space_id=row["space_id"],
            principal_type=row["principal_type"],
            principal_id=row["principal_id"],
            access_level=AccessLevel(row["access_level"]),
            role=role,
            granted_by=row["granted_by"],
            granted_at=datetime.fromisoformat(row["granted_at"]),
            expires_at=expires_at,
        )
