from datetime import datetime, timedelta
from typing import Union, Optional

def timeago(dt: Union[datetime, str], now: Optional[datetime] = None) -> str:
    """
    Convert a datetime to a human-readable relative time string.
    
    Args:
        dt: The datetime to convert (can be datetime object or ISO format string)
        now: Optional reference time (defaults to current time)
    
    Returns:
        str: Human-readable relative time string (e.g., "2 hours ago", "3 days ago")
    """
    if isinstance(dt, str):
        dt = datetime.fromisoformat(dt.replace('Z', '+00:00'))
    
    if now is None:
        now = datetime.utcnow()
    
    diff = now - dt
    
    # Less than a minute
    if diff < timedelta(minutes=1):
        return "just now"
    
    # Less than an hour
    if diff < timedelta(hours=1):
        minutes = int(diff.total_seconds() / 60)
        return f"{minutes} minute{'s' if minutes != 1 else ''} ago"
    
    # Less than a day
    if diff < timedelta(days=1):
        hours = int(diff.total_seconds() / 3600)
        return f"{hours} hour{'s' if hours != 1 else ''} ago"
    
    # Less than a week
    if diff < timedelta(days=7):
        days = diff.days
        return f"{days} day{'s' if days != 1 else ''} ago"
    
    # Less than a month
    if diff < timedelta(days=30):
        weeks = int(diff.days / 7)
        return f"{weeks} week{'s' if weeks != 1 else ''} ago"
    
    # Less than a year
    if diff < timedelta(days=365):
        months = int(diff.days / 30)
        return f"{months} month{'s' if months != 1 else ''} ago"
    
    # More than a year
    years = int(diff.days / 365)
    return f"{years} year{'s' if years != 1 else ''} ago"

def format_datetime(dt: Union[datetime, str], format: str = "%Y-%m-%d %H:%M:%S") -> str:
    """
    Format a datetime object or ISO string to a specified format.
    
    Args:
        dt: The datetime to format (can be datetime object or ISO format string)
        format: The format string to use (defaults to "YYYY-MM-DD HH:MM:SS")
    
    Returns:
        str: Formatted datetime string
    """
    if isinstance(dt, str):
        dt = datetime.fromisoformat(dt.replace('Z', '+00:00'))
    return dt.strftime(format)

def parse_datetime(dt_str: str) -> datetime:
    """
    Parse a datetime string in various formats to a datetime object.
    
    Args:
        dt_str: The datetime string to parse
    
    Returns:
        datetime: Parsed datetime object
    
    Raises:
        ValueError: If the string cannot be parsed
    """
    # Try ISO format first
    try:
        return datetime.fromisoformat(dt_str.replace('Z', '+00:00'))
    except ValueError:
        pass
    
    # Try common formats
    formats = [
        "%Y-%m-%d %H:%M:%S",
        "%Y-%m-%d %H:%M",
        "%Y-%m-%d",
        "%d/%m/%Y %H:%M:%S",
        "%d/%m/%Y %H:%M",
        "%d/%m/%Y",
        "%m/%d/%Y %H:%M:%S",
        "%m/%d/%Y %H:%M",
        "%m/%d/%Y"
    ]
    
    for fmt in formats:
        try:
            return datetime.strptime(dt_str, fmt)
        except ValueError:
            continue
    
    raise ValueError(f"Could not parse datetime string: {dt_str}") 