"""
Tests for django_lark.utils module.
"""

from unittest.mock import MagicMock, patch

import lark
import pytest

from django_lark.utils import (
    aget_billing_state_for_user,
    aget_or_create_subject_for_user,
    aget_subscriptions_for_user,
    get_billing_state_for_user,
    get_external_id_for_user,
    get_or_create_subject_for_user,
    get_subscriptions_for_user,
)


@pytest.fixture
def mock_user():
    """Create a mock Django user."""
    user = MagicMock()
    user.pk = 1
    user.email = "test@example.com"
    user.get_full_name.return_value = "Test User"
    user.is_authenticated = True
    return user


class TestGetExternalIdForUser:
    """Tests for get_external_id_for_user function."""

    def test_returns_email_by_default(self, mock_user):
        """Default setting uses email as external_id."""
        external_id = get_external_id_for_user(mock_user)
        assert external_id == "test@example.com"

    def test_returns_django_user_prefix_for_id_field(self, mock_user):
        """When field is 'id', returns 'django_user_{pk}'."""
        with patch("django_lark.utils.get_settings") as mock_settings:
            mock_settings.return_value.user_subject_field = "id"
            external_id = get_external_id_for_user(mock_user)
            assert external_id == "django_user_1"

    def test_returns_custom_field(self, mock_user):
        """Can use any user field as external_id."""
        mock_user.uuid = "abc-123-def"
        with patch("django_lark.utils.get_settings") as mock_settings:
            mock_settings.return_value.user_subject_field = "uuid"
            external_id = get_external_id_for_user(mock_user)
            assert external_id == "abc-123-def"

    def test_callable_field(self, mock_user):
        """Can use a callable to compute external_id."""
        with patch("django_lark.utils.get_settings") as mock_settings:
            mock_settings.return_value.user_subject_field = lambda u: f"custom_{u.pk}"
            external_id = get_external_id_for_user(mock_user)
            assert external_id == "custom_1"


class TestGetOrCreateSubjectForUser:
    """Tests for get_or_create_subject_for_user function."""

    def test_returns_existing_subject(self, mock_user, mock_lark_client):
        """Returns existing subject when found by external_id."""
        subject, created = get_or_create_subject_for_user(mock_user)

        assert subject.id == "subj_123"
        assert created is False
        mock_lark_client.subjects.retrieve.assert_called_once_with("test@example.com")

    def test_creates_subject_when_not_found(self, mock_user, mock_lark_client):
        """Creates new subject when not found and create_if_missing=True."""
        mock_response = MagicMock()
        mock_lark_client.subjects.retrieve.side_effect = lark.NotFoundError(
            "Not found", response=mock_response, body=None
        )

        subject, created = get_or_create_subject_for_user(mock_user)

        assert subject.id == "subj_new"
        assert created is True
        mock_lark_client.subjects.create.assert_called_once_with(
            name="Test User",
            email="test@example.com",
            external_id="test@example.com",
        )

    def test_raises_when_not_found_and_create_disabled(self, mock_user, mock_lark_client):
        """Raises LookupError when subject not found and create_if_missing=False."""
        mock_response = MagicMock()
        mock_lark_client.subjects.retrieve.side_effect = lark.NotFoundError(
            "Not found", response=mock_response, body=None
        )

        with pytest.raises(LookupError):
            get_or_create_subject_for_user(mock_user, create_if_missing=False)

    def test_updates_subject_when_update_if_exists(self, mock_user, mock_lark_client):
        """Updates subject when update_if_exists=True."""
        subject, created = get_or_create_subject_for_user(
            mock_user, update_if_exists=True
        )

        assert created is False
        mock_lark_client.subjects.update.assert_called_once_with(
            "test@example.com",
            name="Test User",
            email="test@example.com",
        )


class TestGetBillingStateForUser:
    """Tests for get_billing_state_for_user function."""

    def test_returns_billing_state(self, mock_user, mock_lark_client):
        """Returns billing state from Lark API."""
        billing_state = get_billing_state_for_user(mock_user)

        assert billing_state.has_active_subscription is True
        mock_lark_client.customer_access.retrieve_billing_state.assert_called_once_with(
            "test@example.com"
        )

    def test_raises_on_api_error(self, mock_user, mock_lark_client):
        """Raises APIError when API call fails."""
        mock_request = MagicMock()
        mock_lark_client.customer_access.retrieve_billing_state.side_effect = (
            lark.APIError("Error", mock_request, body=None)
        )

        with pytest.raises(lark.APIError):
            get_billing_state_for_user(mock_user)


class TestGetSubscriptionsForUser:
    """Tests for get_subscriptions_for_user function."""

    def test_returns_subscriptions(self, mock_user, mock_lark_client):
        """Returns subscriptions from Lark API."""
        result = get_subscriptions_for_user(mock_user)

        mock_lark_client.subscriptions.list.assert_called_once_with(
            subject_id="test@example.com"
        )
        assert len(result.data) == 2


@pytest.mark.asyncio
class TestAsyncFunctions:
    """Tests for async utility functions."""

    async def test_aget_billing_state_for_user(self, mock_user):
        """Async version returns billing state."""
        with patch("django_lark.utils.get_async_lark_client") as mock_get_client:
            mock_client = MagicMock()
            mock_get_client.return_value = mock_client

            async def async_retrieve(external_id):
                return MagicMock(has_active_subscription=True)

            mock_client.customer_access.retrieve_billing_state = async_retrieve

            billing_state = await aget_billing_state_for_user(mock_user)
            assert billing_state.has_active_subscription is True

    async def test_aget_subscriptions_for_user(self, mock_user):
        """Async version returns subscriptions."""
        with patch("django_lark.utils.get_async_lark_client") as mock_get_client:
            mock_client = MagicMock()
            mock_get_client.return_value = mock_client

            async def async_list(subject_id):
                return MagicMock(data=[MagicMock(id="sub_1")])

            mock_client.subscriptions.list = async_list

            result = await aget_subscriptions_for_user(mock_user)
            assert len(result.data) == 1

    async def test_aget_or_create_subject_for_user(self, mock_user):
        """Async version gets or creates subject."""
        with patch("django_lark.utils.get_async_lark_client") as mock_get_client:
            mock_client = MagicMock()
            mock_get_client.return_value = mock_client

            async def async_retrieve(external_id):
                return MagicMock(id="subj_123")

            mock_client.subjects.retrieve = async_retrieve

            subject, created = await aget_or_create_subject_for_user(mock_user)
            assert subject.id == "subj_123"
            assert created is False
