"""
Утилиты для форматирования времени и дат
"""
from typing import Optional, Union
from datetime import datetime, timedelta
from enum import Enum

__all__ = (
    'format_duration',
    'format_retry_time',
    'format_timestamp',
    'format_relative_time',
    'parse_duration',
    'TimeFormat',
    'get_plural_form',
    'seconds_to_human',
    'time_until',
    'time_since',
    'format_date_range',
    'is_today',
    'is_yesterday',
    'is_tomorrow',
    'smart_date'
)


class TimeFormat(str, Enum):
    """Форматы времени"""
    FULL = 'full'  # 1 час 30 минут 45 секунд
    SHORT = 'short'  # 1ч 30м 45с
    COMPACT = 'compact'  # 1:30:45
    MINIMAL = 'minimal'  # 1ч 30м (без секунд если есть часы/минуты)


def get_plural_form(number: int, forms: tuple[str, str, str]) -> str:
    """
    Возвращает правильную форму множественного числа для русского языка.

    Args:
        number: Число
        forms: Кортеж форм (1 секунда, 2 секунды, 5 секунд)

    Returns:
        str: Правильная форма

    Example:
        >> get_plural_form(1, ('секунда', 'секунды', 'секунд'))
        'секунда'
        >> get_plural_form(2, ('секунда', 'секунды', 'секунд'))
        'секунды'
        >> get_plural_form(5, ('секунда', 'секунды', 'секунд'))
        'секунд'
    """
    n = abs(number)
    n %= 100

    if 5 <= n <= 20:
        return forms[2]

    n %= 10

    if n == 1:
        return forms[0]
    elif 2 <= n <= 4:
        return forms[1]
    else:
        return forms[2]


def format_duration(
        seconds: int,
        format_type: TimeFormat = TimeFormat.FULL,
        include_seconds: bool = True,
        max_units: Optional[int] = None
) -> str:
    """
    Форматирует длительность в читаемый вид.

    Args:
        seconds: Длительность в секундах
        format_type: Тип форматирования
        include_seconds: Включать секунды в вывод
        max_units: Максимальное количество единиц времени (например, только часы и минуты)

    Returns:
        str: Отформатированная строка

    Example:
        >> format_duration(3665)
        '1 час 1 минута 5 секунд'

        >> format_duration(3665, TimeFormat.SHORT)
        '1ч 1м 5с'

        >> format_duration(3665, TimeFormat.COMPACT)
        '1:01:05'

        >> format_duration(3665, max_units=2)
        '1 час 1 минута'
    """
    if seconds == 0:
        if format_type == TimeFormat.FULL:
            return "0 секунд"
        elif format_type == TimeFormat.SHORT:
            return "0с"
        elif format_type == TimeFormat.COMPACT:
            return "0:00"
        else:
            return "0с"

    # Разбиваем на единицы
    weeks, remainder = divmod(seconds, 604800)  # 7 * 24 * 60 * 60
    days, remainder = divmod(remainder, 86400)  # 24 * 60 * 60
    hours, remainder = divmod(remainder, 3600)
    minutes, secs = divmod(remainder, 60)

    # Компактный формат
    if format_type == TimeFormat.COMPACT:
        if weeks > 0:
            return f"{weeks * 7 + days}д {hours:02d}:{minutes:02d}:{secs:02d}"
        elif days > 0:
            return f"{days}д {hours:02d}:{minutes:02d}:{secs:02d}"
        elif hours > 0:
            return f"{hours}:{minutes:02d}:{secs:02d}"
        elif minutes > 0:
            return f"{minutes}:{secs:02d}"
        else:
            return f"0:{secs:02d}"

    # Собираем части
    parts = []
    units_count = 0

    # Недели
    if weeks > 0:
        if format_type == TimeFormat.SHORT:
            parts.append(f"{weeks}нед")
        else:
            week_form = get_plural_form(weeks, ('неделя', 'недели', 'недель'))
            parts.append(f"{weeks} {week_form}")
        units_count += 1
        if max_units and units_count >= max_units:
            return ' '.join(parts)

    # Дни
    if days > 0:
        if format_type == TimeFormat.SHORT:
            parts.append(f"{days}д")
        else:
            day_form = get_plural_form(days, ('день', 'дня', 'дней'))
            parts.append(f"{days} {day_form}")
        units_count += 1
        if max_units and units_count >= max_units:
            return ' '.join(parts)

    # Часы
    if hours > 0:
        if format_type == TimeFormat.SHORT:
            parts.append(f"{hours}ч")
        else:
            hour_form = get_plural_form(hours, ('час', 'часа', 'часов'))
            parts.append(f"{hours} {hour_form}")
        units_count += 1
        if max_units and units_count >= max_units:
            return ' '.join(parts)

    # Минуты
    if minutes > 0:
        if format_type == TimeFormat.SHORT:
            parts.append(f"{minutes}м")
        else:
            minute_form = get_plural_form(minutes, ('минута', 'минуты', 'минут'))
            parts.append(f"{minutes} {minute_form}")
        units_count += 1
        if max_units and units_count >= max_units:
            return ' '.join(parts)

    # Секунды
    if secs > 0 and include_seconds:
        # Минимальный формат: не показываем секунды если есть часы или дни
        if format_type == TimeFormat.MINIMAL and (hours > 0 or days > 0 or weeks > 0):
            pass
        else:
            if format_type == TimeFormat.SHORT:
                parts.append(f"{secs}с")
            else:
                second_form = get_plural_form(secs, ('секунда', 'секунды', 'секунд'))
                parts.append(f"{secs} {second_form}")

    return ' '.join(parts) if parts else "0 секунд"


def format_retry_time(retry_after: int, format_type: TimeFormat = TimeFormat.FULL) -> str:
    """
    Форматирует время повторной попытки.

    Args:
        retry_after: Время в секундах до следующей попытки
        format_type: Тип форматирования

    Returns:
        str: Отформатированная строка

    Example:
        >> format_retry_time(3665)
        '1 час 1 минута 5 секунд'

        >> format_retry_time(3665, TimeFormat.SHORT)
        '1ч 1м 5с'
    """
    return format_duration(retry_after, format_type=format_type)


def format_timestamp(
        timestamp: Union[int, float, datetime],
        format_string: str = "%d.%m.%Y %H:%M:%S",
        timezone_offset: Optional[int] = None
) -> str:
    """
    Форматирует timestamp в читаемую дату.

    Args:
        timestamp: Unix timestamp или datetime объект
        format_string: Формат вывода
        timezone_offset: Смещение часового пояса в часах

    Returns:
        str: Отформатированная дата

    Example:
        >> format_timestamp(1640000000)
        '20.12.2021 13:33:20'

        >> format_timestamp(datetime.now(), "%d %B %Y")
        '17 февраля 2026'
    """
    if isinstance(timestamp, (int, float)):
        dt = datetime.fromtimestamp(timestamp)
    else:
        dt = timestamp

    # Применяем смещение часового пояса
    if timezone_offset is not None:
        dt = dt + timedelta(hours=timezone_offset)

    return dt.strftime(format_string)


def format_relative_time(
        timestamp: Union[int, float, datetime],
        now: Optional[datetime] = None,
        detailed: bool = False
) -> str:
    """
    Форматирует время относительно текущего момента.

    Args:
        timestamp: Unix timestamp или datetime объект
        now: Текущее время (по умолчанию datetime.now())
        detailed: Детальный формат (например "2 часа 30 минут назад" вместо "2 часа назад")

    Returns:
        str: Относительное время

    Example:
        >> format_relative_time(time.time() - 3600)
        '1 час назад'

        >> format_relative_time(time.time() + 7200)
        'через 2 часа'

        >> format_relative_time(time.time() - 90, detailed=True)
        '1 минута 30 секунд назад'
    """
    if now is None:
        now = datetime.now()

    if isinstance(timestamp, (int, float)):
        dt = datetime.fromtimestamp(timestamp)
    else:
        dt = timestamp

    # Вычисляем разницу
    delta = now - dt
    is_past = delta.total_seconds() > 0

    seconds = abs(int(delta.total_seconds()))

    # Если меньше минуты
    if seconds < 60:
        if is_past:
            return "только что"
        else:
            return "сейчас"

    # Форматируем длительность
    if detailed:
        duration = format_duration(seconds, TimeFormat.FULL, max_units=2)
    else:
        duration = format_duration(seconds, TimeFormat.FULL, max_units=1)

    if is_past:
        return f"{duration} назад"
    else:
        return f"через {duration}"


def parse_duration(duration_str: str) -> Optional[int]:
    """
    Парсит строку длительности в секунды.

    Args:
        duration_str: Строка длительности (например "1ч 30м", "2h 15m", "90s")

    Returns:
        Optional[int]: Длительность в секундах или None если не удалось распарсить

    Example:
        >> parse_duration("1ч 30м")
        5400

        >> parse_duration("2h 15m 30s")
        8130

        >> parse_duration("90s")
        90
    """
    import re

    # Паттерны для разных единиц
    patterns = {
        'weeks': r'(\d+)\s*(?:нед|w|week|weeks)',
        'days': r'(\d+)\s*(?:д|d|day|days)',
        'hours': r'(\d+)\s*(?:ч|h|hour|hours)',
        'minutes': r'(\d+)\s*(?:м|m|min|minutes)',
        'seconds': r'(\d+)\s*(?:с|s|sec|seconds)'
    }

    total_seconds = 0

    # Ищем каждую единицу
    for unit, pattern in patterns.items():
        match = re.search(pattern, duration_str, re.IGNORECASE)
        if match:
            value = int(match.group(1))

            if unit == 'weeks':
                total_seconds += value * 604800
            elif unit == 'days':
                total_seconds += value * 86400
            elif unit == 'hours':
                total_seconds += value * 3600
            elif unit == 'minutes':
                total_seconds += value * 60
            elif unit == 'seconds':
                total_seconds += value

    return total_seconds if total_seconds > 0 else None


# ================= ДОПОЛНИТЕЛЬНЫЕ УТИЛИТЫ =================

def seconds_to_human(seconds: int) -> str:
    """
    Преобразует секунды в человекопонятный формат (самая большая единица).

    Args:
        seconds: Количество секунд

    Returns:
        str: Человекопонятный формат

    Example:
        >> seconds_to_human(3600)
        '1 час'

        >> seconds_to_human(90)
        '1.5 минуты'
    """
    if seconds >= 604800:  # Неделя
        weeks = seconds / 604800
        week_form = get_plural_form(int(weeks), ('неделя', 'недели', 'недель'))
        return f"{weeks:.1f} {week_form}".replace('.0', '')
    elif seconds >= 86400:  # День
        days = seconds / 86400
        day_form = get_plural_form(int(days), ('день', 'дня', 'дней'))
        return f"{days:.1f} {day_form}".replace('.0', '')
    elif seconds >= 3600:  # Час
        hours = seconds / 3600
        hour_form = get_plural_form(int(hours), ('час', 'часа', 'часов'))
        return f"{hours:.1f} {hour_form}".replace('.0', '')
    elif seconds >= 60:  # Минута
        minutes = seconds / 60
        minute_form = get_plural_form(int(minutes), ('минута', 'минуты', 'минут'))
        return f"{minutes:.1f} {minute_form}".replace('.0', '')
    else:  # Секунда
        second_form = get_plural_form(seconds, ('секунда', 'секунды', 'секунд'))
        return f"{seconds} {second_form}"


def time_until(target_time: datetime, format_type: TimeFormat = TimeFormat.FULL) -> str:
    """
    Возвращает время до указанного момента.

    Args:
        target_time: Целевое время
        format_type: Тип форматирования

    Returns:
        str: Отформатированное время

    Example:
        >> target = datetime.now() + timedelta(hours=2, minutes=30)
        >> time_until(target)
        '2 часа 30 минут'
    """
    now = datetime.now()
    delta = target_time - now

    if delta.total_seconds() <= 0:
        return "уже прошло"

    seconds = int(delta.total_seconds())
    return format_duration(seconds, format_type=format_type)


def time_since(start_time: datetime, format_type: TimeFormat = TimeFormat.FULL) -> str:
    """
    Возвращает время с указанного момента.

    Args:
        start_time: Начальное время
        format_type: Тип форматирования

    Returns:
        str: Отформатированное время

    Example:
        >> start = datetime.now() - timedelta(hours=1, minutes=15)
        >> time_since(start)
        '1 час 15 минут'
    """
    now = datetime.now()
    delta = now - start_time

    if delta.total_seconds() <= 0:
        return "еще не началось"

    seconds = int(delta.total_seconds())
    return format_duration(seconds, format_type=format_type)


def format_date_range(start: datetime, end: datetime) -> str:
    """
    Форматирует диапазон дат.

    Args:
        start: Начальная дата
        end: Конечная дата

    Returns:
        str: Отформатированный диапазон

    Example:
        >> start = datetime(2026, 2, 17, 10, 0)
        >> end = datetime(2026, 2, 17, 18, 0)
        >> format_date_range(start, end)
        '17.02.2026 с 10:00 до 18:00'
    """
    if start.date() == end.date():
        # Один день
        return f"{start.strftime('%d.%m.%Y')} с {start.strftime('%H:%M')} до {end.strftime('%H:%M')}"
    else:
        # Разные дни
        return f"с {start.strftime('%d.%m.%Y %H:%M')} до {end.strftime('%d.%m.%Y %H:%M')}"


def is_today(dt: datetime) -> bool:
    """Проверяет, является ли дата сегодняшней"""
    return dt.date() == datetime.now().date()


def is_yesterday(dt: datetime) -> bool:
    """Проверяет, является ли дата вчерашней"""
    yesterday = datetime.now().date() - timedelta(days=1)
    return dt.date() == yesterday


def is_tomorrow(dt: datetime) -> bool:
    """Проверяет, является ли дата завтрашней"""
    tomorrow = datetime.now().date() + timedelta(days=1)
    return dt.date() == tomorrow


def smart_date(dt: datetime) -> str:
    """
    Умное форматирование даты (сегодня, вчера, завтра, или дата).

    Args:
        dt: Дата для форматирования

    Returns:
        str: Отформатированная дата

    Example:
        >> smart_date(datetime.now())
        'сегодня в 14:30'

        >> smart_date(datetime.now() - timedelta(days=1))
        'вчера в 20:15'
    """
    if is_today(dt):
        return f"сегодня в {dt.strftime('%H:%M')}"
    elif is_yesterday(dt):
        return f"вчера в {dt.strftime('%H:%M')}"
    elif is_tomorrow(dt):
        return f"завтра в {dt.strftime('%H:%M')}"
    else:
        # Если в этом году, не показываем год
        if dt.year == datetime.now().year:
            return dt.strftime('%d.%m в %H:%M')
        else:
            return dt.strftime('%d.%m.%Y в %H:%M')
