# Utility Functions
import datetime
import logging
import os
import re
import sys
import yaml
import zoneinfo
from traceback import format_exc

logger = logging.getLogger(__name__)

def execution_dir() -> str:
    """Return the top-level execution directory (parent of the entry-point script)."""
    return os.path.dirname(os.path.abspath(sys.argv[0]))

def get_timestamp() -> str:
    """Get the current timestamp as a string formatted 'YYYY-MM-DD_HH-MM-SS'."""
    now = datetime.datetime.now()
    timestamp = now.strftime("%Y-%m-%d_%H-%M-%S")
    return timestamp

def open_file(file_path: str):
    """Read a basic text file with UTF-8 encoding."""
    with open(file_path, 'r', encoding='utf-8') as file:
        return file.read()

def read_reverse_order(file_path: str):
    """Read the file backwards by returning every stripped line in reverse order."""
    with open(file_path, 'r', encoding='utf-8') as file:
        return file.read().splitlines()[::-1]

def read_yaml(file_path: str):
    """Read a YAML-formatted file to retrieve data as a list or dictionary."""
    with open(file_path, 'r', encoding='utf-8') as file:
        data = yaml.safe_load(file)
    return data

def read_text(file_path: str) -> str:
    """Read a plain text file and returns the string output."""
    text = open_file(file_path)
    return text.strip()

def exact_word_match(word: str, text: str) -> bool:
    """
    Determine if the given text has the exact word.
    Example: 'Word' for 'Word-cross' (match) vs. 'word1-cross' (no match)
    """
    return re.search(rf'\b{word}\b', text) is not None

def generate_filename(name: str) -> str:
    """Generate a log filename string formatted '<name>_<timestamp>.log'."""
    return f"{name}_{get_timestamp()}.log"

def generate_file_path(directory: str, file: str, name: str = "", text: str = "") -> str:
    """
    Ensure a file exists at the given path, creating it with optional content if needed.

    If the file does not already exist, creates any missing parent directories and
    writes the optional text content. Does nothing if the file is already present.
    A creation event is logged via logger.info.

    Args:
        directory: Parent directory for the file (must be a valid directory path).
        file: Filename to create (may include subdirectory segments like 'subdir/file.txt').
        name: Human-readable label for logging (e.g. 'config file'). Defaults to 'path'.
        text: Initial content to write into a newly created file. Empty string or omitted
              creates an empty file.

    Returns:
        The absolute path to the file (created or pre-existing).
    """
    path = os.path.join(directory, file)
    name = "path" if not name else name
    if not os.path.exists(path) and not os.path.isfile(path):
        try:
            os.makedirs(os.path.dirname(path), exist_ok=True)
            with open(path, 'w', encoding='utf-8') as f:
                if not text:
                    pass
                else:
                    f.write(text)
            logger.info(f"Created {name} file: {path}")
        except Exception as e:
            logger.error(f"{type(e).__name__} generating '{path}':\n{format_exc()}")
    return path

def format_dt(dt: datetime.datetime) -> str:
    """Format a timezone-aware datetime as '2026/03/31 12:59:53 AM UTC'."""
    hour = dt.hour % 12 or 12
    ampm = "AM" if dt.hour < 12 else "PM"
    return f"{dt.strftime('%Y/%m/%d')} {hour:02d}:{dt.strftime('%M:%S')} {ampm} {dt.strftime('%Z')}"

def format_now(tz_name: str | None) -> str:
    """
    Return the current datetime as a human-readable string in the given timezone.

    Uses portable manual hour formatting to avoid platform-specific strftime directives
    (e.g. %-I is Linux-only). Falls back to UTC if tz_name is None.

    Args:
        tz_name: IANA timezone string (e.g. 'America/New_York'), or None for UTC.

    Returns:
        Formatted string, e.g. '2026/03/30 10:23:45 AM UTC'.
    """
    utc_now = datetime.datetime.now(datetime.timezone.utc)
    now = utc_now.astimezone(zoneinfo.ZoneInfo(tz_name)) if tz_name else utc_now
    return format_dt(now)

def log_error(error: Exception | str, error_type: str):
    """
    Log an error message via the logging system with timestamp and error type.

    Args:
        error (Exception or str): The error object or message to log.
        error_type (str): A label for the error type (e.g., 'ValueError', 'ConnectionError').
    """
    logger.error(f"{error_type}: {error}", exc_info=isinstance(error, BaseException))
