"""Tests for package initialization and exports.

This module tests that all public classes, exceptions, and symbols
are properly exported from the aiokwikset package.
"""

from __future__ import annotations

import aiokwikset
from aiokwikset import (
    # Main API class
    API,
    # Device types and results
    AccessoryAction,
    AuthenticationError,
    CommandResult,
    # Endpoint handlers
    Device,
    # Specific exceptions
    DeviceCommandError,
    DeviceError,
    DeviceStatus,
    InvalidActionError,
    InvalidDeviceError,
    InvalidStatusError,
    InvalidUserError,
    # Base exceptions
    KwiksetError,
    LockAction,
    MFAChallengeRequired,
    PasswordChangeRequired,
    RequestError,
    Unauthenticated,
    UnknownError,
    User,
    UserExists,
    UserNotConfirmed,
    UserNotFound,
    ValidationResult,
)

# ============================================================================
# Version Tests
# ============================================================================


class TestVersion:
    """Tests for package version."""

    def test_version_defined(self) -> None:
        """Test __version__ is defined."""
        assert hasattr(aiokwikset, "__version__")

    def test_version_is_string(self) -> None:
        """Test __version__ is a string."""
        assert isinstance(aiokwikset.__version__, str)

    def test_version_format(self) -> None:
        """Test __version__ follows semantic versioning."""
        version = aiokwikset.__version__

        # Should contain at least major.minor.patch
        parts = version.split(".")
        assert len(parts) >= 2, "Version should have at least major.minor"

        # Major and minor should be numeric (patch might have suffix like 'b1')
        assert parts[0].isdigit(), "Major version should be numeric"
        assert parts[1].isdigit(), "Minor version should be numeric"


# ============================================================================
# __all__ Tests
# ============================================================================


class TestAllExports:
    """Tests for __all__ exports."""

    def test_all_defined(self) -> None:
        """Test __all__ is defined."""
        assert hasattr(aiokwikset, "__all__")

    def test_all_is_list(self) -> None:
        """Test __all__ is a list."""
        assert isinstance(aiokwikset.__all__, list)

    def test_all_contains_main_classes(self) -> None:
        """Test __all__ contains main classes."""
        expected = ["API", "Device", "User"]

        for name in expected:
            assert name in aiokwikset.__all__, f"{name} should be in __all__"

    def test_all_contains_device_types(self) -> None:
        """Test __all__ contains device types and results."""
        expected = [
            "AccessoryAction",
            "CommandResult",
            "DeviceStatus",
            "LockAction",
            "ValidationResult",
        ]

        for name in expected:
            assert name in aiokwikset.__all__, f"{name} should be in __all__"

    def test_all_contains_base_exceptions(self) -> None:
        """Test __all__ contains base exception classes."""
        expected = [
            "KwiksetError",
            "AuthenticationError",
            "DeviceError",
        ]

        for name in expected:
            assert name in aiokwikset.__all__, f"{name} should be in __all__"

    def test_all_contains_specific_exceptions(self) -> None:
        """Test __all__ contains specific exception classes."""
        expected = [
            "DeviceCommandError",
            "InvalidActionError",
            "InvalidDeviceError",
            "InvalidStatusError",
            "InvalidUserError",
            "MFAChallengeRequired",
            "PasswordChangeRequired",
            "RequestError",
            "Unauthenticated",
            "UnknownError",
            "UserExists",
            "UserNotConfirmed",
            "UserNotFound",
        ]

        for name in expected:
            assert name in aiokwikset.__all__, f"{name} should be in __all__"

    def test_all_exports_are_accessible(self) -> None:
        """Test all items in __all__ are actually accessible."""
        for name in aiokwikset.__all__:
            assert hasattr(aiokwikset, name), f"{name} in __all__ but not accessible"


# ============================================================================
# Import Tests
# ============================================================================


class TestImports:
    """Tests for package imports."""

    def test_api_importable(self) -> None:
        """Test API class is importable."""
        assert API is not None
        assert callable(API)

    def test_device_importable(self) -> None:
        """Test Device class is importable."""
        assert Device is not None
        assert callable(Device)

    def test_user_importable(self) -> None:
        """Test User class is importable."""
        assert User is not None
        assert callable(User)

    def test_enums_importable(self) -> None:
        """Test enum classes are importable."""
        assert AccessoryAction is not None
        assert DeviceStatus is not None
        assert LockAction is not None

    def test_dataclasses_importable(self) -> None:
        """Test dataclass types are importable."""
        assert CommandResult is not None
        assert ValidationResult is not None

    def test_exceptions_importable(self) -> None:
        """Test exception classes are importable."""
        exceptions = [
            KwiksetError,
            AuthenticationError,
            DeviceError,
            DeviceCommandError,
            InvalidActionError,
            InvalidDeviceError,
            InvalidStatusError,
            InvalidUserError,
            MFAChallengeRequired,
            PasswordChangeRequired,
            RequestError,
            Unauthenticated,
            UnknownError,
            UserExists,
            UserNotConfirmed,
            UserNotFound,
        ]

        for exc_class in exceptions:
            assert exc_class is not None
            assert issubclass(exc_class, Exception)


# ============================================================================
# Class Type Tests
# ============================================================================


class TestClassTypes:
    """Tests for exported class types."""

    def test_api_is_class(self) -> None:
        """Test API is a class."""
        assert isinstance(API, type)

    def test_device_is_class(self) -> None:
        """Test Device is a class."""
        assert isinstance(Device, type)

    def test_user_is_class(self) -> None:
        """Test User is a class."""
        assert isinstance(User, type)

    def test_lock_action_is_enum(self) -> None:
        """Test LockAction is an Enum."""
        from enum import Enum

        assert issubclass(LockAction, Enum)

    def test_device_status_is_enum(self) -> None:
        """Test DeviceStatus is an Enum."""
        from enum import Enum

        assert issubclass(DeviceStatus, Enum)

    def test_accessory_action_is_enum(self) -> None:
        """Test AccessoryAction is an Enum."""
        from enum import Enum

        assert issubclass(AccessoryAction, Enum)

    def test_command_result_is_dataclass(self) -> None:
        """Test CommandResult is a dataclass."""
        from dataclasses import is_dataclass

        assert is_dataclass(CommandResult)

    def test_validation_result_is_dataclass(self) -> None:
        """Test ValidationResult is a dataclass."""
        from dataclasses import is_dataclass

        assert is_dataclass(ValidationResult)


# ============================================================================
# Docstring Tests
# ============================================================================


class TestDocstrings:
    """Tests for module and class docstrings."""

    def test_package_has_docstring(self) -> None:
        """Test package has a docstring."""
        assert aiokwikset.__doc__ is not None
        assert len(aiokwikset.__doc__) > 0

    def test_api_has_docstring(self) -> None:
        """Test API class has a docstring."""
        assert API.__doc__ is not None
        assert len(API.__doc__) > 0

    def test_device_has_docstring(self) -> None:
        """Test Device class has a docstring."""
        assert Device.__doc__ is not None

    def test_user_has_docstring(self) -> None:
        """Test User class has a docstring."""
        assert User.__doc__ is not None


# ============================================================================
# Example Usage Tests
# ============================================================================


class TestExampleUsage:
    """Tests based on documentation example patterns."""

    def test_can_create_api_instance(self) -> None:
        """Test API can be instantiated."""
        api = API()
        assert api is not None

    def test_can_check_authentication_status(self) -> None:
        """Test can check is_authenticated property."""
        api = API()
        assert api.is_authenticated is False

    def test_can_access_endpoints_before_auth(self) -> None:
        """Test endpoint handlers are None before authentication."""
        api = API()
        assert api.device is None
        assert api.user is None

    def test_exceptions_can_be_raised(self) -> None:
        """Test exceptions can be raised and caught."""
        try:
            raise MFAChallengeRequired(
                message="MFA required",
                mfa_type="SOFTWARE_TOKEN_MFA",
            )
        except AuthenticationError as err:
            assert err.mfa_type == "SOFTWARE_TOKEN_MFA"

    def test_command_result_can_be_created(self) -> None:
        """Test CommandResult can be instantiated."""
        result = CommandResult(
            success=True,
            data={"status": "locked"},
            device_id="SN12345",
            action="lock",
        )

        assert result.success is True
        assert result.device_id == "SN12345"

    def test_validation_result_can_be_created(self) -> None:
        """Test ValidationResult can be instantiated and used."""
        result = ValidationResult()
        result.add_error("Test error")

        assert result.is_valid is False
        assert "Test error" in result.errors
