import json
import toml
from datetime import datetime
import inspect
import types
import os
import logging
import socket
import re

from pipeline.time_manager import TimeManager

logger = logging.getLogger(__name__)


def load_json(filepath):
    if not os.path.exists(filepath):
        logger.warning(f"[load_json] File not found: {filepath}")
        return {}

    if os.path.getsize(filepath) == 0:
        logger.warning(f"[load_json] File is empty: {filepath}")
        return {}

    try:
        with open(filepath, 'r') as file:
            return json.load(file)
    except json.JSONDecodeError as e:
        logger.error(f"[load_json] Failed to decode JSON in {filepath}: {e}")
        return {}

def load_toml(filepath):
    # Load TOML data from the file
    with open(filepath, 'r') as f:
        dic_toml = toml.load(f)
    return dic_toml

#def round_datetime_to_nearest_past_five_minutes(dt: datetime) -> datetime:
def round_datetime_to_nearest_past_five_minutes(dt):
    #print(f"dt = {dt}")
    allowed_minutes = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55]
    # Find the largest allowed minute <= current minute
    rounded_minute = max(m for m in allowed_minutes if m <= dt.minute)
    return dt.replace(minute=rounded_minute, second=0, microsecond=0)

def get_now_time_rounded(workspace_manager):# -> int:
    '''
    workspace_manager is included here so that references can be made to the configured timezone
    '''
    logger.debug(f"helpers.get_now_time_rounded(workspace_manager)")
    nowtime = round_datetime_to_nearest_past_five_minutes(datetime.now())
    logger.debug(f"rounded nowtime = {nowtime}")
    nowtime_local =  int(nowtime.timestamp())+300
    nowtime_local = TimeManager(nowtime_local).as_datetime()
    if False:
        try:
            config = load_toml(workspace_manager.get_configuration_file_path())
            timezone_config = config["settings"]["timezone"]
        except:
            timezone_config = "America/Chicago"
        nowtime_utc = TimeManager.from_local(nowtime_local, zone_name = timezone_config).as_unix()
        logger.debug(f"return nowtime_utc")
        return nowtime_utc
    else:
        logger.debug(f"return nowtime_local")
        return TimeManager(nowtime_local).as_unix() # nowtime_utc

def function_view(globals_passed=None):
    # Use the calling frame to get info about the *caller* module
    caller_frame = inspect.stack()[1].frame
    if globals_passed is None:
        globals_passed = caller_frame.f_globals

    # Get filename → basename only (e.g., 'calls.py')
    filename = os.path.basename(caller_frame.f_code.co_filename)

    print(f"Functions defined in {filename}:")

    for name, obj in list(globals_passed.items()):
        if isinstance(obj, types.FunctionType):
            if getattr(obj, "__module__", None) == globals_passed.get('__name__', ''):
                print(f"  {name}")
    print("\n")

#def get_nested_config(dct: dict, keys: list[str]):
def get_nested_config(dct, keys):
    """Retrieve nested dict value by keys list; raise KeyError with full path if missing."""
    current = dct
    for key in keys:
        try:
            current = current[key]
        except KeyError as e:
            full_path = " -> ".join(keys)
            raise KeyError(f"Missing required configuration at path: {full_path}") from e
    return current

def human_readable(ts):
    return datetime.fromtimestamp(ts).strftime("%H:%M:%S")

def iso(ts):
    return datetime.fromtimestamp(ts).isoformat()

def get_lan_ip_address_of_current_machine():
    """Get the LAN IP address of the current machine."""
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        # Doesn't need to be reachable; just picks the active interface
        s.connect(("8.8.8.8", 80))
        return s.getsockname()[0]
    finally:
        s.close()
        
def nice_step(delta_sec: int) -> int:
    """
    Return a "nice" step in seconds (1,2,5,10,15,30,60,120,...)
    """
    nice_numbers = [1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600, 7200, 14400, 28800, 86400]
    target_step = delta_sec // 400  # aim for ~400 points
    # find the smallest nice_number >= target_step
    for n in nice_numbers:
        if n >= target_step:
            return n
    return nice_numbers[-1]



def sanitize_date_input(date_str: str) -> str:
    '''Sanitize date input strings by adding spaces where needed, to overcome error in fuzzy date parsing by the pendulum library.'''
    # 1. Add space between letters and numbers
    date_str = re.sub(r'([A-Za-z])(\d)', r'\1 \2', date_str)
    # 2. Ensure a space after commas
    date_str = re.sub(r',\s*', ', ', date_str)
    # 3. Normalize multiple spaces
    date_str = re.sub(r'\s+', ' ', date_str).strip()
    return date_str
    
if __name__ == "__main__":
    function_view()
    # Example
    sanitize_date_input("December12,2024")  # -> "December 12,2024"
