"""
密码重置 Use Case 测试
"""

from datetime import datetime, timedelta, timezone
from unittest.mock import Mock

import pytest

from packages.application.auth.password_reset_use_case import (
    RequestPasswordResetRequest,
    RequestPasswordResetUseCase,
    ResetPasswordRequest,
    ResetPasswordUseCase,
)
from packages.domain.entities import User


class TestRequestPasswordResetUseCase:
    """请求密码重置测试"""

    @pytest.fixture
    def mock_user_repo(self):
        repo = Mock()
        repo.find_by_email = Mock(return_value=None)
        repo.save = Mock()
        return repo

    @pytest.fixture
    def use_case(self, mock_user_repo):
        email_service = Mock()
        email_service.send_password_reset_email.return_value = (True, None)
        return RequestPasswordResetUseCase(
            user_repository=mock_user_repo,
            base_url="https://test.com",
            token_expire_hours=1,
            email_service=email_service,
        )

    @pytest.fixture
    def test_user(self):
        return User(
            id="user-123",
            email="test@example.com",
            username="testuser",
            display_name="Test User",
            password_hash="hash",
        )

    def test_request_reset_success(self, use_case, mock_user_repo, test_user):
        """测试请求重置成功"""
        mock_user_repo.find_by_email.return_value = test_user

        request = RequestPasswordResetRequest(email="test@example.com")
        success, error = use_case.execute(request)

        assert success is True
        assert error is None

        # 验证保存了用户
        mock_user_repo.save.assert_called_once()
        saved_user = mock_user_repo.save.call_args[0][0]
        assert saved_user.password_reset_token is not None
        assert saved_user.password_reset_expires_at is not None

        # 验证发送了邮件
        use_case.email_service.send_password_reset_email.assert_called_once()

    def test_request_reset_user_not_exists(self, use_case, mock_user_repo):
        """测试用户不存在（仍返回成功，避免暴露）"""
        mock_user_repo.find_by_email.return_value = None

        request = RequestPasswordResetRequest(email="nonexistent@example.com")
        success, error = use_case.execute(request)

        assert success is True  # 安全考虑，仍返回成功
        assert error is None

        # 不发送邮件
        use_case.email_service.send_password_reset_email.assert_not_called()

    def test_request_reset_missing_email(self, use_case):
        """测试缺少邮箱"""
        request = RequestPasswordResetRequest(email="")
        success, error = use_case.execute(request)

        assert success is False
        assert error == "Email is required"


class TestResetPasswordUseCase:
    """重置密码测试"""

    @pytest.fixture
    def mock_user_repo(self):
        repo = Mock()
        repo.find_by_password_reset_token = Mock(return_value=None)
        repo.save = Mock()
        return repo

    @pytest.fixture
    def use_case(self, mock_user_repo):
        return ResetPasswordUseCase(user_repository=mock_user_repo)

    @pytest.fixture
    def test_user(self):
        return User(
            id="user-123",
            email="test@example.com",
            username="testuser",
            display_name="Test User",
            password_hash="old-hash",
            password_reset_token="valid-token",
            password_reset_expires_at=datetime.now(timezone.utc) + timedelta(hours=1),
        )

    def test_reset_password_success(self, use_case, mock_user_repo, test_user):
        """测试重置密码成功"""
        mock_user_repo.find_by_password_reset_token.return_value = test_user

        request = ResetPasswordRequest(
            token="valid-token",
            new_password="NewSecurePass123",
        )
        success, error = use_case.execute(request)

        assert success is True
        assert error is None

        # 验证密码已更新
        assert test_user.password_hash != "old-hash"
        assert test_user.password_reset_token is None
        assert test_user.password_reset_expires_at is None

        # 验证保存了用户
        mock_user_repo.save.assert_called_once()

    def test_reset_password_success_with_naive_database_datetime(self, use_case, mock_user_repo, test_user):
        """测试数据库返回 naive datetime 时仍可重置密码"""
        test_user.password_reset_expires_at = (datetime.now(timezone.utc) + timedelta(hours=1)).replace(tzinfo=None)
        mock_user_repo.find_by_password_reset_token.return_value = test_user

        success, error = use_case.execute(ResetPasswordRequest(token="valid-token", new_password="NewSecurePass123"))

        assert success is True
        assert error is None
        mock_user_repo.save.assert_called_once()

    def test_reset_password_weak_password(self, use_case, mock_user_repo, test_user):
        """测试弱密码"""
        mock_user_repo.find_by_password_reset_token.return_value = test_user

        request = ResetPasswordRequest(
            token="valid-token",
            new_password="weak",
        )
        success, error = use_case.execute(request)

        assert success is False
        assert "at least 8 characters" in error

    def test_reset_password_invalid_token(self, use_case, mock_user_repo):
        """测试无效令牌"""
        mock_user_repo.find_by_password_reset_token.return_value = None

        request = ResetPasswordRequest(
            token="invalid-token",
            new_password="NewSecurePass123",
        )
        success, error = use_case.execute(request)

        assert success is False
        assert error == "Invalid or expired reset token"

    def test_reset_password_expired_token(self, use_case, mock_user_repo, test_user):
        """测试过期令牌"""
        test_user.password_reset_expires_at = datetime.now(timezone.utc) - timedelta(hours=1)
        mock_user_repo.find_by_password_reset_token.return_value = test_user

        request = ResetPasswordRequest(
            token="valid-token",
            new_password="NewSecurePass123",
        )
        success, error = use_case.execute(request)

        assert success is False
        assert error == "Reset token has expired"

    def test_reset_password_missing_token(self, use_case):
        """测试缺少令牌"""
        request = ResetPasswordRequest(
            token="",
            new_password="NewSecurePass123",
        )
        success, error = use_case.execute(request)

        assert success is False
        assert error == "Reset token is required"

    def test_reset_password_missing_password(self, use_case, mock_user_repo, test_user):
        """测试缺少新密码"""
        mock_user_repo.find_by_password_reset_token.return_value = test_user

        request = ResetPasswordRequest(
            token="valid-token",
            new_password="",
        )
        success, error = use_case.execute(request)

        assert success is False
        assert error == "New password is required"
