import os
from datetime import timedelta, datetime
from abc import ABC, abstractmethod
from typing import Dict, Optional, Any, Union

from modules.database.services.neo4j_service import Neo4jService
import modules.database.schemas.nodes.users as user_nodes
import modules.database.schemas.nodes.workers.workers as worker_nodes
import modules.database.init.init_calendar as init_calendar
import modules.database.schemas.relationships.entity_relationships as entity_relationships
import modules.database.tools.neontology_tools as neon
import modules.database.tools.supabase_storage_tools as storage_tools
from modules.logger_tool import initialise_logger
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)

def create_and_check_db(db_name):
    neo4j_service = Neo4jService()
    neo4j_service.create_database(db_name)
    database_status = neo4j_service.check_database_exists(db_name)
    if not database_status['exists']:
        raise ValueError(f"Database {db_name} not found")
    return database_status

class UserCreator(ABC):
    def __init__(
        self,
        user_id,
        cc_users_db_name,
        user_type,
        worker_type,
        user_email,
        worker_email,
        cc_username,
        user_name,
        worker_name,
        calendar_start_date,
        calendar_end_date,
        storage_tools=None,
        user_db_name: Optional[str] = None,
        worker_db_name: Optional[str] = None,
        cc_schools_db_name: str = "cc.institutes",
    ):
        self.cc_users_db_name = cc_users_db_name
        self.user_db_name = user_db_name or f"{cc_users_db_name}.{user_type}.{cc_username}"
        self.worker_db_name = worker_db_name or f"{cc_schools_db_name}.{worker_type}.{cc_username}"
        self.user_type = user_type
        self.worker_type = worker_type
        self.cc_username = cc_username
        self.user_email = user_email
        self.worker_email = worker_email
        self.user_name = user_name
        self.worker_name = worker_name
        self.user_id = user_id
        self.storage_tools = storage_tools  # Store the storage tools instance
        self.user_nodes: Dict[str, Optional[Any]] = {
            'default_user_node': None,
            'private_user_node': None,
            'worker_node': None,
            'calendar_node': None
        }
        if calendar_start_date and calendar_end_date:
            self.calendar_start_date = calendar_start_date
            self.calendar_end_date = calendar_end_date
        else:
            logger.warning("No calendar start and end date provided, using default values")
            self.calendar_start_date = datetime.now().date()
            self.calendar_end_date = (datetime.now() + timedelta(days=5)).date()

    def _derive_user_storage_path(self) -> str:
        return os.path.join(
            "users",
            self.user_id,
            "databases",
            self.user_db_name,
            self.user_id,
        ).replace('\\', '/')

    def _derive_internal_worker_path(self, worker_kind: str) -> str:
        return os.path.join(
            "users",
            self.user_id,
            "databases",
            self.user_db_name,
            worker_kind,
            self.user_id,
        ).replace('\\', '/')

    @abstractmethod
    def create_user(self):
        pass

    def create_user_node(self, db_name: str):
        logger.info(f"Module is creating {self.cc_users_db_name} user node for {self.user_type} user {self.cc_username}")
        try:
            user_node = self._create_user_node(db_name)
            logger.debug(f"User node creation completed for {self.cc_users_db_name} user node for {self.user_type} user {self.cc_username}: {user_node.to_dict()}")
            return user_node
        except Exception as e:
            logger.error(f"Error creating user node: {e}")
            raise

    def _create_user_node(self, db_name: str):
        # Ensure Neontology is initialized
        neon.init_neontology_connection()

        # Generate storage path for user node using Supabase Storage
        if self.storage_tools:
            user_dir_created, node_storage_path = self.storage_tools.create_user_storage_path(self.user_id)
            self.user_path = node_storage_path  # Store for later use
        else:
            node_storage_path = self._derive_user_storage_path()
            self.user_path = None
        
        user_node = user_nodes.UserNode(
            uuid_string=f"{self.user_id}",
            node_storage_path=node_storage_path,
            cc_username=f"{self.cc_username}",
            user_email=f"{self.user_email}",
            user_name=f"{self.user_name}",
            user_db_name=f"{self.user_db_name}",
            user_type=f"{self.user_type}",
        )
        logger.debug(f"User node template created: {user_node.to_dict()}. Writing to database {db_name}")
        logger.debug(f"About to call create_or_merge_neontology_node with node class: {user_node.__class__.__name__}")
        logger.debug(f"Node primary label: {user_node.__primarylabel__}")
        logger.debug(f"Node primary property: {user_node.__primaryproperty__}")
        
        try:
            neon.create_or_merge_neontology_node(node=user_node, database=db_name, operation='merge')
            logger.info(f"User node created successfully: {user_node.to_dict()}")
        except Exception as e:
            logger.error(f"Failed to create user node: {e}")
            raise
        return user_node

    def create_storage_bucket(self, bucket_id: str, bucket_name: str, access_token: Optional[str] = None) -> bool:
        """Create storage buckets for the user - DEPRECATED: Use centralized bucket initialization instead"""
        logger.warning(f"Individual user bucket creation is deprecated. Use centralized bucket initialization instead.")
        logger.info(f"User {self.cc_username} will use centralized storage buckets.")
        return True  # Return success to avoid breaking existing code

class SchoolUserCreator(UserCreator):
    def __init__(
        self,
        user_id,
        cc_users_db_name,
        user_type,
        worker_type,
        user_email,
        worker_email,
        cc_username,
        user_name,
        worker_name,
        calendar_start_date,
        calendar_end_date,
        school_node,
        worker_node=None,
        storage_tools=None,
        user_db_name: Optional[str] = None,
        worker_db_name: Optional[str] = None,
    ):
        super().__init__(
            user_id,
            cc_users_db_name,
            user_type,
            worker_type,
            user_email,
            worker_email,
            cc_username,
            user_name,
            worker_name,
            calendar_start_date,
            calendar_end_date,
            storage_tools,
            user_db_name=user_db_name,
            worker_db_name=worker_db_name or (school_node.private_database_name if hasattr(school_node, "private_database_name") else None),
            cc_schools_db_name="cc.institutes",
        )
        self.school_node = school_node
        self.worker_node = worker_node

    def _derive_school_worker_path(self, worker_kind: str) -> str:
        school_identifier = getattr(self.school_node, 'uuid_string', None) if self.school_node else None
        if not school_identifier and self.worker_db_name:
            school_identifier = self.worker_db_name.split('.')[-1]
        school_identifier = school_identifier or self.user_id
        db_name_segment = self.worker_db_name or f"cc.institutes.{school_identifier}"
        return os.path.join(
            "schools",
            school_identifier,
            "databases",
            db_name_segment,
            worker_kind,
            self.user_id,
        ).replace('\\', '/')

    def create_user(self):
        # Ensure Neontology is initialized
        logger.debug(f"Initializing Neontology connection. Closing any existing connection")
        neon.close_neontology_connection()
        logger.debug(f"Neontology connection closed. Initializing new connection")
        neon.init_neontology_connection()
                
        if self.user_type in ['email_teacher', 'ms_teacher']:
            worker_node = self.create_teacher_node()
        elif self.user_type in ['email_student', 'ms_student']:
            worker_node = self.create_student_node()
        else:
            raise ValueError(f"User type {self.user_type} not supported")
        
        self.user_nodes[f'worker_node'] = worker_node
        
        user_node = self.create_user_node(self.user_db_name)
        
        logger.info(f"User node created: {user_node}")
        
        self.user_nodes['default_user_node'] = user_node

        self.create_user_worker_relationship(user_node, worker_node)
        
        self.create_worker_school_relationship(worker_node, self.school_node)
        
        logger.info(f"Worker school relationship created between {worker_node} and {self.school_node}")
        return self.user_nodes

    def create_teacher_node(self):
        logger.debug(f"Teacher node will be created for school: {self.school_node}")
        try:
            return self._create_teacher_node()
        except KeyError as ke:
            raise ValueError(f"Missing required key in worker_data: {ke}") from ke
        except Exception as e:
            raise ValueError(f"Error creating teacher node: {e}") from e

    def _create_teacher_node(self):
        # Generate storage path for teacher node using Supabase Storage
        if self.storage_tools:
            teacher_dir_created, node_storage_path = self.storage_tools.create_teacher_storage_path(self.user_id)
        else:
            node_storage_path = self._derive_school_worker_path(self.worker_type)
        
        teacher_node = worker_nodes.TeacherNode(
            uuid_string=f"{self.user_id}",
            node_storage_path=node_storage_path,
            worker_name=self.worker_name,
            worker_email=self.worker_email,
            worker_db_name=self.worker_db_name,
            worker_type=self.worker_type
        )
        # Use the school's private database name if available
        school_db = self.worker_db_name or (
            self.school_node.private_database_name if hasattr(self.school_node, 'private_database_name')
            else f"cc.institutes.{self.school_node.school_type}.{getattr(self.school_node, 'id', self.school_node.uuid_string)}"
        )
        logger.info(f"Teacher node template created: {teacher_node}... setting school db to {school_db}")

        neon.create_or_merge_neontology_node(node=teacher_node, database=school_db, operation='merge')

        logger.info(f"Teacher node merged into database {school_db}: {teacher_node}")
        return teacher_node

    def create_student_node(self):
        # Generate storage path for student node using Supabase Storage
        if self.storage_tools:
            student_dir_created, node_storage_path = self.storage_tools.create_student_storage_path(f"Student_{self.user_id}")
        else:
            node_storage_path = self._derive_school_worker_path(self.worker_type)
        
        student_node = worker_nodes.StudentNode(
            uuid_string=f"Student_{self.user_id}",
            worker_name=self.worker_name,
            worker_email=self.worker_email,
            worker_db_name=self.worker_db_name,
            worker_type=self.worker_type,
            node_storage_path=node_storage_path
        )
        # Use the school's private database name if available
        school_db = self.worker_db_name or (
            self.school_node.private_database_name if hasattr(self.school_node, 'private_database_name')
            else f"cc.institutes.{self.school_node.school_type}.{getattr(self.school_node, 'id', self.school_node.uuid_string)}"
        )
        logger.info(f"Student node template created: {student_node}... setting school db to {school_db}")

        neon.create_or_merge_neontology_node(node=student_node, database=school_db, operation='merge')

        logger.info(f"Student node merged into database {school_db}: {student_node}")
        return student_node

    def create_user_worker_relationship(self, user_node, worker_node):
        user_role_rel = entity_relationships.UserIsSchoolWorker(source=user_node, target=worker_node)
        # Use the school's private database name if available
        school_db = self.worker_db_name or (
            self.school_node.private_database_name if hasattr(self.school_node, 'private_database_name')
            else f"cc.institutes.{self.school_node.school_type}.{getattr(self.school_node, 'id', self.school_node.uuid_string)}"
        )
        neon.create_or_merge_neontology_relationship(user_role_rel, database=school_db, operation='merge')
        logger.info(f"Relationship created between user and worker in database {school_db}")

    def create_worker_school_relationship(self, worker_node, school_node):
        worker_school_rel = entity_relationships.EntityBelongsToSchool(source=worker_node, target=school_node)
        # Use the school's private database name if available
        school_db = self.worker_db_name or (
            school_node.private_database_name if hasattr(school_node, 'private_database_name')
            else f"cc.institutes.{school_node.school_type}.{getattr(school_node, 'id', school_node.uuid_string)}"
        )
        neon.create_or_merge_neontology_relationship(worker_school_rel, database=school_db, operation='merge')
        logger.info(f"Relationship created between worker and school in database {school_db}")

class NonSchoolUserCreator(UserCreator):
    def __init__(
        self,
        user_id,
        cc_users_db_name,
        user_type,
        worker_type,
        user_email,
        worker_email,
        cc_username,
        user_name,
        worker_name,
        calendar_start_date,
        calendar_end_date,
        developer_role: str = "developer",
        storage_tools=None,
        user_db_name: Optional[str] = None,
        worker_db_name: Optional[str] = None,
    ):
        super().__init__(
            user_id,
            cc_users_db_name,
            user_type,
            worker_type,
            user_email,
            worker_email,
            cc_username,
            user_name,
            worker_name,
            calendar_start_date,
            calendar_end_date,
            storage_tools,
            user_db_name=user_db_name,
            worker_db_name=worker_db_name,
        )
        self.developer_role = developer_role

    def create_user(self, access_token: Optional[str] = None):
        logger.debug(f"Creating user node for {self.user_type} user {self.cc_username} in database {self.cc_users_db_name}")
        
        # Create storage buckets for the user
        user_bucket_id = self.user_db_name
        user_bucket_name = f"{self.user_type.title()} User Files - {self.user_name}"
        if not self.create_storage_bucket(user_bucket_id, user_bucket_name, access_token=access_token):
            logger.error(f"Failed to create storage bucket for user {self.cc_username}")
            raise ValueError(f"Failed to create storage bucket for user {self.cc_username}")
        
        # Create default user node first
        default_user_node = self.create_user_node(self.cc_users_db_name)
        logger.debug(f"Default user node created: {default_user_node}")
        
        # Verify the return value of create_user_node
        if default_user_node is None:
            logger.error("Failed to create default user node. It is None.")
            raise ValueError("Failed to create default user node. It is None.")

        self.user_nodes[f'default_user_node'] = default_user_node
        
        # Create the appropriate user db based on user_type
        if self.user_type == 'admin':
            logger.debug(f"Creating super admin db for {self.user_type} user {self.cc_username} in database {self.user_db_name}")
            self.create_super_admin_db()
        elif self.user_type == 'developer':
            logger.debug(f"Creating developer db for {self.user_type} user {self.cc_username} in database {self.user_db_name}")
            self.create_developer_db()
        else:
            logger.warning(f"User type {self.user_type} not explicitly supported; defaulting to developer workspace")
            self.create_developer_db()

        logger.debug(f"User nodes after creation: {self.user_nodes}")
        return self.user_nodes

    def create_super_admin_db(self):
        logger.debug(f"Creating super admin db for {self.user_type} user {self.cc_username} in database {self.user_db_name}")
        neon.init_neontology_connection()

        # Create the user db self.user_db_name
        create_and_check_db(self.user_db_name)

        try:
            # Create the user node again for the user db
            logger.debug(f"Creating super admin user node for {self.user_type} user {self.cc_username} in database {self.user_db_name}")
            private_user_node = self.create_user_node(self.user_db_name)

            # Generate storage path for super admin node using Supabase Storage
            if self.storage_tools:
                admin_dir_created, node_storage_path = self.storage_tools.create_super_admin_storage_path(self.user_id)
            else:
                node_storage_path = self._derive_internal_worker_path(self.worker_type)
            
            super_admin_node = worker_nodes.SuperAdminNode(
                uuid_string=self.user_id,
                worker_email=self.worker_email,
                node_storage_path=node_storage_path,
                worker_name=self.worker_name,
                worker_db_name=self.worker_db_name,
                worker_type=self.worker_type
            )
            logger.debug(f"Super admin node template created: {super_admin_node}. Writing to database {self.user_db_name}")
            neon.create_or_merge_neontology_node(node=super_admin_node, database=self.user_db_name, operation='merge')
            logger.info(f"Super admin node created: {super_admin_node}")

            logger.debug(f"Creating relationship between user node: {private_user_node} and worker node: {super_admin_node}")
            self.create_user_specific_relationship(user_node=private_user_node, worker_node=super_admin_node)

            logger.debug(f"Creating calendar for {self.user_type} user {self.cc_username} in database {self.user_db_name}")
            calendar_nodes = self.create_calendar(user_node=private_user_node)
            logger.info(f"Super admin calendar created.")

            self.user_nodes['private_user_node'] = private_user_node
            self.user_nodes['worker_node'] = super_admin_node

            logger.info(f"Returning user nodes: {self.user_nodes}")

            return self.user_nodes
        except Exception as e:
            logger.error(f"Error creating super admin node: {e}")
            raise ValueError(f"Error creating super admin node: {e}") from e

    def create_developer_db(self):
        neon.init_neontology_connection()

        # Create the user db self.user_db_name
        create_and_check_db(self.user_db_name)

        try:
            # Create the user node again for the user db
            private_user_node = self.create_user_node(self.user_db_name)

            # Generate storage path for developer node using Supabase Storage
            if self.storage_tools:
                dev_dir_created, node_storage_path = self.storage_tools.create_developer_storage_path(self.user_id)
            else:
                node_storage_path = self._derive_internal_worker_path(self.worker_type)
            
            developer_node = worker_nodes.DeveloperNode(
                uuid_string=self.user_id,
                worker_name=self.worker_name,
                worker_email=self.worker_email,
                node_storage_path=node_storage_path,
                worker_db_name=self.worker_db_name,
                worker_type=self.worker_type,
                developer_role=self.developer_role
            )
            
            neon.create_or_merge_neontology_node(developer_node, database=self.user_db_name, operation='merge')
            logger.info(f"Developer node created: {developer_node}")

            self.user_nodes['private_user_node'] = private_user_node
            self.user_nodes['worker_node'] = developer_node

            self.create_user_specific_relationship(user_node=private_user_node, worker_node=developer_node)

            return self.user_nodes
        except Exception as e:
            raise ValueError(f"Error creating developer node: {e}") from e

    def create_user_specific_relationship(self, user_node: user_nodes.UserNode, worker_node: Union[worker_nodes.SuperAdminNode, worker_nodes.DeveloperNode]):
        if user_node is None or worker_node is None:
            logger.error("User node or worker node is None. Cannot create relationship.")
            raise ValueError("User node or worker node is None. Cannot create relationship.")

        logger.info(f"Creating relationship between user node: {user_node} and worker node: {worker_node}")

        # Log the state of user_node and worker_node
        logger.debug(f"user_node: {user_node}")
        logger.debug(f"worker_node: {worker_node}")

        if worker_node.worker_type == 'developer':
            specific_user_rel = entity_relationships.UserIsSystemWorker(source=user_node, target=worker_node)
        elif worker_node.worker_type == 'superadmin':
            specific_user_rel = entity_relationships.UserIsSystemWorker(source=user_node, target=worker_node)
        else:
            raise ValueError(f"User type {worker_node.worker_type} not supported")
        
        neon.create_or_merge_neontology_relationship(specific_user_rel, database=self.user_db_name, operation='merge')
        logger.info("Relationship created between user and specific node")

def create_calendar(self, user_node: user_nodes.UserNode):
        calendar_nodes = init_calendar.create_calendar(self.user_db_name, self.calendar_start_date, self.calendar_end_date, attach_to_calendar_node=True, owner_node=user_node, filesystem=self.filesystem)
        
        logger.info(f"Calendar nodes created.")
        return calendar_nodes


def _default_date_range():
    today = datetime.now().date()
    return today, (datetime.now() + timedelta(days=365)).date()


def create_user(
    *,
    user_id: str,
    user_type: str,
    username: str,
    user_email: str,
    user_name: Optional[str] = None,
    worker_name: Optional[str] = None,
    worker_type: Optional[str] = None,
    worker_email: Optional[str] = None,
    cc_users_db_name: str = "cc.users",
    user_db_name: Optional[str] = None,
    worker_db_name: Optional[str] = None,
    calendar_start_date: Optional[datetime.date] = None,
    calendar_end_date: Optional[datetime.date] = None,
    school_node: Optional[Any] = None,
    storage_tools=None,
) -> Dict[str, Optional[Any]]:
    """Create a user graph structure in Neo4j.

    Args:
        user_id: Identifier used as UUID for graph nodes.
        user_type: Application-level user type (e.g. email_teacher, developer).
        username: Canonical username/slug.
        user_email: Contact email for the user node.
        user_name: Friendly display name (defaults to username).
        worker_name: Friendly name for worker node (defaults to user_name).
        worker_type: Worker role (teacher, student, developer, etc.).
        worker_email: Email for worker node (defaults to user_email).
        cc_users_db_name: Root namespace for user databases (defaults to cc.users).
        user_db_name: Fully-qualified target database for the user graph.
        worker_db_name: Database for worker entities (usually school private DB).
        calendar_start_date/calendar_end_date: Date range for initial calendar seeding.
        school_node: Optional school context; if provided a SchoolUserCreator is used.
        storage_tools: Optional Supabase storage tools for generating storage paths.

    Returns:
        Dict describing created nodes keyed by semantic role.
    """

    start_date, end_date = calendar_start_date, calendar_end_date
    if not start_date or not end_date:
        start_date, end_date = _default_date_range()

    worker_email = worker_email or user_email
    user_name = user_name or username
    worker_name = worker_name or user_name

    if school_node is not None:
        creator = SchoolUserCreator(
            user_id=user_id,
            cc_users_db_name=cc_users_db_name,
            user_type=user_type,
            worker_type=worker_type or "teacher",
            user_email=user_email,
            worker_email=worker_email,
            cc_username=username,
            user_name=user_name,
            worker_name=worker_name,
            calendar_start_date=start_date,
            calendar_end_date=end_date,
            school_node=school_node,
            worker_node=None,
            storage_tools=storage_tools,
            user_db_name=user_db_name,
            worker_db_name=worker_db_name,
        )
    else:
        creator = NonSchoolUserCreator(
            user_id=user_id,
            cc_users_db_name=cc_users_db_name,
            user_type=user_type,
            worker_type=worker_type or user_type,
            user_email=user_email,
            worker_email=worker_email,
            cc_username=username,
            user_name=user_name,
            worker_name=worker_name,
            calendar_start_date=start_date,
            calendar_end_date=end_date,
            storage_tools=storage_tools,
            user_db_name=user_db_name,
            worker_db_name=worker_db_name,
        )

    return creator.create_user()
