"""
Deep behavioral tests for core/utils.py pure logic.
Tests date string generation, ID generation,
UTC time handling, and hours_ago calculation.
"""

from datetime import datetime, timezone


# --- get_local_date_string timezone handling ---


class TestLocalDateStringTimezoneHandling:
    """Tests for get_local_date_string timezone logic."""

    def _get_timezone_source(self, settings_manager, env_tz):
        if settings_manager:
            tz = settings_manager.get("app.timezone")
            if tz:
                return tz
        if env_tz:
            return env_tz
        return "UTC"

    def test_from_settings_manager(self):
        manager = {"app.timezone": "America/New_York"}
        result = self._get_timezone_source(manager, None)
        assert result == "America/New_York"

    def test_from_env_when_no_settings(self):
        result = self._get_timezone_source(None, "Europe/London")
        assert result == "Europe/London"

    def test_default_utc(self):
        result = self._get_timezone_source(None, None)
        assert result == "UTC"

    def test_settings_takes_precedence(self):
        manager = {"app.timezone": "Asia/Tokyo"}
        result = self._get_timezone_source(manager, "Europe/Paris")
        assert result == "Asia/Tokyo"


class TestLocalDateStringFormat:
    """Tests for date string format."""

    def _format_date(self, dt):
        return dt.date().isoformat()

    def test_iso_format(self):
        dt = datetime(2025, 6, 15, 10, 30, 0, tzinfo=timezone.utc)
        result = self._format_date(dt)
        assert result == "2025-06-15"

    def test_single_digit_month(self):
        dt = datetime(2025, 1, 5, 10, 30, 0, tzinfo=timezone.utc)
        result = self._format_date(dt)
        assert result == "2025-01-05"

    def test_double_digit_day(self):
        dt = datetime(2025, 12, 31, 10, 30, 0, tzinfo=timezone.utc)
        result = self._format_date(dt)
        assert result == "2025-12-31"


class TestTimezoneValidation:
    """Tests for timezone validation fallback."""

    def _is_valid_timezone(self, tz_name):
        try:
            from zoneinfo import ZoneInfo

            ZoneInfo(tz_name)
            return True
        except Exception:
            return False

    def test_valid_timezone(self):
        assert self._is_valid_timezone("UTC") is True
        assert self._is_valid_timezone("America/New_York") is True

    def test_invalid_timezone(self):
        assert self._is_valid_timezone("Invalid/Timezone") is False
        assert self._is_valid_timezone("") is False


# --- generate_card_id ---


class TestGenerateCardId:
    """Tests for generate_card_id UUID generation."""

    def _is_valid_uuid(self, value):
        import re

        pattern = (
            r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
        )
        return bool(re.match(pattern, value.lower()))

    def test_returns_string(self):
        import uuid

        card_id = str(uuid.uuid4())
        assert isinstance(card_id, str)

    def test_valid_uuid_format(self):
        import uuid

        card_id = str(uuid.uuid4())
        assert self._is_valid_uuid(card_id) is True

    def test_unique_each_call(self):
        import uuid

        id1 = str(uuid.uuid4())
        id2 = str(uuid.uuid4())
        assert id1 != id2

    def test_uuid_length(self):
        import uuid

        card_id = str(uuid.uuid4())
        assert len(card_id) == 36  # UUID with hyphens


# --- generate_subscription_id ---


class TestGenerateSubscriptionId:
    """Tests for generate_subscription_id UUID generation."""

    def _is_valid_uuid(self, value):
        import re

        pattern = (
            r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
        )
        return bool(re.match(pattern, value.lower()))

    def test_returns_string(self):
        import uuid

        sub_id = str(uuid.uuid4())
        assert isinstance(sub_id, str)

    def test_valid_uuid_format(self):
        import uuid

        sub_id = str(uuid.uuid4())
        assert self._is_valid_uuid(sub_id) is True


# --- utc_now ---


class TestUtcNow:
    """Tests for utc_now function."""

    def _utc_now(self):
        return datetime.now(timezone.utc)

    def test_returns_datetime(self):
        result = self._utc_now()
        assert isinstance(result, datetime)

    def test_has_timezone(self):
        result = self._utc_now()
        assert result.tzinfo is not None

    def test_timezone_is_utc(self):
        result = self._utc_now()
        assert result.tzinfo == timezone.utc

    def test_close_to_now(self):
        before = datetime.now(timezone.utc)
        result = self._utc_now()
        after = datetime.now(timezone.utc)
        assert before <= result <= after


# --- hours_ago ---


class TestHoursAgo:
    """Tests for hours_ago calculation."""

    def _hours_ago(self, dt, now=None):
        if now is None:
            now = datetime.now(timezone.utc)
        if dt.tzinfo is None:
            dt = dt.replace(tzinfo=timezone.utc)
        delta = now - dt
        return delta.total_seconds() / 3600

    def test_one_hour_ago(self):
        now = datetime(2025, 6, 15, 11, 0, 0, tzinfo=timezone.utc)
        dt = datetime(2025, 6, 15, 10, 0, 0, tzinfo=timezone.utc)
        result = self._hours_ago(dt, now)
        assert abs(result - 1.0) < 0.01

    def test_24_hours_ago(self):
        now = datetime(2025, 6, 15, 10, 0, 0, tzinfo=timezone.utc)
        dt = datetime(2025, 6, 14, 10, 0, 0, tzinfo=timezone.utc)
        result = self._hours_ago(dt, now)
        assert abs(result - 24.0) < 0.01

    def test_half_hour_ago(self):
        now = datetime(2025, 6, 15, 10, 30, 0, tzinfo=timezone.utc)
        dt = datetime(2025, 6, 15, 10, 0, 0, tzinfo=timezone.utc)
        result = self._hours_ago(dt, now)
        assert abs(result - 0.5) < 0.01

    def test_future_is_negative(self):
        now = datetime(2025, 6, 15, 10, 0, 0, tzinfo=timezone.utc)
        dt = datetime(2025, 6, 15, 11, 0, 0, tzinfo=timezone.utc)
        result = self._hours_ago(dt, now)
        assert result < 0

    def test_same_time_is_zero(self):
        now = datetime(2025, 6, 15, 10, 0, 0, tzinfo=timezone.utc)
        result = self._hours_ago(now, now)
        assert abs(result) < 0.01


class TestHoursAgoNaiveDatetime:
    """Tests for hours_ago with naive datetime handling."""

    def _hours_ago_with_tz_handling(self, dt, now):
        if dt.tzinfo is None:
            dt = dt.replace(tzinfo=timezone.utc)
        delta = now - dt
        return delta.total_seconds() / 3600

    def test_naive_datetime_treated_as_utc(self):
        now = datetime(2025, 6, 15, 11, 0, 0, tzinfo=timezone.utc)
        dt_naive = datetime(2025, 6, 15, 10, 0, 0)  # No timezone
        result = self._hours_ago_with_tz_handling(dt_naive, now)
        assert abs(result - 1.0) < 0.01


# --- Environment variable handling ---


class TestEnvVariableHandling:
    """Tests for TZ environment variable handling."""

    def _get_tz_from_env(self, env_vars):
        return env_vars.get("TZ")

    def test_tz_present(self):
        env = {"TZ": "America/Chicago"}
        result = self._get_tz_from_env(env)
        assert result == "America/Chicago"

    def test_tz_not_present(self):
        env = {}
        result = self._get_tz_from_env(env)
        assert result is None

    def test_tz_empty_string(self):
        env = {"TZ": ""}
        result = self._get_tz_from_env(env)
        assert result == ""


# --- Settings manager fallback ---


class TestSettingsManagerFallback:
    """Tests for settings manager error handling."""

    def _get_tz_from_settings(self, manager, key):
        try:
            if manager is not None:
                return manager.get(key)
        except Exception:
            pass
        return None

    def test_success(self):
        manager = {"app.timezone": "UTC"}
        result = self._get_tz_from_settings(manager, "app.timezone")
        assert result == "UTC"

    def test_key_missing(self):
        manager = {}
        result = self._get_tz_from_settings(manager, "app.timezone")
        assert result is None

    def test_manager_none(self):
        result = self._get_tz_from_settings(None, "app.timezone")
        assert result is None


# --- UUID string format ---


class TestUuidStringFormat:
    """Tests for UUID string format patterns."""

    def test_lowercase_format(self):
        import uuid

        result = str(uuid.uuid4())
        # UUID strings are lowercase
        assert result == result.lower()

    def test_hyphen_positions(self):
        import uuid

        result = str(uuid.uuid4())
        # Hyphens at positions 8, 13, 18, 23
        assert result[8] == "-"
        assert result[13] == "-"
        assert result[18] == "-"
        assert result[23] == "-"

    def test_hex_characters_only(self):
        import uuid

        result = str(uuid.uuid4())
        hex_chars = set("0123456789abcdef-")
        assert all(c in hex_chars for c in result)


# --- Datetime isoformat patterns ---


class TestDatetimeIsoformatPatterns:
    """Tests for datetime isoformat output."""

    def test_date_isoformat(self):
        dt = datetime(2025, 6, 15, tzinfo=timezone.utc)
        result = dt.date().isoformat()
        assert result == "2025-06-15"

    def test_datetime_isoformat(self):
        dt = datetime(2025, 6, 15, 10, 30, 0, tzinfo=timezone.utc)
        result = dt.isoformat()
        assert "2025-06-15" in result
        assert "10:30:00" in result

    def test_isoformat_includes_timezone(self):
        dt = datetime(2025, 6, 15, 10, 30, 0, tzinfo=timezone.utc)
        result = dt.isoformat()
        assert "+00:00" in result


# --- Total seconds calculation ---


class TestTotalSecondsCalculation:
    """Tests for timedelta total_seconds conversion."""

    def _to_hours(self, total_seconds):
        return total_seconds / 3600

    def test_one_hour(self):
        assert self._to_hours(3600) == 1.0

    def test_half_hour(self):
        assert self._to_hours(1800) == 0.5

    def test_24_hours(self):
        assert self._to_hours(86400) == 24.0

    def test_fractional_hours(self):
        # 90 minutes = 5400 seconds = 1.5 hours
        assert self._to_hours(5400) == 1.5
