"""Tests for the User class.

This module contains comprehensive tests for the User endpoint handler,
including user information retrieval and home listing operations.
"""

from __future__ import annotations

from typing import Any
from unittest.mock import AsyncMock

import pytest

from aiokwikset.user import User

from .conftest import MOCK_HOMES, MOCK_USER_INFO

# ============================================================================
# User Initialization Tests
# ============================================================================


class TestUserInitialization:
    """Tests for User class initialization."""

    def test_init_with_request_callable(self) -> None:
        """Test User initialization with request callable."""
        mock_request = AsyncMock()
        user = User(mock_request)

        assert user._request is mock_request

    def test_uses_slots(self) -> None:
        """Test User uses __slots__ for memory efficiency."""
        assert hasattr(User, "__slots__")
        assert "_request" in User.__slots__


# ============================================================================
# get_info Tests
# ============================================================================


class TestGetInfo:
    """Tests for User.get_info method."""

    @pytest.mark.asyncio
    async def test_get_info_success(self) -> None:
        """Test successful get_info call."""
        mock_request = AsyncMock(return_value=MOCK_USER_INFO)
        user = User(mock_request)

        result = await user.get_info()

        assert result is not None
        assert result["email"] == "test@example.com"
        assert result["firstname"] == "John"
        assert result["lastname"] == "Doe"
        mock_request.assert_called_once()

    @pytest.mark.asyncio
    async def test_get_info_returns_first_item(self) -> None:
        """Test get_info returns first item from data array."""
        mock_response = {
            "data": [
                {"userid": "user-1", "email": "first@example.com"},
                {"userid": "user-2", "email": "second@example.com"},
            ]
        }
        mock_request = AsyncMock(return_value=mock_response)
        user = User(mock_request)

        result = await user.get_info()

        assert result is not None
        assert result["userid"] == "user-1"
        assert result["email"] == "first@example.com"

    @pytest.mark.asyncio
    async def test_get_info_empty_data_returns_none(self) -> None:
        """Test get_info returns None when data is empty."""
        mock_request = AsyncMock(return_value={"data": []})
        user = User(mock_request)

        result = await user.get_info()

        assert result is None

    @pytest.mark.asyncio
    async def test_get_info_missing_data_returns_none(self) -> None:
        """Test get_info returns None when data key is missing."""
        mock_request = AsyncMock(return_value={})
        user = User(mock_request)

        result = await user.get_info()

        assert result is None

    @pytest.mark.asyncio
    async def test_get_info_calls_correct_endpoint(self) -> None:
        """Test get_info calls the correct API endpoint."""
        mock_request = AsyncMock(return_value=MOCK_USER_INFO)
        user = User(mock_request)

        await user.get_info()

        mock_request.assert_called_once_with("get", mock_request.call_args[0][1])
        # Verify the URL contains expected path
        call_url = mock_request.call_args[0][1]
        assert "users/me" in call_url


# ============================================================================
# get_homes Tests
# ============================================================================


class TestGetHomes:
    """Tests for User.get_homes method."""

    @pytest.mark.asyncio
    async def test_get_homes_success(self) -> None:
        """Test successful get_homes call."""
        mock_request = AsyncMock(return_value=MOCK_HOMES)
        user = User(mock_request)

        result = await user.get_homes()

        assert len(result) == 2
        assert result[0]["homeid"] == "home-001"
        assert result[0]["homename"] == "My Home"
        assert result[1]["homeid"] == "home-002"
        assert result[1]["homename"] == "Beach House"

    @pytest.mark.asyncio
    async def test_get_homes_single_home(self) -> None:
        """Test get_homes with single home."""
        mock_response = {"data": [{"homeid": "home-single", "homename": "Only Home"}]}
        mock_request = AsyncMock(return_value=mock_response)
        user = User(mock_request)

        result = await user.get_homes()

        assert len(result) == 1
        assert result[0]["homename"] == "Only Home"

    @pytest.mark.asyncio
    async def test_get_homes_empty_returns_empty_list(self) -> None:
        """Test get_homes returns empty list when no homes."""
        mock_request = AsyncMock(return_value={"data": []})
        user = User(mock_request)

        result = await user.get_homes()

        assert result == []
        assert isinstance(result, list)

    @pytest.mark.asyncio
    async def test_get_homes_missing_data_returns_empty_list(self) -> None:
        """Test get_homes returns empty list when data key is missing."""
        mock_request = AsyncMock(return_value={})
        user = User(mock_request)

        result = await user.get_homes()

        assert result == []

    @pytest.mark.asyncio
    async def test_get_homes_calls_correct_endpoint(self) -> None:
        """Test get_homes calls the correct API endpoint."""
        mock_request = AsyncMock(return_value=MOCK_HOMES)
        user = User(mock_request)

        await user.get_homes()

        mock_request.assert_called_once()
        call_url = mock_request.call_args[0][1]
        assert "users/me/homes" in call_url

    @pytest.mark.asyncio
    async def test_get_homes_returns_all_home_fields(self) -> None:
        """Test get_homes returns all fields for each home."""
        mock_request = AsyncMock(return_value=MOCK_HOMES)
        user = User(mock_request)

        result = await user.get_homes()

        # Verify all expected fields are present
        first_home = result[0]
        assert "homeid" in first_home
        assert "homename" in first_home
        assert "timezone" in first_home


# ============================================================================
# Integration-style Tests
# ============================================================================


class TestUserIntegration:
    """Integration-style tests for User class."""

    @pytest.mark.asyncio
    async def test_get_info_then_homes(self) -> None:
        """Test getting user info then homes in sequence."""
        call_count = 0

        async def mock_request_handler(
            _method: str,
            url: str,
        ) -> dict[str, Any]:
            nonlocal call_count
            call_count += 1

            if "homes" in url:
                return MOCK_HOMES
            else:
                return MOCK_USER_INFO

        mock_request = AsyncMock(side_effect=mock_request_handler)
        user = User(mock_request)

        # Get user info
        user_info = await user.get_info()
        assert user_info is not None
        assert user_info["email"] == "test@example.com"

        # Get homes
        homes = await user.get_homes()
        assert len(homes) == 2

        # Verify both calls were made
        assert call_count == 2

    @pytest.mark.asyncio
    async def test_request_failure_propagates(self) -> None:
        """Test that request failures propagate correctly."""
        mock_request = AsyncMock(side_effect=Exception("Network error"))
        user = User(mock_request)

        with pytest.raises(Exception) as exc_info:
            await user.get_info()

        assert "Network error" in str(exc_info.value)
