# test odb setup, in particular the custom page link
# Derek Fujimoto
# Aug 2026

import pytest
import os
import sys

# star import, like every other test module: importing only "client" meant the autouse
# start_frontend fixture in fixtures.py never applied here, so these tests ran against
# whatever ODB the previous module happened to leave behind, and called setup_odb against
# the live experiment with no clear or restore
from fixtures import *

sys.path.append('..')
from setup_odb import setup_odb, get_log_path

# name of the file the custom page should point at
page = 'shiftschedule.html'

def find_page_keys(client):
    """Find the /Custom keys which link to the shift schedule page

    Args:
        client (midas.client.client): midas client object

    Returns:
        dict: key name -> value, for the keys pointing at the shift schedule page
    """

    try:
        custom = client.odb_get('/Custom', recurse_dir=False)
    except KeyError:
        return {}

    # match on the value, not the key name, which sets the menu label.
    # subdirectories come back as dicts - skip those
    return {key: value for key, value in custom.items()
            if isinstance(value, str) and os.path.basename(value) == page}

@pytest.fixture
def no_custom_page(client):
    """Remove all links to the shift schedule page, restoring the originals on teardown"""

    original = find_page_keys(client)

    for key in original:
        client.odb_delete(f'/Custom/{key}')

    yield

    # drop whatever the test left behind, then put the original keys back
    for key in find_page_keys(client):
        client.odb_delete(f'/Custom/{key}')

    for key, value in original.items():
        client.odb_set(f'/Custom/{key}', contents=value, create_if_needed=True)

# check the page link is made when nothing in the odb links to the page
def test_custom_page_created(client, no_custom_page):

    setup_odb(client)

    assert client.odb_exists('/Custom/ShiftSchedule'), \
        "Expected setup to create /Custom/ShiftSchedule when no link to the page exists, but it did not"

    value = client.odb_get('/Custom/ShiftSchedule')
    assert value == page, \
        f"Expected /Custom/ShiftSchedule to be '{page}', got '{value}'"

# check no second link is made when the page is already linked under another key name,
# for values written either as a bare filename or as a path
@pytest.mark.parametrize('value', [page, f'shiftschedule/{page}'])
def test_custom_page_not_duplicated(client, no_custom_page, value):

    client.odb_set('/Custom/TestShiftPage', contents=value, create_if_needed=True)

    setup_odb(client)

    assert not client.odb_exists('/Custom/ShiftSchedule'), \
        f"Expected setup to skip making /Custom/ShiftSchedule when /Custom/TestShiftPage " \
        f"already links to '{value}', but it was created anyway"

    found = client.odb_get('/Custom/TestShiftPage')
    assert found == value, \
        f"Expected the existing link /Custom/TestShiftPage to still be '{value}', got '{found}'"

# check re-running the setup with the default link in place changes nothing
def test_custom_page_rerun_is_noop(client, no_custom_page):

    client.odb_set('/Custom/ShiftSchedule', contents=page, create_if_needed=True)

    setup_odb(client)

    found = find_page_keys(client)
    assert found == {'ShiftSchedule': page}, \
        f"Expected re-running setup to leave a single link {{'ShiftSchedule': '{page}'}}, got {found}"

# directory holding the package, worked out from this file rather than from setup_odb, so
# the assertions below do not just repeat whatever the code under test computed
package_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

def experiment_dir():
    """Directory holding the exptab file, which is what midas resolves relative log dirs against"""
    return os.path.dirname(os.environ['MIDAS_EXPTAB'])

@pytest.fixture
def log_dir(client, tmp_path):
    """Send the midas log directory to a temporary directory, and yield it

    Restores the keys on teardown by hand: /Logger sits outside the /Shifts tree the suite
    clears and restores, so a test which left these pointing at a temporary directory would
    leave the running experiment writing its messages somewhere that is about to be deleted.
    """
    original = {key: client.odb_get(key)
                for key in ('/Logger/Message dir', '/Logger/Data dir')}

    client.odb_set('/Logger/Message dir', '')
    client.odb_set('/Logger/Data dir', str(tmp_path))

    yield tmp_path

    for key, value in original.items():
        client.odb_set(key, value)

# check the log lands in the directory midas keeps its own logs in
def test_log_path_data_dir(client, log_dir):

    expected = str(log_dir / 'shiftschedule.log')
    found = get_log_path(client)
    assert found == expected, \
        f"Expected the log path to follow /Logger/Data dir to '{expected}', got '{found}'"

# check the message directory wins over the data directory, the order midas reads them in
def test_log_path_prefers_message_dir(client, log_dir):

    message_dir = log_dir / 'messages'
    client.odb_set('/Logger/Message dir', str(message_dir))

    expected = str(message_dir / 'shiftschedule.log')
    found = get_log_path(client)
    assert found == expected, \
        f"Expected /Logger/Message dir to take precedence, giving '{expected}', got '{found}'"

# check a relative directory is read against the experiment directory, not the working one.
# pytest runs from tests/, so the two are easy to tell apart
def test_log_path_relative_to_experiment(client, log_dir):

    client.odb_set('/Logger/Data dir', 'logs')

    expected = os.path.join(experiment_dir(), 'logs', 'shiftschedule.log')
    found = get_log_path(client)
    assert found == expected, \
        f"Expected a relative log directory to resolve against the experiment directory as " \
        f"'{expected}', got '{found}'"

# check both keys blank falls back to the experiment directory, as midas does
def test_log_path_blank_falls_back(client, log_dir):

    client.odb_set('/Logger/Data dir', '')

    expected = os.path.join(experiment_dir(), 'shiftschedule.log')
    found = get_log_path(client)
    assert found == expected, \
        f"Expected blank log directory keys to fall back to '{expected}', got '{found}'"

# check the symlink an older setup left at the log path is removed. left in place, the client
# would follow it and write into the package instead
def test_setup_removes_stale_symlink(client, log_dir):

    link = str(log_dir / 'shiftschedule.log')
    os.symlink(os.path.join(package_dir, 'shiftschedule.log'), link)

    setup_odb(client)

    assert not os.path.lexists(link), \
        f"Expected setup to remove the obsolete log symlink at '{link}', but it is still there"

# check a real log file at that path is never deleted
def test_setup_keeps_real_log(client, log_dir):

    logfile = log_dir / 'shiftschedule.log'
    logfile.write_text('a real log, not a link')

    setup_odb(client)

    assert logfile.read_text() == 'a real log, not a link', \
        "Expected setup to leave a real log file at the log path untouched"

# check the program start command is recorded without the directory setup happened to run in
def test_start_command_path(client):

    setup_odb(client)

    expected = os.path.join(package_dir, 'start_shiftschedule.bash')
    found = client.odb_get('/Programs/shiftschedule/Start command')
    assert found == expected, \
        f"Expected the start command to be '{expected}', got '{found}'"
