"""
VerificationCode 验证码领域模型单元测试
"""

from datetime import datetime, timedelta, timezone

import pytest
from domain.verification_code import VerificationCode


class TestVerificationCodeCreate:
    """创建验证码测试"""

    def test_create_default_6digit_code(self):
        vc = VerificationCode.create("test@example.com", "email_bind")
        assert vc.id is not None
        assert len(vc.id) == 32  # uuid4 hex
        assert vc.recipient == "test@example.com"
        assert vc.code_type == "email_bind"
        assert len(vc.code) == 6
        assert vc.code.isdigit()
        assert vc.used_at is None
        assert vc.attempts == 0

    def test_create_custom_code(self):
        vc = VerificationCode.create("13800138000", "phone_bind", custom_code="123456")
        assert vc.code == "123456"

    def test_create_default_ttl_300s(self):
        before = datetime.now(timezone.utc)
        vc = VerificationCode.create("test@example.com", "email_login")
        after = datetime.now(timezone.utc)
        expected_expiry_min = before + timedelta(seconds=300)
        expected_expiry_max = after + timedelta(seconds=300)
        assert expected_expiry_min <= vc.expires_at <= expected_expiry_max

    def test_create_custom_ttl(self):
        vc = VerificationCode.create("test@example.com", "reset_password", ttl_seconds=60)
        expected = datetime.now(timezone.utc) + timedelta(seconds=60)
        diff = abs((vc.expires_at - expected).total_seconds())
        assert diff < 2

    def test_create_recipient_stripped(self):
        vc = VerificationCode.create("  test@example.com  ", "email_bind")
        assert vc.recipient == "test@example.com"

    def test_create_phone_recipient(self):
        vc = VerificationCode.create("13800138000", "phone_login")
        assert vc.recipient == "13800138000"
        assert vc.code_type == "phone_login"

    def test_create_sets_created_at(self):
        vc = VerificationCode.create("test@example.com", "email_bind")
        assert vc.created_at is not None
        assert isinstance(vc.created_at, datetime)


class TestVerificationCodeExpiry:
    """过期状态测试"""

    def test_fresh_code_not_expired(self):
        vc = VerificationCode.create("test@example.com", "email_bind")
        assert vc.is_expired is False

    def test_expired_code_is_expired(self):
        vc = VerificationCode.create("test@example.com", "email_bind", ttl_seconds=-60)
        assert vc.is_expired is True

    def test_boundary_not_expired_at_expiry_time(self):
        now = datetime.now(timezone.utc)
        vc = VerificationCode.create("test@example.com", "email_bind")
        vc.expires_at = now + timedelta(seconds=1)
        assert vc.is_expired is False

    def test_boundary_expired_right_after(self):
        vc = VerificationCode.create("test@example.com", "email_bind")
        vc.expires_at = datetime.now(timezone.utc) - timedelta(microseconds=1)
        assert vc.is_expired is True


class TestVerificationCodeUsed:
    """使用状态测试"""

    def test_fresh_code_not_used(self):
        vc = VerificationCode.create("test@example.com", "email_bind")
        assert vc.is_used is False

    def test_mark_used(self):
        vc = VerificationCode.create("test@example.com", "email_bind")
        vc.mark_used()
        assert vc.is_used is True
        assert vc.used_at is not None
        assert isinstance(vc.used_at, datetime)

    def test_mark_used_sets_recent_time(self):
        vc = VerificationCode.create("test@example.com", "email_bind")
        before = datetime.now(timezone.utc)
        vc.mark_used()
        after = datetime.now(timezone.utc)
        assert before <= vc.used_at <= after

    def test_mark_used_idempotent(self):
        vc = VerificationCode.create("test@example.com", "email_bind")
        vc.mark_used()
        first_used_at = vc.used_at
        vc.mark_used()
        # 第二次会更新时间
        assert vc.used_at >= first_used_at


class TestVerificationCodeValidity:
    """有效性（未过期+未使用）测试"""

    def test_fresh_code_is_valid(self):
        vc = VerificationCode.create("test@example.com", "email_bind")
        assert vc.is_valid is True

    def test_expired_code_not_valid(self):
        vc = VerificationCode.create("test@example.com", "email_bind", ttl_seconds=-10)
        assert vc.is_valid is False

    def test_used_code_not_valid(self):
        vc = VerificationCode.create("test@example.com", "email_bind")
        vc.mark_used()
        assert vc.is_valid is False

    def test_expired_and_used_not_valid(self):
        vc = VerificationCode.create("test@example.com", "email_bind", ttl_seconds=-10)
        vc.mark_used()
        assert vc.is_valid is False


class TestVerificationCodeAttempts:
    """尝试次数测试"""

    def test_initial_attempts_zero(self):
        vc = VerificationCode.create("test@example.com", "email_bind")
        assert vc.attempts == 0

    def test_increment_attempts(self):
        vc = VerificationCode.create("test@example.com", "email_bind")
        vc.increment_attempts()
        assert vc.attempts == 1

    def test_increment_attempts_multiple(self):
        vc = VerificationCode.create("test@example.com", "email_bind")
        for _ in range(5):
            vc.increment_attempts()
        assert vc.attempts == 5


class TestVerificationCodeTypes:
    """不同验证码类型测试"""

    @pytest.mark.parametrize(
        "code_type",
        [
            "email_bind",
            "phone_bind",
            "email_login",
            "phone_login",
            "reset_password",
        ],
    )
    def test_all_supported_types(self, code_type):
        vc = VerificationCode.create("test@example.com", code_type)
        assert vc.code_type == code_type
        assert vc.is_valid is True


class TestVerificationCodeExtended:
    """VerificationCode 深度补充测试"""

    def test_id_is_hex(self):
        vc = VerificationCode.create("test@example.com", "email_bind")
        int(vc.id, 16)

    def test_ids_are_unique(self):
        vc1 = VerificationCode.create("test@example.com", "email_bind")
        vc2 = VerificationCode.create("test@example.com", "email_bind")
        assert vc1.id != vc2.id

    def test_slots_no_extra_attrs(self):
        import pytest

        vc = VerificationCode.create("test@example.com", "email_bind")
        with pytest.raises(AttributeError):
            vc.new_field = "value"  # type: ignore[attr-defined]

    def test_custom_code_non_numeric(self):
        """自定义 code 可以是非数字"""
        vc = VerificationCode.create("test@example.com", "email_bind", custom_code="abcdef")
        assert vc.code == "abcdef"

    def test_custom_code_short(self):
        vc = VerificationCode.create("test@example.com", "email_bind", custom_code="12")
        assert vc.code == "12"

    def test_custom_code_long(self):
        long_code = "1" * 20
        vc = VerificationCode.create("test@example.com", "email_bind", custom_code=long_code)
        assert vc.code == long_code
        assert len(vc.code) == 20

    def test_ttl_zero_expires_immediately(self):
        vc = VerificationCode.create("test@example.com", "email_bind", ttl_seconds=0)
        # ttl=0 时 expires_at = now，可能已过期或刚好
        assert vc.expires_at is not None

    def test_very_long_ttl(self):
        vc = VerificationCode.create("test@example.com", "email_bind", ttl_seconds=86400)
        assert vc.is_expired is False

    def test_negative_ttl_expired(self):
        vc = VerificationCode.create("test@example.com", "email_bind", ttl_seconds=-100)
        assert vc.is_expired is True

    def test_attempts_starts_at_zero(self):
        vc = VerificationCode.create("test@example.com", "email_bind")
        assert vc.attempts == 0

    def test_increment_attempts_returns_none(self):
        vc = VerificationCode.create("test@example.com", "email_bind")
        result = vc.increment_attempts()
        assert result is None
        assert vc.attempts == 1

    def test_mark_used_returns_none(self):
        vc = VerificationCode.create("test@example.com", "email_bind")
        result = vc.mark_used()
        assert result is None
        assert vc.is_used is True

    def test_code_type_empty_string(self):
        vc = VerificationCode.create("test@example.com", "")
        assert vc.code_type == ""

    def test_recipient_empty_string(self):
        vc = VerificationCode.create("", "email_bind")
        assert vc.recipient == ""

    def test_created_at_equals_expires_minus_ttl(self):
        vc = VerificationCode.create("test@example.com", "email_bind", ttl_seconds=60)
        diff = (vc.expires_at - vc.created_at).total_seconds()
        assert abs(diff - 60) < 2

    def test_multiple_mark_used_updates_time(self):
        vc = VerificationCode.create("test@example.com", "email_bind")
        vc.mark_used()
        first = vc.used_at
        vc.mark_used()
        second = vc.used_at
        assert second >= first
