"""
Unit tests for platform/api/permissions.py

Tests permission classes for the public API.
"""

from unittest.mock import Mock, patch

import pytest

from smoothschedule.platform.api.permissions import (
    HasAPIToken,
    HasScope,
    HasAllScopes,
    require_scopes,
    CanReadServices,
    CanReadResources,
    CanReadAvailability,
    CanReadBookings,
    CanWriteBookings,
    CanReadCustomers,
    CanWriteCustomers,
    CanReadBusiness,
    CanManageWebhooks,
    BookingsReadWritePermission,
    CustomersReadWritePermission,
)
from smoothschedule.platform.api.models import APIScope


class TestHasAPIToken:
    """Tests for HasAPIToken permission class."""

    def test_has_permission_returns_true_with_valid_token(self):
        """Should return True when request has valid API token."""
        permission = HasAPIToken()

        token = Mock()
        token.is_valid.return_value = True

        request = Mock()
        request.api_token = token
        view = Mock()

        assert permission.has_permission(request, view) is True

    def test_has_permission_returns_false_when_no_api_token_attr(self):
        """Should return False when request has no api_token attribute."""
        permission = HasAPIToken()

        request = Mock(spec=['user'])  # No api_token
        view = Mock()

        assert permission.has_permission(request, view) is False

    def test_has_permission_returns_false_when_api_token_is_none(self):
        """Should return False when api_token is None."""
        permission = HasAPIToken()

        request = Mock()
        request.api_token = None
        view = Mock()

        assert permission.has_permission(request, view) is False

    def test_has_permission_returns_false_when_token_invalid(self):
        """Should return False when token.is_valid() returns False."""
        permission = HasAPIToken()

        token = Mock()
        token.is_valid.return_value = False

        request = Mock()
        request.api_token = token
        view = Mock()

        assert permission.has_permission(request, view) is False

    def test_message_attribute(self):
        """Should have appropriate error message."""
        permission = HasAPIToken()
        assert 'API token' in permission.message


class TestHasScope:
    """Tests for HasScope permission class."""

    def test_has_permission_returns_false_without_api_token(self):
        """Should return False when no API token."""
        permission = HasScope()

        request = Mock(spec=['user'])
        view = Mock()

        assert permission.has_permission(request, view) is False

    def test_has_permission_returns_false_when_token_is_none(self):
        """Should return False when api_token is None."""
        permission = HasScope()

        request = Mock()
        request.api_token = None
        view = Mock()

        assert permission.has_permission(request, view) is False

    def test_has_permission_returns_true_when_no_scopes_required(self):
        """Should return True when no scopes are required."""
        permission = HasScope()

        token = Mock()
        request = Mock()
        request.api_token = token

        view = Mock(spec=['get'])  # No required_scopes
        view.required_scopes = None

        assert permission.has_permission(request, view) is True

    def test_has_permission_returns_true_when_token_has_scope(self):
        """Should return True when token has required scope."""
        permission = HasScope()

        token = Mock()
        token.has_any_scope.return_value = True

        request = Mock()
        request.api_token = token

        view = Mock()
        view.required_scopes = ['services:read']

        assert permission.has_permission(request, view) is True
        token.has_any_scope.assert_called_once_with(['services:read'])

    def test_has_permission_returns_false_when_token_lacks_scope(self):
        """Should return False when token lacks required scope."""
        permission = HasScope()

        token = Mock()
        token.has_any_scope.return_value = False

        request = Mock()
        request.api_token = token

        view = Mock()
        view.required_scopes = ['services:write']

        assert permission.has_permission(request, view) is False

    def test_get_required_scopes_with_list(self):
        """Should return list of scopes directly."""
        permission = HasScope()

        request = Mock()
        view = Mock()
        view.required_scopes = ['scope1', 'scope2']

        result = permission._get_required_scopes(request, view)

        assert result == ['scope1', 'scope2']

    def test_get_required_scopes_with_dict_by_method(self):
        """Should return scopes for specific HTTP method."""
        permission = HasScope()

        request = Mock()
        request.method = 'POST'

        view = Mock()
        view.required_scopes = {
            'GET': ['read'],
            'POST': ['write'],
        }

        result = permission._get_required_scopes(request, view)

        assert result == ['write']

    def test_get_required_scopes_with_dict_missing_method(self):
        """Should return empty list when method not in dict."""
        permission = HasScope()

        request = Mock()
        request.method = 'DELETE'

        view = Mock()
        view.required_scopes = {
            'GET': ['read'],
            'POST': ['write'],
        }

        result = permission._get_required_scopes(request, view)

        assert result == []

    def test_get_required_scopes_with_callable(self):
        """Should call callable to get scopes."""
        permission = HasScope()

        request = Mock()
        view = Mock()
        view.required_scopes = lambda req, v: ['dynamic:scope']

        result = permission._get_required_scopes(request, view)

        assert result == ['dynamic:scope']

    def test_get_required_scopes_with_none(self):
        """Should return empty list when required_scopes is None."""
        permission = HasScope()

        request = Mock()
        view = Mock()
        view.required_scopes = None

        result = permission._get_required_scopes(request, view)

        assert result == []


class TestHasAllScopes:
    """Tests for HasAllScopes permission class."""

    def test_has_permission_returns_false_without_api_token(self):
        """Should return False when no API token."""
        permission = HasAllScopes()

        request = Mock(spec=['user'])
        view = Mock()

        assert permission.has_permission(request, view) is False

    def test_has_permission_returns_true_when_no_scopes_required(self):
        """Should return True when no scopes are required."""
        permission = HasAllScopes()

        token = Mock()
        request = Mock()
        request.api_token = token

        view = Mock()
        view.required_scopes = []

        assert permission.has_permission(request, view) is True

    def test_has_permission_uses_has_all_scopes(self):
        """Should use has_all_scopes instead of has_any_scope."""
        permission = HasAllScopes()

        token = Mock()
        token.has_all_scopes.return_value = True

        request = Mock()
        request.api_token = token

        view = Mock()
        view.required_scopes = ['scope1', 'scope2']

        result = permission.has_permission(request, view)

        assert result is True
        token.has_all_scopes.assert_called_once_with(['scope1', 'scope2'])

    def test_message_attribute(self):
        """Should have appropriate error message."""
        permission = HasAllScopes()
        assert 'all required scopes' in permission.message


class TestRequireScopesDecorator:
    """Tests for require_scopes decorator."""

    def test_sets_required_scopes_on_class(self):
        """Should set required_scopes attribute on decorated class."""

        @require_scopes('services:read', 'services:write')
        class TestView:
            pass

        assert TestView.required_scopes == ['services:read', 'services:write']

    def test_sets_required_scopes_on_function(self):
        """Should set required_scopes attribute on decorated function."""

        @require_scopes('bookings:write')
        def create_booking(request):
            pass

        assert create_booking.required_scopes == ['bookings:write']

    def test_single_scope(self):
        """Should work with single scope."""

        @require_scopes('read')
        class TestView:
            pass

        assert TestView.required_scopes == ['read']


class TestCanReadServices:
    """Tests for CanReadServices permission class."""

    def test_returns_services_read_scope(self):
        """Should require services:read scope."""
        permission = CanReadServices()

        request = Mock()
        view = Mock()

        scopes = permission._get_required_scopes(request, view)

        assert scopes == [APIScope.SERVICES_READ]


class TestCanReadResources:
    """Tests for CanReadResources permission class."""

    def test_returns_resources_read_scope(self):
        """Should require resources:read scope."""
        permission = CanReadResources()

        request = Mock()
        view = Mock()

        scopes = permission._get_required_scopes(request, view)

        assert scopes == [APIScope.RESOURCES_READ]


class TestCanReadAvailability:
    """Tests for CanReadAvailability permission class."""

    def test_returns_availability_read_scope(self):
        """Should require availability:read scope."""
        permission = CanReadAvailability()

        request = Mock()
        view = Mock()

        scopes = permission._get_required_scopes(request, view)

        assert scopes == [APIScope.AVAILABILITY_READ]


class TestCanReadBookings:
    """Tests for CanReadBookings permission class."""

    def test_returns_bookings_read_scope(self):
        """Should require bookings:read scope."""
        permission = CanReadBookings()

        request = Mock()
        view = Mock()

        scopes = permission._get_required_scopes(request, view)

        assert scopes == [APIScope.BOOKINGS_READ]


class TestCanWriteBookings:
    """Tests for CanWriteBookings permission class."""

    def test_returns_bookings_write_scope(self):
        """Should require bookings:write scope."""
        permission = CanWriteBookings()

        request = Mock()
        view = Mock()

        scopes = permission._get_required_scopes(request, view)

        assert scopes == [APIScope.BOOKINGS_WRITE]


class TestCanReadCustomers:
    """Tests for CanReadCustomers permission class."""

    def test_returns_customers_read_scope(self):
        """Should require customers:read scope."""
        permission = CanReadCustomers()

        request = Mock()
        view = Mock()

        scopes = permission._get_required_scopes(request, view)

        assert scopes == [APIScope.CUSTOMERS_READ]


class TestCanWriteCustomers:
    """Tests for CanWriteCustomers permission class."""

    def test_returns_customers_write_scope(self):
        """Should require customers:write scope."""
        permission = CanWriteCustomers()

        request = Mock()
        view = Mock()

        scopes = permission._get_required_scopes(request, view)

        assert scopes == [APIScope.CUSTOMERS_WRITE]


class TestCanReadBusiness:
    """Tests for CanReadBusiness permission class."""

    def test_returns_business_read_scope(self):
        """Should require business:read scope."""
        permission = CanReadBusiness()

        request = Mock()
        view = Mock()

        scopes = permission._get_required_scopes(request, view)

        assert scopes == [APIScope.BUSINESS_READ]


class TestCanManageWebhooks:
    """Tests for CanManageWebhooks permission class."""

    def test_returns_webhooks_manage_scope(self):
        """Should require webhooks:manage scope."""
        permission = CanManageWebhooks()

        request = Mock()
        view = Mock()

        scopes = permission._get_required_scopes(request, view)

        assert scopes == [APIScope.WEBHOOKS_MANAGE]


class TestBookingsReadWritePermission:
    """Tests for BookingsReadWritePermission class."""

    def test_get_request_requires_read_scope(self):
        """GET requests should require bookings:read scope."""
        permission = BookingsReadWritePermission()

        request = Mock()
        request.method = 'GET'
        view = Mock()

        scopes = permission._get_required_scopes(request, view)

        assert scopes == [APIScope.BOOKINGS_READ]

    def test_head_request_requires_read_scope(self):
        """HEAD requests should require bookings:read scope."""
        permission = BookingsReadWritePermission()

        request = Mock()
        request.method = 'HEAD'
        view = Mock()

        scopes = permission._get_required_scopes(request, view)

        assert scopes == [APIScope.BOOKINGS_READ]

    def test_options_request_requires_read_scope(self):
        """OPTIONS requests should require bookings:read scope."""
        permission = BookingsReadWritePermission()

        request = Mock()
        request.method = 'OPTIONS'
        view = Mock()

        scopes = permission._get_required_scopes(request, view)

        assert scopes == [APIScope.BOOKINGS_READ]

    def test_post_request_requires_write_scope(self):
        """POST requests should require bookings:write scope."""
        permission = BookingsReadWritePermission()

        request = Mock()
        request.method = 'POST'
        view = Mock()

        scopes = permission._get_required_scopes(request, view)

        assert scopes == [APIScope.BOOKINGS_WRITE]

    def test_patch_request_requires_write_scope(self):
        """PATCH requests should require bookings:write scope."""
        permission = BookingsReadWritePermission()

        request = Mock()
        request.method = 'PATCH'
        view = Mock()

        scopes = permission._get_required_scopes(request, view)

        assert scopes == [APIScope.BOOKINGS_WRITE]

    def test_delete_request_requires_write_scope(self):
        """DELETE requests should require bookings:write scope."""
        permission = BookingsReadWritePermission()

        request = Mock()
        request.method = 'DELETE'
        view = Mock()

        scopes = permission._get_required_scopes(request, view)

        assert scopes == [APIScope.BOOKINGS_WRITE]


class TestCustomersReadWritePermission:
    """Tests for CustomersReadWritePermission class."""

    def test_get_request_requires_read_scope(self):
        """GET requests should require customers:read scope."""
        permission = CustomersReadWritePermission()

        request = Mock()
        request.method = 'GET'
        view = Mock()

        scopes = permission._get_required_scopes(request, view)

        assert scopes == [APIScope.CUSTOMERS_READ]

    def test_post_request_requires_write_scope(self):
        """POST requests should require customers:write scope."""
        permission = CustomersReadWritePermission()

        request = Mock()
        request.method = 'POST'
        view = Mock()

        scopes = permission._get_required_scopes(request, view)

        assert scopes == [APIScope.CUSTOMERS_WRITE]

    def test_put_request_requires_write_scope(self):
        """PUT requests should require customers:write scope."""
        permission = CustomersReadWritePermission()

        request = Mock()
        request.method = 'PUT'
        view = Mock()

        scopes = permission._get_required_scopes(request, view)

        assert scopes == [APIScope.CUSTOMERS_WRITE]
