import os
from modules.logger_tool import initialise_logger
logger = initialise_logger(__name__, os.getenv("LOG_LEVEL"), os.getenv("LOG_PATH"), 'default', True)
from datetime import timedelta, datetime
import pandas as pd
from modules.database.schemas.structures import structures
import modules.database.schemas.nodes.schools.timetable as timetable
import modules.database.schemas.relationships.timetables as tt_rels
import modules.database.schemas.relationships.entity_timetable_rels as entity_tt_rels
import modules.database.schemas.relationships.calendar_timetable_rels as cal_tt_rels
import modules.database.init.init_calendar as init_calendar
import modules.database.tools.neontology_tools as neon

def create_school_timetable(dataframes, db_name, school_node=None, filesystem=None):
    logger.info(f"Creating school timetable for {db_name}")
    if dataframes is None:
        raise ValueError("Data is required to create the calendar and timetable.")

    logger.info(f"Initialising neo4j connection...")
    neon.init_neontology_connection()

    school_df = dataframes['school']
    if school_node is None:
        logger.info(f"School node is None, using school data from dataframe")
        school_uuid_string = school_df[school_df['Identifier'] == 'SchoolID']['Data'].iloc[0]
    else:
        logger.info(f"School node is not None, using school data from school node: {school_node}")
        school_uuid_string = school_node.uuid_string
    
    terms_df = dataframes['terms']
    weeks_df = dataframes['weeks']
    days_df = dataframes['days']
    periods_df = dataframes['periods']
    
    school_df_year_start = school_df[school_df['Identifier'] == 'AcademicYearStart']['Data'].iloc[0]
    school_df_year_end = school_df[school_df['Identifier'] == 'AcademicYearEnd']['Data'].iloc[0]
    if isinstance(school_df_year_start, str):
        school_year_start_date = datetime.strptime(school_df_year_start, '%Y-%m-%d')
    else:
        school_year_start_date = school_df_year_start
    if isinstance(school_df_year_end, str):
        school_year_end_date = datetime.strptime(school_df_year_end, '%Y-%m-%d')
    else:
        school_year_end_date = school_df_year_end

    # Create a dictionary to store the timetable nodes
    timetable_nodes = {
        'timetable_node': None,
        'academic_year_nodes': [],
        'academic_term_nodes': [],
        'academic_week_nodes': [],
        'academic_day_nodes': [],
        'academic_period_nodes': []
    }
    
    # Create AcademicTimetable Node
    school_timetable_uuid_string = f"{school_uuid_string}_{school_year_start_date.year}_{school_year_end_date.year}"
    
    # Generate storage path for timetable node
    if filesystem:
        timetable_dir_created, timetable_path = filesystem.create_school_timetable_directory()
        node_storage_path = os.path.relpath(timetable_path, filesystem.base_path)
        logger.info(f"Generated timetable node_storage_path: {node_storage_path}")
    else:
        node_storage_path = ""
        logger.warning("No filesystem provided, using empty storage path")
    
    school_timetable_node = timetable.SchoolTimetableNode(
        school_timetable_id=school_timetable_uuid_string,
        uuid_string=school_timetable_uuid_string,
        start_date=school_year_start_date,
        end_date=school_year_end_date,
        node_storage_path=node_storage_path
    )
    neon.create_or_merge_neontology_node(school_timetable_node, database=db_name, operation='merge')
    timetable_nodes['timetable_node'] = school_timetable_node
    
    if school_node:
        logger.info(f"Creating calendar for {school_uuid_string} from Neo4j SchoolNode: {school_node.uuid_string}")
        calendar_nodes = init_calendar.create_calendar(db_name, school_year_start_date, school_year_end_date, attach_to_calendar_node=True, owner_node=school_node, filesystem=filesystem)
        # Link the school node to the timetable node
        neon.create_or_merge_neontology_relationship(
            entity_tt_rels.SchoolHasTimetable(source=school_node, target=school_timetable_node),
            database=db_name, operation='merge'
        )
        timetable_nodes['calendar_nodes'] = calendar_nodes
    else:
        logger.info(f"Creating calendar for {school_uuid_string} from dataframe SchoolID: {school_uuid_string}")
        calendar_nodes = init_calendar.create_calendar(db_name, school_year_start_date, school_year_end_date, attach_to_calendar_node=False, owner_node=None, filesystem=filesystem)

    # Create AcademicYear nodes for each year within the range
    for year in range(school_year_start_date.year, school_year_end_date.year + 1):
        year_str = str(year)
        academic_year_uuid_string = f"{school_timetable_uuid_string}_{year}"
        
        # Generate storage path for academic year node
        if filesystem:
            year_dir_created, year_path = filesystem.create_school_timetable_year_directory(timetable_path, year)
            node_storage_path = os.path.relpath(year_path, filesystem.base_path)
        else:
            node_storage_path = ""
        
        academic_year_node = timetable.AcademicYearNode(
            uuid_string=academic_year_uuid_string,
            year=year_str,
            node_storage_path=node_storage_path
        )
        neon.create_or_merge_neontology_node(academic_year_node, database=db_name, operation='merge')
        timetable_nodes['academic_year_nodes'].append(academic_year_node)
        logger.info(f'Created academic year node: {academic_year_node.uuid_string}')
        neon.create_or_merge_neontology_relationship(
            tt_rels.AcademicTimetableHasAcademicYear(source=school_timetable_node, target=academic_year_node),
            database=db_name, operation='merge'
        )
        logger.info(f"Created school timetable relationship from {school_timetable_node.uuid_string} to {academic_year_node.uuid_string}")

        # Link the academic year with the corresponding calendar year node
        for year_node in calendar_nodes['calendar_year_nodes']:
            if year_node.year == year:
                neon.create_or_merge_neontology_relationship(
                    cal_tt_rels.AcademicYearIsCalendarYear(source=academic_year_node, target=year_node),
                    database=db_name, operation='merge'
                )
                logger.info(f"Created school timetable relationship from {academic_year_node.uuid_string} to {year_node.uuid_string}")
                break

    # Create Term and TermBreak nodes linked to AcademicYear
    term_number = 1
    academic_term_number = 1
    for _, term_row in terms_df.iterrows():
        term_node_class = timetable.AcademicTermNode if term_row['TermType'] == 'Term' else timetable.AcademicTermBreakNode
        term_name = term_row['TermName']
        term_name_no_spaces = term_name.replace(' ', '')
        term_start_date = term_row['StartDate']
        if isinstance(term_start_date, pd.Timestamp):
            term_start_date = term_start_date.strftime('%Y-%m-%d')
        
        term_end_date = term_row['EndDate']
        if isinstance(term_end_date, pd.Timestamp):
            term_end_date = term_end_date.strftime('%Y-%m-%d')

        # Generate storage path for term node
        if filesystem:
            if term_row['TermType'] == 'Term':
                term_dir_created, term_path = filesystem.create_school_timetable_academic_term_directory(timetable_path, term_name, academic_term_number)
            else:
                term_dir_created, term_path = filesystem.create_school_timetable_academic_term_break_directory(timetable_path, term_name)
            node_storage_path = os.path.relpath(term_path, filesystem.base_path)
        else:
            node_storage_path = ""

        if term_row['TermType'] == 'Term':
            term_node_uuid_string = f"{school_timetable_uuid_string}_{academic_term_number}_{term_name_no_spaces}"
            academic_term_number_str = str(academic_term_number)
            term_node = term_node_class(
                uuid_string=term_node_uuid_string,
                term_name=term_name,
                term_number=academic_term_number_str,
                start_date=datetime.strptime(term_start_date, '%Y-%m-%d'),
                end_date=datetime.strptime(term_end_date, '%Y-%m-%d'),
                node_storage_path=node_storage_path
            )
            academic_term_number += 1
        else:
            term_break_node_uuid_string = f"{school_timetable_uuid_string}_{term_name_no_spaces}"
            term_node = term_node_class(
                uuid_string=term_break_node_uuid_string,
                term_break_name=term_name,
                start_date=datetime.strptime(term_start_date, '%Y-%m-%d'),
                end_date=datetime.strptime(term_end_date, '%Y-%m-%d'),
                node_storage_path=node_storage_path
            )
        neon.create_or_merge_neontology_node(term_node, database=db_name, operation='merge')
        logger.info(f'Created academic term break node: {term_node.uuid_string}')
        timetable_nodes['academic_term_nodes'].append(term_node)
        term_number += 1 # We don't use this but we could

        # Link term node to the correct academic year
        term_years = set()
        term_years.update([term_node.start_date.year, term_node.end_date.year])

        for academic_year_node in timetable_nodes['academic_year_nodes']:
            if int(academic_year_node.year) in term_years:
                relationship_class = tt_rels.AcademicYearHasAcademicTerm if term_row['TermType'] == 'Term' else tt_rels.AcademicYearHasAcademicTermBreak
                neon.create_or_merge_neontology_relationship(
                    relationship_class(source=academic_year_node, target=term_node),
                    database=db_name, operation='merge'
                )
                logger.info(f"Created school timetable relationship from {academic_year_node.uuid_string} to {term_node.uuid_string}")

    # Create Week nodes
    academic_week_number = 1
    for _, week_row in weeks_df.iterrows():
        week_node_class = timetable.HolidayWeekNode if week_row['WeekType'] == 'Holiday' else timetable.AcademicWeekNode
        week_start_date = week_row['WeekStart']
        if isinstance(week_start_date, pd.Timestamp):
            week_start_date = week_start_date.strftime('%Y-%m-%d')
        
        week_node_uuid_string = f"{school_timetable_uuid_string}_{week_row['WeekNumber']}_{week_row['WeekType']}Week"
        
        # Generate storage path for week node
        if filesystem:
            week_dir_created, week_path = filesystem.create_school_timetable_academic_week_directory(timetable_path, week_row['WeekNumber'])
            node_storage_path = os.path.relpath(week_path, filesystem.base_path)
        else:
            node_storage_path = ""
        
        if week_row['WeekType'] == 'Holiday':
            week_node = week_node_class(
                uuid_string=week_node_uuid_string,
                start_date=datetime.strptime(week_start_date, '%Y-%m-%d'),
                node_storage_path=node_storage_path
            )
        else:
            academic_week_number_str = str(academic_week_number)
            week_type = week_row['WeekType']
            week_node = week_node_class(
                uuid_string=week_node_uuid_string,
                academic_week_number=academic_week_number_str,
                start_date=datetime.strptime(week_start_date, '%Y-%m-%d'),
                week_type=week_type,
                node_storage_path=node_storage_path
            )
            academic_week_number += 1
        neon.create_or_merge_neontology_node(week_node, database=db_name, operation='merge')
        timetable_nodes['academic_week_nodes'].append(week_node)
        logger.info(f"Created week node: {week_node.uuid_string}")
        for calendar_node in calendar_nodes['calendar_week_nodes']:
            if calendar_node.start_date == week_node.start_date:
                if isinstance(week_node, timetable.AcademicWeekNode):
                    neon.create_or_merge_neontology_relationship(
                        cal_tt_rels.AcademicWeekIsCalendarWeek(source=week_node, target=calendar_node),
                        database=db_name, operation='merge'
                    )
                    logger.info(f"Created school timetable relationship from {calendar_node.uuid_string} to {week_node.uuid_string}")
                elif isinstance(week_node, timetable.HolidayWeekNode):
                    neon.create_or_merge_neontology_relationship(
                        cal_tt_rels.HolidayWeekIsCalendarWeek(source=week_node, target=calendar_node),
                        database=db_name, operation='merge'
                    )
                    logger.info(f"Created school timetable relationship from {calendar_node.uuid_string} to {week_node.uuid_string}")
                break

        # Link week node to the correct academic term
        for term_node in timetable_nodes['academic_term_nodes']:
            if term_node.start_date <= week_node.start_date <= term_node.end_date:
                relationship_class = tt_rels.AcademicTermHasAcademicWeek if week_row['WeekType'] != 'Holiday' else tt_rels.AcademicTermBreakHasHolidayWeek
                neon.create_or_merge_neontology_relationship(
                    relationship_class(source=term_node, target=week_node),
                    database=db_name, operation='merge'
                )
                logger.info(f"Created school timetable relationship from {term_node.uuid_string} to {week_node.uuid_string}")
                break

        # Link week node to the correct academic year
        for academic_year_node in timetable_nodes['academic_year_nodes']:
            if int(academic_year_node.year) == week_node.start_date.year:
                relationship_class = tt_rels.AcademicYearHasAcademicWeek if week_row['WeekType'] != 'Holiday' else tt_rels.AcademicYearHasHolidayWeek
                neon.create_or_merge_neontology_relationship(
                    relationship_class(source=academic_year_node, target=week_node),
                    database=db_name, operation='merge'
                )
                logger.info(f"Created school timetable relationship from {academic_year_node.uuid_string} to {week_node.uuid_string}")
                break

    # Create Day nodes
    day_number = 1
    academic_day_number = 1
    for _, day_row in days_df.iterrows():        
        date_str = day_row['Date']
        if isinstance(date_str, pd.Timestamp):
            date_str = date_str.strftime('%Y-%m-%d')

        day_node_class = {
            'Academic': timetable.AcademicDayNode,
            'Holiday': timetable.HolidayDayNode,
            'OffTimetable': timetable.OffTimetableDayNode,
            'StaffDay': timetable.StaffDayNode
        }[day_row['DayType']]

        # Generate storage path for day node
        if filesystem:
            day_dir_created, day_path = filesystem.create_school_timetable_academic_day_directory(timetable_path, academic_day_number)
            node_storage_path = os.path.relpath(day_path, filesystem.base_path)
        else:
            node_storage_path = ""
        
        # Format the unique ID as {day_node_class.__name__}Day
        day_node_data = {
            'uuid_string': f"{school_timetable_uuid_string}_{day_number}_{day_node_class.__name__}Day",
            'date': datetime.strptime(date_str, '%Y-%m-%d'),
            'day_of_week': datetime.strptime(date_str, '%Y-%m-%d').strftime('%A'),
            'node_storage_path': node_storage_path
        }
        
        if day_row['DayType'] == 'Academic':  
            day_node_data['academic_day'] = str(academic_day_number)
            day_node_data['day_type'] = day_row['WeekType']
        
        day_node = day_node_class(**day_node_data)
        
        for calendar_node in calendar_nodes['calendar_day_nodes']:
            if calendar_node.date == day_node.date:
                neon.create_or_merge_neontology_node(day_node, database=db_name, operation='merge')
                timetable_nodes['academic_day_nodes'].append(day_node)
                logger.info(f"Created day node: {day_node.uuid_string}")
                
                if isinstance(day_node, timetable.AcademicDayNode):
                    relationship_class = cal_tt_rels.AcademicDayIsCalendarDay
                elif isinstance(day_node, timetable.HolidayDayNode):
                    relationship_class = cal_tt_rels.HolidayDayIsCalendarDay
                elif isinstance(day_node, timetable.OffTimetableDayNode):
                    relationship_class = cal_tt_rels.OffTimetableDayIsCalendarDay
                elif isinstance(day_node, timetable.StaffDayNode):
                    relationship_class = cal_tt_rels.StaffDayIsCalendarDay
                
                neon.create_or_merge_neontology_relationship(
                    relationship_class(source=day_node, target=calendar_node),
                    database=db_name, operation='merge'
                )
                logger.info(f'Created relationship from {calendar_node.uuid_string} to {day_node.uuid_string}')
                break
        
        # Link day node to the correct academic week
        for academic_week_node in timetable_nodes['academic_week_nodes']:
            if academic_week_node.start_date <= day_node.date <= (academic_week_node.start_date + timedelta(days=6)):
                if day_row['DayType'] == 'Academic':
                    relationship_class = tt_rels.AcademicWeekHasAcademicDay
                elif day_row['DayType'] == 'Holiday':
                    if hasattr(academic_week_node, 'week_type') and academic_week_node.week_type in ['A', 'B']:
                        relationship_class = tt_rels.AcademicWeekHasHolidayDay
                    else:
                        relationship_class = tt_rels.HolidayWeekHasHolidayDay
                elif day_row['DayType'] == 'OffTimetable':
                    relationship_class = tt_rels.AcademicWeekHasOffTimetableDay
                elif day_row['DayType'] == 'Staff':
                    relationship_class = tt_rels.AcademicWeekHasStaffDay
                else:
                    continue  # Skip linking for other day types
                neon.create_or_merge_neontology_relationship(
                    relationship_class(source=academic_week_node, target=day_node),
                    database=db_name, operation='merge'
                )
                logger.info(f"Created relationship from {academic_week_node.uuid_string} to {day_node.uuid_string}")
                break

        # Link day node to the correct academic term
        for term_node in timetable_nodes['academic_term_nodes']:
            if term_node.start_date <= day_node.date <= term_node.end_date:
                if day_row['DayType'] == 'Academic':
                    relationship_class = tt_rels.AcademicTermHasAcademicDay
                elif day_row['DayType'] == 'Holiday':
                    if isinstance(term_node, timetable.AcademicTermNode):
                        relationship_class = tt_rels.AcademicTermHasHolidayDay
                    else:
                        relationship_class = tt_rels.AcademicTermBreakHasHolidayDay
                elif day_row['DayType'] == 'OffTimetable':
                    relationship_class = tt_rels.AcademicTermHasOffTimetableDay
                elif day_row['DayType'] == 'Staff':
                    relationship_class = tt_rels.AcademicTermHasStaffDay
                else:
                    continue  # Skip linking for other day types
                neon.create_or_merge_neontology_relationship(
                    relationship_class(source=term_node, target=day_node),
                    database=db_name, operation='merge'
                )
                logger.info(f"Created relationship from {term_node.uuid_string} to {day_node.uuid_string}")
                break
        
        # Create Period nodes for each academic day
        if day_row['DayType'] == 'Academic':
            logger.info(f"Creating periods for {day_node.uuid_string}")
            period_of_day = 1
            academic_or_registration_period_of_day = 1
            for _, period_row in periods_df.iterrows():
                period_node_class = {
                    'Academic': timetable.AcademicPeriodNode,
                    'Registration': timetable.RegistrationPeriodNode,
                    'Break': timetable.BreakPeriodNode,
                    'OffTimetable': timetable.OffTimetablePeriodNode
                }[period_row['PeriodType']]
                
                logger.info(f"Creating period node for {period_node_class.__name__} Period: {period_of_day}")
                period_node_uuid_string = f"{school_timetable_uuid_string}_{academic_day_number}_{period_of_day}_{period_node_class.__name__}Period"
                logger.debug(f"Period node unique id: {period_node_uuid_string}")
                # Generate storage path for period node
                if filesystem:
                    period_dir_created, period_path = filesystem.create_school_timetable_period_directory(timetable_path, academic_day_number, period_row['PeriodCode'])
                    node_storage_path = os.path.relpath(period_path, filesystem.base_path)
                else:
                    node_storage_path = ""
                
                period_node_data = {
                    'uuid_string': period_node_uuid_string,
                    'name': period_row['PeriodName'],
                    'date': day_node.date,
                    'start_time': datetime.combine(day_node.date, period_row['StartTime']),
                    'end_time': datetime.combine(day_node.date, period_row['EndTime']),
                    'node_storage_path': node_storage_path
                }
                logger.debug(f"Period node data: {period_node_data}")
                if period_row['PeriodType'] in ['Academic', 'Registration']:
                    week_type = day_row['WeekType']
                    day_name_short = day_node.day_of_week[:3]
                    period_code = period_row['PeriodCode']
                    period_code_formatted = f"{week_type}{day_name_short}{period_code}"
                    period_node_data['period_code'] = period_code_formatted
                    
                    academic_or_registration_period_of_day += 1
                
                period_node = period_node_class(**period_node_data)
                neon.create_or_merge_neontology_node(period_node, database=db_name, operation='merge')
                timetable_nodes['academic_period_nodes'].append(period_node)
                logger.info(f'Created period node: {period_node.uuid_string}')
                
                relationship_class = {
                    'Academic': tt_rels.AcademicDayHasAcademicPeriod,
                    'Registration': tt_rels.AcademicDayHasRegistrationPeriod,
                    'Break': tt_rels.AcademicDayHasBreakPeriod,
                    'OffTimetable': tt_rels.AcademicDayHasOffTimetablePeriod
                }[period_row['PeriodType']]
                
                neon.create_or_merge_neontology_relationship(
                    relationship_class(source=day_node, target=period_node),
                    database=db_name, operation='merge'
                )
                logger.info(f"Created relationship from {day_node.uuid_string} to {period_node.uuid_string}")
                period_of_day += 1 # We don't use this but we could
            academic_day_number += 1 # This is a bit of a hack but it works to keep the directories aligned (reorganise)
        day_number += 1 # We don't use this but we could

    def create_school_timetable_node_sequence_rels(timetable_nodes):
        def sort_and_create_relationships(nodes, relationship_map, sort_key):
            sorted_nodes = sorted(nodes, key=sort_key)
            for i in range(len(sorted_nodes) - 1):
                source_node = sorted_nodes[i]
                target_node = sorted_nodes[i + 1]
                node_type_pair = (type(source_node), type(target_node))
                relationship_class = relationship_map.get(node_type_pair)
                if relationship_class:
                    # Avoid self-referential relationships
                    if source_node.uuid_string != target_node.uuid_string:
                        neon.create_or_merge_neontology_relationship(
                            relationship_class(
                                source=source_node,
                                target=target_node
                            ),
                            database=db_name, operation='merge'
                        )
                        logger.info(f"Created relationship from {source_node.uuid_string} to {target_node.uuid_string}")
                    else:
                        logger.warning(f"Skipped self-referential relationship for node {source_node.uuid_string}")
        
        # Relationship maps for different node types
        academic_year_relationship_map = {
            (timetable.AcademicYearNode, timetable.AcademicYearNode): tt_rels.AcademicYearFollowsAcademicYear
        }

        academic_term_relationship_map = {
            (timetable.AcademicTermNode, timetable.AcademicTermBreakNode): tt_rels.AcademicTermBreakFollowsAcademicTerm,
            (timetable.AcademicTermBreakNode, timetable.AcademicTermNode): tt_rels.AcademicTermFollowsAcademicTermBreak
        }

        academic_week_relationship_map = {
            (timetable.AcademicWeekNode, timetable.AcademicWeekNode): tt_rels.AcademicWeekFollowsAcademicWeek,
            (timetable.HolidayWeekNode, timetable.HolidayWeekNode): tt_rels.HolidayWeekFollowsHolidayWeek,
            (timetable.AcademicWeekNode, timetable.HolidayWeekNode): tt_rels.HolidayWeekFollowsAcademicWeek,
            (timetable.HolidayWeekNode, timetable.AcademicWeekNode): tt_rels.AcademicWeekFollowsHolidayWeek
        }

        academic_day_relationship_map = {
            (timetable.AcademicDayNode, timetable.AcademicDayNode): tt_rels.AcademicDayFollowsAcademicDay,
            (timetable.HolidayDayNode, timetable.HolidayDayNode): tt_rels.HolidayDayFollowsHolidayDay,
            (timetable.OffTimetableDayNode, timetable.OffTimetableDayNode): tt_rels.OffTimetableDayFollowsOffTimetableDay,
            (timetable.StaffDayNode, timetable.StaffDayNode): tt_rels.StaffDayFollowsStaffDay,

            (timetable.AcademicDayNode, timetable.HolidayDayNode): tt_rels.HolidayDayFollowsAcademicDay,
            (timetable.AcademicDayNode, timetable.OffTimetableDayNode): tt_rels.OffTimetableDayFollowsAcademicDay,
            (timetable.AcademicDayNode, timetable.StaffDayNode): tt_rels.StaffDayFollowsAcademicDay,

            (timetable.HolidayDayNode, timetable.AcademicDayNode): tt_rels.AcademicDayFollowsHolidayDay,
            (timetable.HolidayDayNode, timetable.OffTimetableDayNode): tt_rels.OffTimetableDayFollowsHolidayDay,
            (timetable.HolidayDayNode, timetable.StaffDayNode): tt_rels.StaffDayFollowsHolidayDay,

            (timetable.OffTimetableDayNode, timetable.AcademicDayNode): tt_rels.AcademicDayFollowsOffTimetableDay,
            (timetable.OffTimetableDayNode, timetable.HolidayDayNode): tt_rels.HolidayDayFollowsOffTimetableDay,
            (timetable.OffTimetableDayNode, timetable.StaffDayNode): tt_rels.StaffDayFollowsOffTimetableDay,

            (timetable.StaffDayNode, timetable.AcademicDayNode): tt_rels.AcademicDayFollowsStaffDay,
            (timetable.StaffDayNode, timetable.HolidayDayNode): tt_rels.HolidayDayFollowsStaffDay,
            (timetable.StaffDayNode, timetable.OffTimetableDayNode): tt_rels.OffTimetableDayFollowsStaffDay,
        }

        academic_period_relationship_map = {
            (timetable.AcademicPeriodNode, timetable.AcademicPeriodNode): tt_rels.AcademicPeriodFollowsAcademicPeriod,
            (timetable.AcademicPeriodNode, timetable.BreakPeriodNode): tt_rels.BreakPeriodFollowsAcademicPeriod,
            (timetable.AcademicPeriodNode, timetable.RegistrationPeriodNode): tt_rels.RegistrationPeriodFollowsAcademicPeriod,
            (timetable.AcademicPeriodNode, timetable.OffTimetablePeriodNode): tt_rels.OffTimetablePeriodFollowsAcademicPeriod,
            (timetable.BreakPeriodNode, timetable.AcademicPeriodNode): tt_rels.AcademicPeriodFollowsBreakPeriod,
            (timetable.BreakPeriodNode, timetable.BreakPeriodNode): tt_rels.BreakPeriodFollowsBreakPeriod,
            (timetable.BreakPeriodNode, timetable.RegistrationPeriodNode): tt_rels.RegistrationPeriodFollowsBreakPeriod,
            (timetable.BreakPeriodNode, timetable.OffTimetablePeriodNode): tt_rels.OffTimetablePeriodFollowsBreakPeriod,
            (timetable.RegistrationPeriodNode, timetable.AcademicPeriodNode): tt_rels.AcademicPeriodFollowsRegistrationPeriod,
            (timetable.RegistrationPeriodNode, timetable.RegistrationPeriodNode): tt_rels.RegistrationPeriodFollowsRegistrationPeriod,
            (timetable.RegistrationPeriodNode, timetable.BreakPeriodNode): tt_rels.BreakPeriodFollowsRegistrationPeriod,
            (timetable.RegistrationPeriodNode, timetable.OffTimetablePeriodNode): tt_rels.OffTimetablePeriodFollowsRegistrationPeriod,
            (timetable.OffTimetablePeriodNode, timetable.OffTimetablePeriodNode): tt_rels.OffTimetablePeriodFollowsOffTimetablePeriod,
            (timetable.OffTimetablePeriodNode, timetable.AcademicPeriodNode): tt_rels.AcademicPeriodFollowsOffTimetablePeriod,
            (timetable.OffTimetablePeriodNode, timetable.BreakPeriodNode): tt_rels.BreakPeriodFollowsOffTimetablePeriod,
            (timetable.OffTimetablePeriodNode, timetable.RegistrationPeriodNode): tt_rels.RegistrationPeriodFollowsOffTimetablePeriod,
        }


        # Sort and create relationships
        sort_and_create_relationships(timetable_nodes['academic_year_nodes'], academic_year_relationship_map, lambda x: int(x.year))
        sort_and_create_relationships(timetable_nodes['academic_term_nodes'], academic_term_relationship_map, lambda x: x.start_date)
        sort_and_create_relationships(timetable_nodes['academic_week_nodes'], academic_week_relationship_map, lambda x: x.start_date)
        sort_and_create_relationships(timetable_nodes['academic_day_nodes'], academic_day_relationship_map, lambda x: x.date)
        sort_and_create_relationships(timetable_nodes['academic_period_nodes'], academic_period_relationship_map, lambda x: (x.start_time, x.end_time))
    
    # Call the function with the created timetable nodes
    create_school_timetable_node_sequence_rels(timetable_nodes)
    
    logger.info(f'Created timetable: {timetable_nodes["timetable_node"].uuid_string}')

    # Log the directory structure after creation
    # root_timetable_directory = fs_handler.root_path  # Access the root directory of the filesystem handler
    # fs_handler.log_directory_structure(root_timetable_directory)
    
    return {
        'school_node': school_node,
        'school_calendar_nodes': calendar_nodes,
        'school_timetable_nodes': timetable_nodes
    }