# #region Test.AppTimezone [C:3] [TYPE Module] [SEMANTICS test,timezone]
# @BRIEF Unit tests for AppTimezone module — timezone utilities, caching, and localization.
# @RELATION BINDS_TO -> [AppTimezone]
# @TEST_EDGE: invalid_timezone -> validate_timezone returns False for unknown names
# @TEST_EDGE: none_datetime -> localize returns None for None input
# @TEST_EDGE: cache_invalidation -> invalidate clears cache, next call re-reads env
import pytest
from datetime import datetime
from zoneinfo import ZoneInfo

from src.core.timezone import (
    _get_default_tz_name,
    get_app_timezone,
    invalidate_timezone_cache,
    validate_timezone,
    localize,
    now,
    format_timezone_offset,
)


@pytest.fixture(autouse=True)
def _clean_cache():
    """Ensure cache is clean before and after each test."""
    invalidate_timezone_cache()
    yield
    invalidate_timezone_cache()


# #region test_get_default_tz_name_default [C:2] [TYPE Function]
# @BRIEF Without APP_TIMEZONE env var, returns "Europe/Moscow".
def test_get_default_tz_name_default(monkeypatch):
    monkeypatch.delenv("APP_TIMEZONE", raising=False)
    assert _get_default_tz_name() == "Europe/Moscow"
# #endregion test_get_default_tz_name_default


# #region test_get_default_tz_name_custom [C:2] [TYPE Function]
# @BRIEF With APP_TIMEZONE set, returns the custom value.
def test_get_default_tz_name_custom(monkeypatch):
    monkeypatch.setenv("APP_TIMEZONE", "US/Eastern")
    assert _get_default_tz_name() == "US/Eastern"
# #endregion test_get_default_tz_name_custom


# #region test_get_app_timezone_returns_zoneinfo [C:2] [TYPE Function]
# @BRIEF Returns a ZoneInfo instance for the configured timezone.
def test_get_app_timezone_returns_zoneinfo(monkeypatch):
    monkeypatch.setenv("APP_TIMEZONE", "Europe/Moscow")
    tz = get_app_timezone()
    assert isinstance(tz, ZoneInfo)
    assert tz.key == "Europe/Moscow"
# #endregion test_get_app_timezone_returns_zoneinfo


# #region test_get_app_timezone_cached [C:2] [TYPE Function]
# @BRIEF Two consecutive calls return the exact same object (identity).
def test_get_app_timezone_cached(monkeypatch):
    monkeypatch.setenv("APP_TIMEZONE", "Europe/Berlin")
    first = get_app_timezone()
    second = get_app_timezone()
    assert first is second
# #endregion test_get_app_timezone_cached


# #region test_invalidate_and_recreate_cycle [C:2] [TYPE Function]
# @BRIEF After invalidate, next call re-reads env and returns a new object.
def test_invalidate_and_recreate_cycle(monkeypatch):
    monkeypatch.setenv("APP_TIMEZONE", "Europe/Moscow")
    before = get_app_timezone()
    assert before.key == "Europe/Moscow"

    invalidate_timezone_cache()
    monkeypatch.setenv("APP_TIMEZONE", "US/Eastern")
    after = get_app_timezone()
    assert after.key == "US/Eastern"
    assert before is not after
# #endregion test_invalidate_and_recreate_cycle


# #region test_validate_timezone_valid [C:2] [TYPE Function]
# @BRIEF Known IANA timezone names return True.
@pytest.mark.parametrize("tz_name", ["Europe/Moscow", "UTC", "US/Eastern", "Asia/Tokyo"])
def test_validate_timezone_valid(tz_name):
    assert validate_timezone(tz_name) is True
# #endregion test_validate_timezone_valid


# #region test_validate_timezone_invalid [C:2] [TYPE Function]
# @BRIEF Unknown or garbage timezone names return False.
@pytest.mark.parametrize("tz_name", ["Invalid/Timezone", "NotATZ", "Foo/Bar/Baz"])
def test_validate_timezone_invalid(tz_name):
    assert validate_timezone(tz_name) is False
# #endregion test_validate_timezone_invalid


# #region test_validate_timezone_none [C:2] [TYPE Function]
# @BRIEF None input returns False (TypeError caught internally).
def test_validate_timezone_none():
    assert validate_timezone(None) is False
# #endregion test_validate_timezone_none


# #region test_validate_timezone_empty_string_raises [C:2] [TYPE Function]
# @BRIEF BUG: empty string raises ValueError — production code only catches KeyError/TypeError.
def test_validate_timezone_empty_string_raises():
    with pytest.raises(ValueError, match="normalized relative paths"):
        validate_timezone("")
# #endregion test_validate_timezone_empty_string_raises


# #region test_localize_naive_datetime [C:2] [TYPE Function]
# @BRIEF Naive UTC datetime is treated as UTC and converted to app timezone.
def test_localize_naive_datetime(monkeypatch):
    monkeypatch.setenv("APP_TIMEZONE", "Europe/Moscow")
    invalidate_timezone_cache()
    naive_utc = datetime(2024, 6, 15, 12, 0, 0)
    result = localize(naive_utc)
    # Hardcoded fixture: Moscow is UTC+3 in June (no DST)
    assert result.hour == 15
    assert result.tzinfo is not None
    assert result.tzinfo.key == "Europe/Moscow"
# #endregion test_localize_naive_datetime


# #region test_localize_aware_datetime [C:2] [TYPE Function]
# @BRIEF Aware datetime in a different timezone is converted to app timezone.
def test_localize_aware_datetime(monkeypatch):
    monkeypatch.setenv("APP_TIMEZONE", "Europe/Moscow")
    invalidate_timezone_cache()
    aware_utc = datetime(2024, 1, 15, 10, 0, 0, tzinfo=ZoneInfo("UTC"))
    result = localize(aware_utc)
    # Hardcoded fixture: Moscow is UTC+3 in January (no DST)
    assert result.hour == 13
    assert result.tzinfo.key == "Europe/Moscow"
# #endregion test_localize_aware_datetime


# #region test_localize_none [C:2] [TYPE Function]
# @BRIEF None input returns None without error.
def test_localize_none():
    assert localize(None) is None
# #endregion test_localize_none


# #region test_now_returns_aware [C:2] [TYPE Function]
# @BRIEF now() returns a timezone-aware datetime in the app timezone.
def test_now_returns_aware(monkeypatch):
    monkeypatch.setenv("APP_TIMEZONE", "UTC")
    invalidate_timezone_cache()
    result = now()
    assert result.tzinfo is not None
    assert result.tzinfo.key == "UTC"
# #endregion test_now_returns_aware


# #region test_format_timezone_offset [C:2] [TYPE Function]
# @BRIEF Returns formatted UTC offset string like "+03:00".
def test_format_timezone_offset(monkeypatch):
    monkeypatch.setenv("APP_TIMEZONE", "Europe/Moscow")
    invalidate_timezone_cache()
    offset = format_timezone_offset()
    # Hardcoded fixture: Moscow is always UTC+3
    assert offset == "+03:00"
# #endregion test_format_timezone_offset


# #region test_format_timezone_offset_utc [C:2] [TYPE Function]
# @BRIEF UTC timezone returns "+00:00".
def test_format_timezone_offset_utc(monkeypatch):
    monkeypatch.setenv("APP_TIMEZONE", "UTC")
    invalidate_timezone_cache()
    offset = format_timezone_offset()
    assert offset == "+00:00"
# #endregion test_format_timezone_offset_utc
# #endregion Test.AppTimezone
