"""Auth ports (ABC接口) 单元测试.

验证抽象接口定义正确：不能直接实例化，子类必须实现所有抽象方法。
"""

from __future__ import annotations

from abc import ABC

import pytest
from domain.auth.email_service import EmailServicePort
from domain.auth.jwt_service import JWTServicePort
from domain.auth.password_hasher import PasswordHasherPort, PasswordValidatorPort
from domain.auth.session_store import SessionStorePort
from domain.auth.sms_service import SmsService


class TestSessionStorePort:
    """SessionStorePort 接口测试."""

    def test_is_abstract(self):
        assert issubclass(SessionStorePort, ABC)

    def test_cannot_instantiate(self):
        with pytest.raises(TypeError):
            SessionStorePort()  # type: ignore[misc]

    def test_has_abstract_methods(self):
        abstract_methods = SessionStorePort.__abstractmethods__
        expected = {
            "save_session",
            "get_session",
            "get_session_by_refresh_token",
            "get_refresh_token",
            "update_last_active",
            "delete_session",
            "get_user_sessions",
            "delete_all_user_sessions",
            "session_exists",
        }
        assert expected.issubset(abstract_methods)

    def test_concrete_subclass_works(self):
        class ConcreteStore(SessionStorePort):
            def save_session(self, **kwargs):  # type: ignore[override]
                return True

            def get_session(self, session_id):  # type: ignore[override]
                return None

            def get_session_by_refresh_token(self, token):  # type: ignore[override]
                return None

            def get_refresh_token(self, session_id):  # type: ignore[override]
                return None

            def update_last_active(self, session_id):  # type: ignore[override]
                return True

            def delete_session(self, session_id):  # type: ignore[override]
                return True

            def get_user_sessions(self, user_id):  # type: ignore[override]
                return []

            def delete_all_user_sessions(self, user_id):  # type: ignore[override]
                return 0

            def session_exists(self, session_id):  # type: ignore[override]
                return False

        store = ConcreteStore()
        assert isinstance(store, SessionStorePort)
        assert store.session_exists("s1") is False
        assert store.delete_session("s1") is True


class TestEmailServicePort:
    """EmailServicePort 接口测试."""

    def test_is_abstract(self):
        assert issubclass(EmailServicePort, ABC)

    def test_cannot_instantiate(self):
        with pytest.raises(TypeError):
            EmailServicePort()  # type: ignore[misc]

    def test_has_abstract_methods(self):
        abstract_methods = EmailServicePort.__abstractmethods__
        expected = {"send_email", "send_verification_email", "send_password_reset_email"}
        assert expected.issubset(abstract_methods)


class TestJWTServicePort:
    """JWTServicePort 接口测试."""

    def test_is_abstract(self):
        assert issubclass(JWTServicePort, ABC)

    def test_cannot_instantiate(self):
        with pytest.raises(TypeError):
            JWTServicePort()  # type: ignore[misc]

    def test_has_abstract_methods(self):
        abstract_methods = JWTServicePort.__abstractmethods__
        expected = {
            "create_access_token",
            "create_refresh_token",
            "verify_token",
            "verify_access_token",
            "verify_refresh_token",
        }
        assert expected.issubset(abstract_methods)


class TestPasswordHasherPort:
    """PasswordHasherPort 接口测试."""

    def test_is_abstract(self):
        assert issubclass(PasswordHasherPort, ABC)

    def test_cannot_instantiate(self):
        with pytest.raises(TypeError):
            PasswordHasherPort()  # type: ignore[misc]

    def test_has_abstract_methods(self):
        abstract_methods = PasswordHasherPort.__abstractmethods__
        expected = {"hash_password", "verify_password", "needs_rehash"}
        assert expected.issubset(abstract_methods)


class TestPasswordValidatorPort:
    """PasswordValidatorPort 接口测试."""

    def test_is_abstract(self):
        assert issubclass(PasswordValidatorPort, ABC)

    def test_cannot_instantiate(self):
        with pytest.raises(TypeError):
            PasswordValidatorPort()  # type: ignore[misc]

    def test_has_validate_method(self):
        assert "validate" in PasswordValidatorPort.__abstractmethods__


class TestSmsService:
    """SmsService 接口测试."""

    def test_is_abstract(self):
        assert issubclass(SmsService, ABC)

    def test_cannot_instantiate(self):
        with pytest.raises(TypeError):
            SmsService()  # type: ignore[misc]

    def test_has_abstract_methods(self):
        abstract_methods = SmsService.__abstractmethods__
        expected = {"send_verification_code", "send_template_sms"}
        assert expected.issubset(abstract_methods)
