"""
neo4j_tenant_manager.py
-----------------------
Manages per-tenant Neo4j databases for strict data isolation.

Each tenant gets their own Neo4j database, ensuring complete data separation
and enabling independent backup/restore operations.

Author: HIEROMEM Team
"""

from __future__ import annotations
from typing import Dict
import re
from neo4j import GraphDatabase, Driver
from threading import Lock


class Neo4jTenantManager:
    """Manages tenant-specific Neo4j databases."""

    def __init__(
            self,
            uri: str,
            user: str,
            password: str,
            debug: bool = False):
        """
        Initialize the tenant manager.

        Args:
            uri: Neo4j connection URI (e.g., bolt://localhost:7687)
            user: Neo4j username
            password: Neo4j password
            debug: Enable debug logging
        """
        self.uri = uri
        self.user = user
        self.password = password
        self.debug = debug
        self._driver: Driver = GraphDatabase.driver(uri, auth=(user, password))
        self._lock = Lock()
        self._tenant_dbs: Dict[str, str] = {}  # tenant_id -> database_name
        self._log("Neo4j Tenant Manager initialized")

    def _log(self, message: str) -> None:
        """Log debug messages if debug mode is enabled."""
        if self.debug:
            print(f"[Neo4jTenantManager] {message}")

    def _sanitize_db_name(self, tenant_id: str) -> str:
        """
        Convert tenant ID to a valid Neo4j database name.

        Neo4j database names must:
        - Start with a letter
        - Contain only alphanumeric characters and underscores
        - Be lowercase
        - Be max 63 characters

        Args:
            tenant_id: Tenant identifier

        Returns:
            Valid Neo4j database name
        """
        # Replace invalid characters with underscores
        sanitized = re.sub(r'[^a-zA-Z0-9_]', '_', tenant_id)

        # Ensure it starts with a letter
        if not sanitized[0].isalpha():
            sanitized = f"tenant_{sanitized}"

        # Convert to lowercase and truncate
        db_name = sanitized.lower()[:63]

        return db_name

    def get_database_name(self, tenant_id: str) -> str:
        """
        Get the Neo4j database name for a tenant.

        Args:
            tenant_id: Tenant identifier

        Returns:
            Neo4j database name
        """
        if tenant_id in self._tenant_dbs:
            return self._tenant_dbs[tenant_id]

        db_name = self._sanitize_db_name(tenant_id)
        self._tenant_dbs[tenant_id] = db_name
        return db_name

    def create_database(self, tenant_id: str) -> str:
        """
        Create a new database for a tenant if it doesn't exist.

        Args:
            tenant_id: Tenant identifier

        Returns:
            Database name
        """
        db_name = self.get_database_name(tenant_id)

        with self._lock:
            with self._driver.session(database="system") as session:
                # Check if database exists
                result = session.run(
                    "SHOW DATABASES WHERE name = $name",
                    name=db_name
                )

                if not result.single():
                    # Create database
                    self._log(
                        f"Creating database: {db_name} for tenant: {tenant_id}")
                    session.run(f"CREATE DATABASE {db_name}")
                    self._log(f"Database {db_name} created successfully")
                else:
                    self._log(f"Database {db_name} already exists")

        return db_name

    def delete_database(self, tenant_id: str) -> None:
        """
        Delete a tenant's database.

        WARNING: This permanently deletes all data for the tenant.

        Args:
            tenant_id: Tenant identifier
        """
        db_name = self.get_database_name(tenant_id)

        with self._lock:
            with self._driver.session(database="system") as session:
                self._log(
                    f"Deleting database: {db_name} for tenant: {tenant_id}")
                session.run(f"DROP DATABASE {db_name} IF EXISTS")
                self._log(f"Database {db_name} deleted")

        # Remove from cache
        if tenant_id in self._tenant_dbs:
            del self._tenant_dbs[tenant_id]

    def list_tenant_databases(self) -> Dict[str, str]:
        """
        List all tenant databases.

        Returns:
            Dictionary mapping tenant IDs to database names
        """
        with self._driver.session(database="system") as session:
            result = session.run("SHOW DATABASES")
            databases = [record["name"] for record in result]

        # Filter for tenant databases (exclude system databases)
        system_dbs = {"system", "neo4j"}
        tenant_dbs = {db: db for db in databases if db not in system_dbs}

        return tenant_dbs

    def ensure_database(self, tenant_id: str) -> str:
        """
        Ensure a database exists for the tenant, creating it if necessary.

        Args:
            tenant_id: Tenant identifier

        Returns:
            Database name
        """
        return self.create_database(tenant_id)

    def get_driver(self) -> Driver:
        """Get the Neo4j driver instance."""
        return self._driver

    def close(self) -> None:
        """Close the Neo4j driver connection."""
        self._log("Closing Neo4j driver")
        self._driver.close()

    def __enter__(self):
        """Context manager entry."""
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager exit."""
        self.close()


__all__ = ["Neo4jTenantManager"]
