"""InMemoryUserRepository 单元测试."""

import copy
from datetime import datetime, timezone

import pytest

from packages.adapters.in_memory.user_repository import InMemoryUserRepository
from packages.domain.entities import User


@pytest.fixture
def repo() -> InMemoryUserRepository:
    return InMemoryUserRepository()


@pytest.fixture
def sample_user() -> User:
    return User(
        id="user-1",
        email="Test@Example.com",
        display_name="Test User",
        username="testuser",
        password_hash="hashed-pw",
        email_verification_token="verify-token-123",
        password_reset_token="reset-token-456",
        wechat_openid="wx-openid-abc",
        wechat_unionid="wx-unionid-def",
        phone="13800138000",
        created_at=datetime.now(timezone.utc),
    )


class TestSaveAndFindById:
    def test_save_and_find_by_id(self, repo, sample_user):
        repo.save(sample_user)
        found = repo.find_by_id("user-1")
        assert found is not None
        assert found.id == "user-1"
        assert found.email == "Test@Example.com"

    def test_find_by_id_not_found(self, repo):
        assert repo.find_by_id("nonexistent") is None

    def test_save_overwrite_existing(self, repo, sample_user):
        repo.save(sample_user)
        sample_user.display_name = "Updated Name"
        repo.save(sample_user)

        found = repo.find_by_id("user-1")
        assert found.display_name == "Updated Name"


class TestFindByEmail:
    def test_find_by_email_case_insensitive(self, repo, sample_user):
        repo.save(sample_user)
        # 用不同大小写查找
        found = repo.find_by_email("test@example.com")
        assert found is not None
        assert found.id == "user-1"

    def test_find_by_email_exact_case(self, repo, sample_user):
        repo.save(sample_user)
        found = repo.find_by_email("Test@Example.com")
        assert found is not None

    def test_find_by_email_not_found(self, repo):
        assert repo.find_by_email("notfound@example.com") is None


class TestFindByUsername:
    def test_find_by_username_case_insensitive(self, repo, sample_user):
        repo.save(sample_user)
        found = repo.find_by_username("TESTUSER")
        assert found is not None
        assert found.id == "user-1"

    def test_find_by_username_not_found(self, repo):
        assert repo.find_by_username("nobody") is None

    def test_find_by_username_empty(self, repo, sample_user):
        sample_user.username = ""
        repo.save(sample_user)
        # 空 username 不应该建立索引，但查找空字符串应该返回None
        found = repo.find_by_username("")
        assert found is None


class TestFindByVerificationToken:
    def test_find_by_verification_token(self, repo, sample_user):
        repo.save(sample_user)
        found = repo.find_by_verification_token("verify-token-123")
        assert found is not None
        assert found.id == "user-1"

    def test_find_by_verification_token_not_found(self, repo):
        assert repo.find_by_verification_token("bad-token") is None


class TestFindByPasswordResetToken:
    def test_find_by_password_reset_token(self, repo, sample_user):
        repo.save(sample_user)
        found = repo.find_by_password_reset_token("reset-token-456")
        assert found is not None
        assert found.id == "user-1"

    def test_find_by_password_reset_token_not_found(self, repo):
        assert repo.find_by_password_reset_token("bad-token") is None


class TestFindByWechat:
    def test_find_by_wechat_openid(self, repo, sample_user):
        repo.save(sample_user)
        found = repo.find_by_wechat_openid("wx-openid-abc")
        assert found is not None
        assert found.id == "user-1"

    def test_find_by_wechat_openid_not_found(self, repo):
        assert repo.find_by_wechat_openid("bad-openid") is None

    def test_find_by_wechat_unionid(self, repo, sample_user):
        repo.save(sample_user)
        found = repo.find_by_wechat_unionid("wx-unionid-def")
        assert found is not None
        assert found.id == "user-1"

    def test_find_by_wechat_unionid_not_found(self, repo):
        assert repo.find_by_wechat_unionid("bad-unionid") is None

    def test_find_by_wechat_unionid_empty(self, repo, sample_user):
        sample_user.wechat_unionid = None
        repo.save(sample_user)
        assert repo.find_by_wechat_unionid("") is None


class TestFindByPhone:
    def test_find_by_phone(self, repo, sample_user):
        repo.save(sample_user)
        found = repo.find_by_phone("13800138000")
        assert found is not None
        assert found.id == "user-1"

    def test_find_by_phone_not_found(self, repo):
        assert repo.find_by_phone("13900139000") is None

    def test_find_by_phone_empty(self, repo, sample_user):
        sample_user.phone = None
        repo.save(sample_user)
        assert repo.find_by_phone("") is None


class TestDelete:
    def test_delete_existing_user(self, repo, sample_user):
        repo.save(sample_user)
        assert repo.delete("user-1") is True
        assert repo.find_by_id("user-1") is None

    def test_delete_cleans_all_indexes(self, repo, sample_user):
        repo.save(sample_user)
        repo.delete("user-1")

        assert repo.find_by_email("test@example.com") is None
        assert repo.find_by_username("testuser") is None
        assert repo.find_by_verification_token("verify-token-123") is None
        assert repo.find_by_password_reset_token("reset-token-456") is None

    def test_delete_nonexistent_user(self, repo):
        assert repo.delete("nonexistent") is False

    def test_delete_twice_returns_false(self, repo, sample_user):
        repo.save(sample_user)
        assert repo.delete("user-1") is True
        assert repo.delete("user-1") is False


class TestIndexUpdates:
    def test_save_new_user_with_same_email_raises_uniqueness_error(self, repo, sample_user):
        """不同用户同邮箱应触发唯一约束异常."""
        repo.save(sample_user)
        user2 = User(
            id="user-2",
            email="test@example.com",  # 同邮箱
            display_name="User 2",
            username="user2",
        )
        with pytest.raises(ValueError, match="Email already in use"):
            repo.save(user2)

    def test_same_user_update_email_allowed(self, repo, sample_user):
        """同一用户更新邮箱不触发唯一约束."""
        repo.save(sample_user)
        sample_user.email = "new@example.com"
        repo.save(sample_user)

        found = repo.find_by_email("new@example.com")
        assert found is not None
        assert found.id == "user-1"
        assert repo.find_by_email("test@example.com") is None

    def test_update_email_conflict_does_not_corrupt_indexes(self, repo, sample_user):
        """更新邮箱与其他用户冲突时，索引必须保持一致，旧邮箱索引不丢失."""
        repo.save(sample_user)
        # 第二个用户
        user2 = User(
            id="user-2",
            email="other@example.com",
            display_name="User 2",
            username="user2",
        )
        repo.save(user2)

        # 尝试把 user2 的邮箱改成 sample_user 的邮箱（冲突）
        user2_new = copy.deepcopy(user2)
        user2_new.email = "test@example.com"
        with pytest.raises(ValueError, match="Email already in use"):
            repo.save(user2_new)

        # 索引必须保持一致（user-1仍占test@example.com，user-2仍占other@example.com）
        assert repo.find_by_email("test@example.com").id == "user-1"
        assert repo.find_by_email("other@example.com").id == "user-2"
        assert repo.find_by_username("testuser").id == "user-1"
        assert repo.find_by_username("user2").id == "user-2"

    def test_update_username_conflict_does_not_corrupt_indexes(self, repo, sample_user):
        """更新用户名与他人冲突时，各索引保持一致不丢失."""
        repo.save(sample_user)
        user2 = User(
            id="user-2",
            email="other@example.com",
            display_name="User 2",
            username="user2",
            phone="13900000002",
        )
        repo.save(user2)

        # 尝试把 user2 用户名改成 testuser（冲突）
        user2_new = copy.deepcopy(user2)
        user2_new.username = "testuser"
        with pytest.raises(ValueError, match="Username already in use"):
            repo.save(user2_new)

        # 各索引必须保持一致
        assert repo.find_by_username("testuser").id == "user-1"
        assert repo.find_by_username("user2").id == "user-2"
        assert repo.find_by_email("other@example.com").id == "user-2"
        assert repo.find_by_phone("13900000002").id == "user-2"

    def test_save_stores_independent_copy(self, repo, sample_user):
        """save存储独立副本，外部修改对象不影响仓储内部状态."""
        repo.save(sample_user)

        # 外部修改对象属性
        original_email = sample_user.email
        sample_user.email = "hacked@example.com"
        sample_user.display_name = "Hacked"

        # 仓储中数据不应受影响
        stored = repo.find_by_id(sample_user.id)
        assert stored.email == original_email
        assert repo.find_by_email(original_email) is not None
        assert repo.find_by_email("hacked@example.com") is None

    def test_save_empty_email_raises(self, repo):
        """空邮箱应抛出异常."""
        user = User(
            id="user-empty",
            email="",
            display_name="Empty Email",
            username="emptyuser",
        )
        with pytest.raises(ValueError, match="email cannot be empty"):
            repo.save(user)

    def test_find_by_empty_email_returns_none(self, repo, sample_user):
        """空邮箱查询返回None."""
        repo.save(sample_user)
        assert repo.find_by_email("") is None
        assert repo.find_by_email(None) is None

    def test_find_by_empty_username_returns_none(self, repo, sample_user):
        """空用户名查询返回None."""
        repo.save(sample_user)
        assert repo.find_by_username("") is None

    def test_duplicate_verification_token_raises(self, repo, sample_user):
        """相同邮箱验证令牌应触发唯一约束."""
        sample_user.email_verification_token = "verify-token-abc"
        repo.save(sample_user)

        user2 = User(
            id="user-2",
            email="user2@example.com",
            display_name="User 2",
            username="user2",
            email_verification_token="verify-token-abc",  # 重复
        )
        with pytest.raises(ValueError, match="verification token already in use"):
            repo.save(user2)

    def test_duplicate_reset_token_raises(self, repo, sample_user):
        """相同密码重置令牌应触发唯一约束."""
        sample_user.password_reset_token = "reset-token-xyz"
        repo.save(sample_user)

        user2 = User(
            id="user-2",
            email="user2@example.com",
            display_name="User 2",
            username="user2",
            password_reset_token="reset-token-xyz",  # 重复
        )
        with pytest.raises(ValueError, match="reset token already in use"):
            repo.save(user2)
