"""VideoShare 领域层单元测试 - video_share.py"""

from datetime import datetime, timedelta, timezone

import pytest

from packages.domain.video_share import (
    VideoShare,
    _hash_password,
    generate_share_token,
)


class TestGenerateShareToken:
    """generate_share_token 函数测试"""

    def test_default_length(self):
        token = generate_share_token()
        assert len(token) == 12

    def test_custom_length(self):
        token = generate_share_token(20)
        assert len(token) == 20

    def test_url_friendly_characters(self):
        """token 只包含 URL 友好的字符（没有 l, i, o, 0, 1 等易混字符）"""
        token = generate_share_token(100)
        # 不应包含易混淆字符
        assert "l" not in token
        assert "i" not in token
        assert "o" not in token
        assert "0" not in token
        assert "1" not in token

    def test_randomness(self):
        """两次生成的 token 不同（概率上）"""
        tokens = {generate_share_token() for _ in range(100)}
        # 100 次应该几乎不可能重复
        assert len(tokens) > 95


class TestHashPassword:
    """_hash_password 函数测试"""

    def test_empty_password_returns_empty(self):
        assert _hash_password("") == ""

    def test_none_password_returns_empty(self):
        assert _hash_password(None) == ""  # type: ignore

    def test_hash_is_deterministic(self):
        """相同密码哈希结果相同"""
        h1 = _hash_password("mypassword")
        h2 = _hash_password("mypassword")
        assert h1 == h2

    def test_hash_differs_for_different_passwords(self):
        """不同密码哈希结果不同"""
        h1 = _hash_password("password1")
        h2 = _hash_password("password2")
        assert h1 != h2

    def test_hash_is_hex_string(self):
        """哈希是 64 位十六进制字符串（SHA-256）"""
        h = _hash_password("test")
        assert len(h) == 64
        int(h, 16)  # 应该能解析为十六进制

    def test_hash_includes_salt(self):
        """加盐后与直接 SHA-256 不同"""
        from hashlib import sha256

        direct = sha256("mypass".encode()).hexdigest()
        salted = _hash_password("mypass")
        assert direct != salted


class TestVideoShareCreate:
    """VideoShare.create 工厂方法测试"""

    def test_create_basic(self):
        share = VideoShare.create(video_id="video-1", user_id="user-1")
        assert share.id
        assert len(share.id) == 32
        assert share.video_id == "video-1"
        assert share.user_id == "user-1"
        assert share.share_token
        assert len(share.share_token) == 12
        assert share.password_hash is None
        assert share.expires_at is None
        assert share.view_count == 0
        assert share.download_count == 0
        assert share.is_active is True
        assert share.created_at is not None
        assert share.updated_at is not None

    def test_create_empty_video_id_raises(self):
        with pytest.raises(ValueError, match="video_id cannot be empty"):
            VideoShare.create(video_id="  ", user_id="u1")

    def test_create_empty_user_id_raises(self):
        with pytest.raises(ValueError, match="user_id cannot be empty"):
            VideoShare.create(video_id="v1", user_id="  ")

    def test_create_with_password(self):
        share = VideoShare.create(video_id="v1", user_id="u1", password="secret123")
        assert share.password_hash is not None
        assert share.password_hash != "secret123"  # 已哈希
        assert len(share.password_hash) == 64  # SHA-256 hex

    def test_create_with_expires_at(self):
        future = datetime.now(timezone.utc) + timedelta(days=7)
        share = VideoShare.create(video_id="v1", user_id="u1", expires_at=future)
        assert share.expires_at == future

    def test_create_past_expires_at_raises(self):
        past = datetime.now(timezone.utc) - timedelta(days=1)
        with pytest.raises(ValueError, match="expires_at cannot be in the past"):
            VideoShare.create(video_id="v1", user_id="u1", expires_at=past)

    def test_create_fields_stripped(self):
        share = VideoShare.create(video_id="  v1  ", user_id="  u1  ")
        assert share.video_id == "v1"
        assert share.user_id == "u1"

    def test_unique_tokens(self):
        """不同分享有不同的 token"""
        shares = [VideoShare.create(video_id="v1", user_id="u1") for _ in range(20)]
        tokens = [s.share_token for s in shares]
        assert len(set(tokens)) == 20


class TestVideoShareProperties:
    """属性测试"""

    def test_has_password_true(self):
        share = VideoShare.create(video_id="v1", user_id="u1", password="secret")
        assert share.has_password is True

    def test_has_password_false(self):
        share = VideoShare.create(video_id="v1", user_id="u1")
        assert share.has_password is False

    def test_is_expired_no_expiry(self):
        share = VideoShare.create(video_id="v1", user_id="u1")
        assert share.is_expired is False

    def test_is_expired_future_expiry(self):
        future = datetime.now(timezone.utc) + timedelta(days=7)
        share = VideoShare.create(video_id="v1", user_id="u1", expires_at=future)
        assert share.is_expired is False

    def test_is_expired_past_expiry(self):
        # 直接设置 expires_at 为过去时间（绕过 create 的校验）
        share = VideoShare.create(video_id="v1", user_id="u1")
        share.expires_at = datetime.now(timezone.utc) - timedelta(days=1)
        assert share.is_expired is True

    def test_is_accessible_active_not_expired(self):
        share = VideoShare.create(video_id="v1", user_id="u1")
        assert share.is_accessible is True

    def test_is_accessible_inactive(self):
        share = VideoShare.create(video_id="v1", user_id="u1")
        share.is_active = False
        assert share.is_accessible is False

    def test_is_accessible_expired(self):
        share = VideoShare.create(video_id="v1", user_id="u1")
        share.expires_at = datetime.now(timezone.utc) - timedelta(days=1)
        assert share.is_accessible is False

    def test_is_accessible_inactive_and_expired(self):
        share = VideoShare.create(video_id="v1", user_id="u1")
        share.is_active = False
        share.expires_at = datetime.now(timezone.utc) - timedelta(days=1)
        assert share.is_accessible is False


class TestVideoSharePassword:
    """密码验证测试"""

    def test_verify_password_correct(self):
        share = VideoShare.create(video_id="v1", user_id="u1", password="secret123")
        assert share.verify_password("secret123") is True

    def test_verify_password_wrong(self):
        share = VideoShare.create(video_id="v1", user_id="u1", password="secret123")
        assert share.verify_password("wrongpass") is False

    def test_verify_no_password_always_true(self):
        """没有设置密码时，任何密码都通过（包括空密码）"""
        share = VideoShare.create(video_id="v1", user_id="u1")
        assert share.verify_password("") is True
        assert share.verify_password("anything") is True

    def test_verify_empty_password_with_password_set(self):
        """有密码时，空密码不通过"""
        share = VideoShare.create(video_id="v1", user_id="u1", password="secret")
        assert share.verify_password("") is False


class TestVideoShareCounters:
    """计数方法测试"""

    def test_increment_view_count(self):
        share = VideoShare.create(video_id="v1", user_id="u1")
        assert share.view_count == 0
        share.increment_view_count()
        assert share.view_count == 1
        share.increment_view_count()
        assert share.view_count == 2

    def test_increment_download_count(self):
        share = VideoShare.create(video_id="v1", user_id="u1")
        assert share.download_count == 0
        share.increment_download_count()
        assert share.download_count == 1

    def test_revoke(self):
        share = VideoShare.create(video_id="v1", user_id="u1")
        assert share.is_active is True
        share.revoke()
        assert share.is_active is False
        assert share.is_accessible is False


class TestGenerateShareTokenExtended:
    """generate_share_token 深度补充测试"""

    def test_zero_length(self):
        token = generate_share_token(0)
        assert token == ""

    def test_length_one(self):
        token = generate_share_token(1)
        assert len(token) == 1

    def test_very_long_token(self):
        token = generate_share_token(100)
        assert len(token) == 100

    def test_no_special_characters(self):
        token = generate_share_token(50)
        assert token.isalnum()

    def test_all_characters_from_alphabet(self):
        alphabet = set("abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789")
        token = generate_share_token(200)
        for c in token:
            assert c in alphabet


class TestVideoShareExtended:
    """VideoShare 深度补充测试"""

    def test_zero_view_count(self):
        share = VideoShare.create(video_id="v1", user_id="u1")
        assert share.view_count == 0

    def test_zero_download_count(self):
        share = VideoShare.create(video_id="v1", user_id="u1")
        assert share.download_count == 0

    def test_large_view_count(self):
        share = VideoShare.create(video_id="v1", user_id="u1")
        for _ in range(1000):
            share.increment_view_count()
        assert share.view_count == 1000

    def test_revoke_idempotent(self):
        share = VideoShare.create(video_id="v1", user_id="u1")
        share.revoke()
        assert share.is_active is False
        share.revoke()
        assert share.is_active is False

    def test_revoke_returns_none(self):
        share = VideoShare.create(video_id="v1", user_id="u1")
        result = share.revoke()
        assert result is None

    def test_increment_view_returns_none(self):
        share = VideoShare.create(video_id="v1", user_id="u1")
        result = share.increment_view_count()
        assert result is None

    def test_increment_download_returns_none(self):
        share = VideoShare.create(video_id="v1", user_id="u1")
        result = share.increment_download_count()
        assert result is None

    def test_id_is_hex(self):
        share = VideoShare.create(video_id="v1", user_id="u1")
        int(share.id, 16)

    def test_ids_are_unique(self):
        s1 = VideoShare.create(video_id="v1", user_id="u1")
        s2 = VideoShare.create(video_id="v1", user_id="u1")
        assert s1.id != s2.id

    def test_expires_at_boundary_exact_now(self):
        """expires_at 恰好是现在，应该被认为过期"""
        share = VideoShare.create(video_id="v1", user_id="u1")
        share.expires_at = datetime.now(timezone.utc) - timedelta(microseconds=1)
        assert share.is_expired is True

    def test_expires_at_boundary_one_second_future(self):
        share = VideoShare.create(video_id="v1", user_id="u1")
        share.expires_at = datetime.now(timezone.utc) + timedelta(seconds=1)
        assert share.is_expired is False

    def test_password_with_special_characters(self):
        special_pass = "pass!@#$%^&*()"
        share = VideoShare.create(video_id="v1", user_id="u1", password=special_pass)
        assert share.verify_password(special_pass) is True
        assert share.verify_password("wrong") is False

    def test_password_unicode(self):
        unicode_pass = "密码🔐测试"
        share = VideoShare.create(video_id="v1", user_id="u1", password=unicode_pass)
        assert share.verify_password(unicode_pass) is True
