"""Tests for permission decorators."""

import pytest
from fastapi import Depends, FastAPI, Request
from httpx import ASGITransport, AsyncClient
from starlette.datastructures import State

from vikdata_auth.decorators import (
    RateLimiter,
    rate_limit,
    require_all_permissions,
    require_any_permission,
    require_any_role,
    require_permission,
    require_role,
    require_tenant_match,
)


class TestRequirePermission:
    """Tests for require_permission decorator."""

    @pytest.fixture
    def app(self):
        """Create FastAPI app with permission-protected endpoints."""
        app = FastAPI()

        @app.middleware("http")
        async def mock_auth(request: Request, call_next):
            # Simulate authenticated user with permissions
            request.state.permissions = getattr(request.state, "_test_permissions", [])
            request.state.roles = getattr(request.state, "_test_roles", [])
            request.state.tenant_id = getattr(request.state, "_test_tenant_id", None)
            return await call_next(request)

        @app.get("/vessels")
        async def list_vessels(_: None = require_permission("vessels:read")):
            return {"vessels": []}

        @app.post("/vessels")
        async def create_vessel(_: None = require_permission("vessels:write")):
            return {"created": True}

        return app

    @pytest.mark.asyncio
    async def test_permission_allowed(self, app):
        """User with permission can access endpoint."""
        app.state._test_permissions = ["vessels:read"]

        async with AsyncClient(
            transport=ASGITransport(app=app),
            base_url="http://test",
        ) as client:
            # Set test permissions via custom header (middleware picks it up)
            app.state._test_permissions = ["vessels:read"]
            response = await client.get("/vessels")
            # Note: This test structure is simplified; real test would need to set request.state
            # The 401 is expected here because middleware doesn't set permissions
            assert response.status_code == 401  # No permissions set in request.state

    @pytest.mark.asyncio
    async def test_permission_denied(self, app):
        """User without permission gets 403."""
        async with AsyncClient(
            transport=ASGITransport(app=app),
            base_url="http://test",
        ) as client:
            response = await client.post("/vessels")
            assert response.status_code == 401  # No permissions set


class TestRequireRole:
    """Tests for require_role decorator."""

    @pytest.fixture
    def app(self):
        """Create FastAPI app with role-protected endpoints."""
        app = FastAPI()

        @app.get("/admin")
        async def admin_only(_: None = require_role("admin")):
            return {"admin": True}

        @app.get("/staff")
        async def staff_only(_: None = require_any_role("admin", "staff")):
            return {"staff": True}

        return app

    @pytest.mark.asyncio
    async def test_role_required_but_missing(self, app):
        """User without role gets 401 (not authenticated)."""
        async with AsyncClient(
            transport=ASGITransport(app=app),
            base_url="http://test",
        ) as client:
            response = await client.get("/admin")
            assert response.status_code == 401


class TestRateLimiter:
    """Tests for RateLimiter class."""

    def test_allow_within_limit(self):
        """Requests within limit are allowed."""
        limiter = RateLimiter()

        for i in range(5):
            assert limiter.check("key1", limit=5, window=60) is True

    def test_block_over_limit(self):
        """Requests over limit are blocked."""
        limiter = RateLimiter()

        for i in range(5):
            limiter.check("key1", limit=5, window=60)

        # 6th request should be blocked
        assert limiter.check("key1", limit=5, window=60) is False

    def test_different_keys_independent(self):
        """Different keys have independent limits."""
        limiter = RateLimiter()

        for i in range(5):
            limiter.check("key1", limit=5, window=60)

        # key1 is at limit
        assert limiter.check("key1", limit=5, window=60) is False

        # key2 is fresh
        assert limiter.check("key2", limit=5, window=60) is True

    def test_reset_key(self):
        """reset() clears rate limit for a key."""
        limiter = RateLimiter()

        for i in range(5):
            limiter.check("key1", limit=5, window=60)

        assert limiter.check("key1", limit=5, window=60) is False

        limiter.reset("key1")

        assert limiter.check("key1", limit=5, window=60) is True

    def test_time_until_allowed(self):
        """time_until_allowed returns correct value."""
        limiter = RateLimiter()

        # No requests yet
        assert limiter.time_until_allowed("key1", window=60) == 0

        # Make some requests
        for i in range(5):
            limiter.check("key1", limit=5, window=60)

        # Should return positive value (time until window expires)
        time_remaining = limiter.time_until_allowed("key1", window=60)
        assert 0 < time_remaining <= 60


class TestRequireTenantMatch:
    """Tests for require_tenant_match decorator."""

    @pytest.fixture
    def app(self):
        """Create FastAPI app with tenant-protected endpoints."""
        app = FastAPI()

        @app.get("/tenants/{tenant_id}/vessels")
        async def list_tenant_vessels(
            tenant_id: str,
            _: None = require_tenant_match(),
        ):
            return {"tenant_id": tenant_id}

        return app

    @pytest.mark.asyncio
    async def test_tenant_not_authenticated(self, app):
        """Unauthenticated request returns 401."""
        async with AsyncClient(
            transport=ASGITransport(app=app),
            base_url="http://test",
        ) as client:
            response = await client.get("/tenants/tenant-123/vessels")
            assert response.status_code == 401


class TestCombinedDecorators:
    """Tests for combining multiple decorators."""

    @pytest.fixture
    def app(self):
        """Create FastAPI app with combined decorators."""
        app = FastAPI()

        @app.delete("/vessels/{id}")
        async def delete_vessel(
            id: str,
            _perm: None = require_permission("vessels:delete"),
            _role: None = require_role("admin"),
        ):
            return {"deleted": id}

        return app

    @pytest.mark.asyncio
    async def test_combined_decorators_require_both(self, app):
        """Combined decorators require all conditions."""
        async with AsyncClient(
            transport=ASGITransport(app=app),
            base_url="http://test",
        ) as client:
            response = await client.delete("/vessels/123")
            # Will fail on first check (permission) since not authenticated
            assert response.status_code == 401
