"""Tests for API key authentication."""

import hashlib
import pytest
from unittest.mock import AsyncMock, MagicMock, patch

from aiohttp import web

from backend.proxy.auth import authenticate, AuthContext


@pytest.fixture
def mock_db():
    return AsyncMock()


class TestAuthenticate:
    @pytest.mark.asyncio
    async def test_missing_auth_header(self):
        request = MagicMock()
        request.headers = {}
        
        with pytest.raises(web.HTTPUnauthorized):
            await authenticate(request)

    @pytest.mark.asyncio
    async def test_invalid_auth_format(self):
        request = MagicMock()
        request.headers = {"Authorization": "Basic abc123"}
        
        with pytest.raises(web.HTTPUnauthorized):
            await authenticate(request)

    @pytest.mark.asyncio
    async def test_empty_token(self):
        request = MagicMock()
        request.headers = {"Authorization": "Bearer "}
        
        with pytest.raises(web.HTTPUnauthorized):
            await authenticate(request)

    @pytest.mark.asyncio
    async def test_invalid_api_key(self, mock_db):
        request = MagicMock()
        request.headers = {"Authorization": "Bearer invalid-key"}
        
        mock_db.execute_fetchall = AsyncMock(return_value=[])
        
        with patch("backend.proxy.auth.get_db", return_value=mock_db):
            with pytest.raises(web.HTTPUnauthorized) as exc_info:
                await authenticate(request)
            assert "Invalid API key" in exc_info.value.text

    @pytest.mark.asyncio
    async def test_valid_api_key(self, mock_db):
        token = "ek-validkey123"
        key_hash = hashlib.sha256(token.encode()).hexdigest()
        
        request = MagicMock()
        request.headers = {"Authorization": f"Bearer {token}"}
        
        mock_row = {
            "id": "key-001",
            "name": "test-key",
            "default_strategy": "auto",
            "allowed_models": None,
            "daily_budget_usd": None,
            "monthly_budget_usd": None,
        }
        mock_db.execute_fetchall = AsyncMock(return_value=[mock_row])
        
        with patch("backend.proxy.auth.get_db", return_value=mock_db):
            ctx = await authenticate(request)
        
        assert isinstance(ctx, AuthContext)
        assert ctx.key_id == "key-001"
        assert ctx.name == "test-key"
        assert ctx.strategy == "auto"
        assert ctx.allowed_models is None

    @pytest.mark.asyncio
    async def test_allowed_models_parsed(self, mock_db):
        token = "ek-test"
        request = MagicMock()
        request.headers = {"Authorization": f"Bearer {token}"}
        
        mock_row = {
            "id": "key-001",
            "name": "test",
            "default_strategy": "best",
            "allowed_models": '["openai/gpt-4o", "anthropic/claude-sonnet"]',
            "daily_budget_usd": None,
            "monthly_budget_usd": None,
        }
        mock_db.execute_fetchall = AsyncMock(return_value=[mock_row])
        
        with patch("backend.proxy.auth.get_db", return_value=mock_db):
            ctx = await authenticate(request)
        
        assert ctx.allowed_models == ["openai/gpt-4o", "anthropic/claude-sonnet"]

    @pytest.mark.asyncio
    async def test_daily_budget_exceeded(self, mock_db):
        token = "ek-test"
        request = MagicMock()
        request.headers = {"Authorization": f"Bearer {token}"}
        
        mock_row = {
            "id": "key-001",
            "name": "test",
            "default_strategy": "auto",
            "allowed_models": None,
            "daily_budget_usd": 10.0,
            "monthly_budget_usd": None,
        }
        
        # First call returns the key, second returns spent amount
        mock_db.execute_fetchall = AsyncMock(side_effect=[
            [mock_row],
            [{"spent": 15.0}],  # Over budget
        ])
        
        with patch("backend.proxy.auth.get_db", return_value=mock_db):
            with pytest.raises(web.HTTPTooManyRequests) as exc_info:
                await authenticate(request)
            assert "Daily budget exceeded" in exc_info.value.text

    @pytest.mark.asyncio
    async def test_monthly_budget_exceeded(self, mock_db):
        token = "ek-test"
        request = MagicMock()
        request.headers = {"Authorization": f"Bearer {token}"}
        
        mock_row = {
            "id": "key-001",
            "name": "test",
            "default_strategy": "auto",
            "allowed_models": None,
            "daily_budget_usd": None,
            "monthly_budget_usd": 100.0,
        }
        
        mock_db.execute_fetchall = AsyncMock(side_effect=[
            [mock_row],
            [{"spent": 150.0}],  # Over budget
        ])
        
        with patch("backend.proxy.auth.get_db", return_value=mock_db):
            with pytest.raises(web.HTTPTooManyRequests) as exc_info:
                await authenticate(request)
            assert "Monthly budget exceeded" in exc_info.value.text


class TestAuthContext:
    def test_auth_context_fields(self):
        ctx = AuthContext(
            key_id="k1",
            name="test",
            strategy="best",
            allowed_models=["model-a"],
            daily_budget_usd=10.0,
            monthly_budget_usd=100.0,
        )
        
        assert ctx.key_id == "k1"
        assert ctx.name == "test"
        assert ctx.strategy == "best"
        assert ctx.allowed_models == ["model-a"]
        assert ctx.daily_budget_usd == 10.0
        assert ctx.monthly_budget_usd == 100.0
