# Utility Functions
import os
import re
import sys
import yaml
from datetime import datetime
from tempfile import gettempdir
from traceback import format_exc
from typing import Optional

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.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(bot_name: str, user_name: str) -> str:
    """Generate a Chat Session log filename string formatted '<bot>-<username>-<timestamp>.log'."""
    timestamp = get_timestamp()
    return f'{bot_name}-{user_name}-{timestamp}.log'

def append_text_file_path(file_path: str, text=""):
    """Append existing text after reading from a file path provided."""
    if not text:
        pass
    else:
        try:
            with open(file_path, 'a', encoding='utf-8') as f:
                f.write(f"{text}\n")
        except Exception as e:
            print(f"{type(e).__name__} writing to '{file_path}':\n{format_exc()}")

def generate_file_path(directory: str, file: str, name: str = "", text: str = "") -> str:
    """Generate a new file by directory with an optional file type name and text."""
    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)
            print(f"Created {name} file: {path}")
        except Exception as e:
            print(f"{type(e).__name__} generating '{path}':\n{format_exc()}")
    return path

def generate_error_path(file: str = "tellmgrambot_error.log") -> str:
    """
    Generate the path to the error log file, respecting the TELLMGRAMBOT_ERRORLOGS_PATH environment variable.

    If TELLMGRAMBOT_ERRORLOGS_PATH is not set, defaults to the system temporary directory and prints
    a notice. Returns the full file path (directory + filename).

    Args:
        file (str): Error log filename. Defaults to 'tellmgrambot_error.log'.

    Returns:
        str: Full path to the error log file.
    """
    env_var = 'TELLMGRAMBOT_ERRORLOGS_PATH'
    if not os.environ.get(env_var):
        os.environ[env_var] = gettempdir()
        print(f"{env_var} not set, using system temp: {os.environ[env_var]}")
    return generate_file_path(os.environ[env_var], file, f"empty {env_var}")

def log_error(error: Exception | str, error_type: str, error_filename: Optional[str] = None):
    """
    Log an error message to both console and file with timestamp and error type.

    The error filename is lazily evaluated: if not provided, the path is resolved by calling
    `generate_error_path()`, which respects the TELLMGRAMBOT_ERRORLOGS_PATH environment variable.
    This deferred resolution allows the environment variable to be set at any point before the
    error is logged, rather than at import/definition time.

    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').
        error_filename (str, optional): Full path to the error log file. If None, `generate_error_path()`
            is called to determine the path. Defaults to None.

    Returns:
        None: Logs to both console (via print) and the error file.
    """
    if error_filename is None:
        error_filename = generate_error_path()
    text = f"{get_timestamp()} {error_type}: {error}"
    print(text) # Also log into console
    append_text_file_path(error_filename, text)
