"""Tests for the settings router."""

from unittest.mock import AsyncMock, MagicMock, patch

import httpx
import pytest

# ============================================================================
# Provider Endpoint Tests
# ============================================================================


class TestListProviders:
    """Tests for GET /api/settings/providers."""

    async def test_list_providers_empty(self, auth_client):
        """Returns empty list when no providers configured."""
        response = await auth_client.get("/api/settings/providers")
        assert response.status_code == 200
        assert response.json() == []

    async def test_list_providers_with_data(self, auth_client):
        """Returns list of configured providers."""
        # First create a provider
        await auth_client.post(
            "/api/settings/providers",
            json={
                "backend_type": "openai",
                "api_key": "sk-test123456789",
            },
        )

        response = await auth_client.get("/api/settings/providers")
        assert response.status_code == 200
        data = response.json()
        assert len(data) == 1
        assert data[0]["backend_type"] == "openai"
        # API key should NOT be returned
        assert "api_key" not in data[0]
        assert "encrypted_api_key" not in data[0]

    async def test_list_providers_requires_auth(self, client):
        """Requires authentication."""
        response = await client.get("/api/settings/providers")
        assert response.status_code == 401


class TestCreateOrUpdateProvider:
    """Tests for POST /api/settings/providers."""

    async def test_create_openai_provider(self, auth_client):
        """Creates an OpenAI provider."""
        response = await auth_client.post(
            "/api/settings/providers",
            json={
                "backend_type": "openai",
                "api_key": "sk-test123456789",
            },
        )
        assert response.status_code == 200
        data = response.json()
        assert data["backend_type"] == "openai"
        assert data["base_url"] is None
        # API key should NOT be returned
        assert "api_key" not in data
        assert "encrypted_api_key" not in data

    async def test_create_anthropic_provider(self, auth_client):
        """Creates an Anthropic provider."""
        response = await auth_client.post(
            "/api/settings/providers",
            json={
                "backend_type": "anthropic",
                "api_key": "sk-ant-test123456789",
            },
        )
        assert response.status_code == 200
        data = response.json()
        assert data["backend_type"] == "anthropic"

    async def test_create_provider_with_base_url(self, auth_client):
        """Creates a provider with custom base URL."""
        response = await auth_client.post(
            "/api/settings/providers",
            json={
                "backend_type": "openai",
                "api_key": "sk-test123456789",
                "base_url": "https://api.custom.com",
            },
        )
        assert response.status_code == 200
        data = response.json()
        assert data["base_url"] == "https://api.custom.com"

    async def test_update_existing_provider(self, auth_client):
        """Updates an existing provider (upsert)."""
        # Create initial provider
        await auth_client.post(
            "/api/settings/providers",
            json={
                "backend_type": "openai",
                "api_key": "sk-original-key",
            },
        )

        # Update the same provider
        response = await auth_client.post(
            "/api/settings/providers",
            json={
                "backend_type": "openai",
                "api_key": "sk-new-key-12345",
                "base_url": "https://api.updated.com",
            },
        )
        assert response.status_code == 200
        data = response.json()
        assert data["base_url"] == "https://api.updated.com"

        # Verify only one provider exists
        list_response = await auth_client.get("/api/settings/providers")
        assert len(list_response.json()) == 1


class TestDeleteProvider:
    """Tests for DELETE /api/settings/providers/{backend_type}."""

    async def test_delete_provider(self, auth_client):
        """Deletes an existing provider."""
        # Create provider first
        await auth_client.post(
            "/api/settings/providers",
            json={"backend_type": "openai", "api_key": "sk-test"},
        )

        response = await auth_client.delete("/api/settings/providers/openai")
        assert response.status_code == 204

        # Verify it's gone
        list_response = await auth_client.get("/api/settings/providers")
        assert list_response.json() == []

    async def test_delete_nonexistent_provider(self, auth_client):
        """Returns 404 for nonexistent provider."""
        response = await auth_client.delete("/api/settings/providers/openai")
        assert response.status_code == 404


class TestTestProviderConnection:
    """Tests for POST /api/settings/providers/{backend_type}/test."""

    async def test_provider_not_configured(self, auth_client):
        """Returns 404 when provider not configured."""
        response = await auth_client.post("/api/settings/providers/openai/test")
        assert response.status_code == 404

    async def test_test_openai_success(self, auth_client):
        """Tests successful OpenAI connection."""
        # Create provider first
        await auth_client.post(
            "/api/settings/providers",
            json={"backend_type": "openai", "api_key": "sk-test-valid"},
        )

        # Mock httpx to return success
        mock_response = AsyncMock()
        mock_response.status_code = 200

        with patch("mlx_manager.routers.settings.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.return_value = mock_response
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            response = await auth_client.post("/api/settings/providers/openai/test")
            assert response.status_code == 200
            assert response.json() == {"success": True}

    async def test_test_openai_with_custom_base_url(self, auth_client):
        """Tests OpenAI connection with custom base URL."""
        await auth_client.post(
            "/api/settings/providers",
            json={
                "backend_type": "openai",
                "api_key": "sk-test",
                "base_url": "https://custom-api.example.com",
            },
        )

        mock_response = AsyncMock()
        mock_response.status_code = 200

        with patch("mlx_manager.routers.settings.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.return_value = mock_response
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            response = await auth_client.post("/api/settings/providers/openai/test")
            assert response.status_code == 200
            # Verify the custom base URL was used
            mock_instance.get.assert_called_once()
            call_args = mock_instance.get.call_args
            assert "https://custom-api.example.com/v1/models" in str(call_args)

    async def test_test_openai_invalid_key(self, auth_client):
        """Tests OpenAI with invalid API key."""
        await auth_client.post(
            "/api/settings/providers",
            json={"backend_type": "openai", "api_key": "sk-invalid"},
        )

        mock_response = AsyncMock()
        mock_response.status_code = 401

        with patch("mlx_manager.routers.settings.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.return_value = mock_response
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            response = await auth_client.post("/api/settings/providers/openai/test")
            assert response.status_code == 400
            assert "Invalid API key" in response.json()["detail"]

    async def test_test_openai_forbidden(self, auth_client):
        """Tests OpenAI with insufficient permissions (403)."""
        await auth_client.post(
            "/api/settings/providers",
            json={"backend_type": "openai", "api_key": "sk-noperm"},
        )

        mock_response = AsyncMock()
        mock_response.status_code = 403

        with patch("mlx_manager.routers.settings.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.return_value = mock_response
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            response = await auth_client.post("/api/settings/providers/openai/test")
            assert response.status_code == 400
            assert "does not have permission" in response.json()["detail"]

    async def test_test_openai_other_error(self, auth_client):
        """Tests OpenAI with other HTTP error status."""
        await auth_client.post(
            "/api/settings/providers",
            json={"backend_type": "openai", "api_key": "sk-error"},
        )

        mock_response = AsyncMock()
        mock_response.status_code = 500

        with patch("mlx_manager.routers.settings.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.return_value = mock_response
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            response = await auth_client.post("/api/settings/providers/openai/test")
            assert response.status_code == 400
            assert "Provider returned status 500" in response.json()["detail"]

    async def test_test_anthropic_success(self, auth_client):
        """Tests successful Anthropic connection."""
        await auth_client.post(
            "/api/settings/providers",
            json={"backend_type": "anthropic", "api_key": "sk-ant-valid"},
        )

        mock_response = AsyncMock()
        mock_response.status_code = 200

        with patch("mlx_manager.routers.settings.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.return_value = mock_response
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            response = await auth_client.post("/api/settings/providers/anthropic/test")
            assert response.status_code == 200
            assert response.json() == {"success": True}

    async def test_test_anthropic_with_custom_base_url(self, auth_client):
        """Tests Anthropic connection with custom base URL."""
        await auth_client.post(
            "/api/settings/providers",
            json={
                "backend_type": "anthropic",
                "api_key": "sk-ant-test",
                "base_url": "https://custom-anthropic.example.com",
            },
        )

        mock_response = AsyncMock()
        mock_response.status_code = 200

        with patch("mlx_manager.routers.settings.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.return_value = mock_response
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            response = await auth_client.post("/api/settings/providers/anthropic/test")
            assert response.status_code == 200

    async def test_test_connection_timeout(self, auth_client):
        """Handles connection timeout gracefully."""
        await auth_client.post(
            "/api/settings/providers",
            json={"backend_type": "openai", "api_key": "sk-test"},
        )

        with patch("mlx_manager.routers.settings.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.side_effect = httpx.TimeoutException("timeout")
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            response = await auth_client.post("/api/settings/providers/openai/test")
            assert response.status_code == 400
            assert "timed out" in response.json()["detail"]

    async def test_test_connection_error(self, auth_client):
        """Handles connection error gracefully."""
        await auth_client.post(
            "/api/settings/providers",
            json={"backend_type": "openai", "api_key": "sk-test"},
        )

        with patch("mlx_manager.routers.settings.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.side_effect = httpx.ConnectError("connect error")
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            response = await auth_client.post("/api/settings/providers/openai/test")
            assert response.status_code == 400
            assert "Could not connect" in response.json()["detail"]


# ============================================================================
# Routing Rules Endpoint Tests
# ============================================================================


class TestListRules:
    """Tests for GET /api/settings/rules."""

    async def test_list_rules_empty(self, auth_client):
        """Returns empty list when no rules configured."""
        response = await auth_client.get("/api/settings/rules")
        assert response.status_code == 200
        assert response.json() == []

    async def test_list_rules_ordered_by_priority(self, auth_client):
        """Returns rules ordered by priority (highest first)."""
        # Create rules with different priorities
        await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "gpt-*",
                "backend_type": "openai",
                "priority": 50,
            },
        )
        await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "claude-*",
                "backend_type": "anthropic",
                "priority": 100,
            },
        )

        response = await auth_client.get("/api/settings/rules")
        data = response.json()
        assert len(data) == 2
        # Highest priority first
        assert data[0]["model_pattern"] == "claude-*"
        assert data[0]["priority"] == 100
        assert data[1]["model_pattern"] == "gpt-*"
        assert data[1]["priority"] == 50


class TestCreateRule:
    """Tests for POST /api/settings/rules."""

    async def test_create_exact_rule(self, auth_client):
        """Creates a rule with exact pattern matching."""
        response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "gpt-4",
                "pattern_type": "exact",
                "backend_type": "openai",
            },
        )
        assert response.status_code == 201
        data = response.json()
        assert data["model_pattern"] == "gpt-4"
        assert data["pattern_type"] == "exact"
        assert data["backend_type"] == "openai"
        assert data["enabled"] is True

    async def test_create_prefix_rule(self, auth_client):
        """Creates a rule with prefix pattern matching."""
        response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "claude-",
                "pattern_type": "prefix",
                "backend_type": "anthropic",
            },
        )
        assert response.status_code == 201
        data = response.json()
        assert data["pattern_type"] == "prefix"

    async def test_create_regex_rule(self, auth_client):
        """Creates a rule with regex pattern matching."""
        response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "^gpt-[34].*",
                "pattern_type": "regex",
                "backend_type": "openai",
            },
        )
        assert response.status_code == 201
        data = response.json()
        assert data["pattern_type"] == "regex"

    async def test_create_rule_invalid_regex(self, auth_client):
        """Rejects invalid regex patterns."""
        response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "[invalid(regex",
                "pattern_type": "regex",
                "backend_type": "openai",
            },
        )
        assert response.status_code == 400
        assert "Invalid regex" in response.json()["detail"]

    async def test_create_rule_invalid_pattern_type(self, auth_client):
        """Rejects invalid pattern type."""
        response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "test",
                "pattern_type": "invalid",
                "backend_type": "openai",
            },
        )
        assert response.status_code == 422  # Pydantic validation error for invalid enum

    async def test_create_rule_with_fallback(self, auth_client):
        """Creates a rule with fallback backend."""
        response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "gpt-4",
                "backend_type": "openai",
                "fallback_backend": "local",
            },
        )
        assert response.status_code == 201
        data = response.json()
        assert data["fallback_backend"] == "local"

    async def test_create_rule_with_backend_model(self, auth_client):
        """Creates a rule with backend model override."""
        response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "my-gpt4",
                "backend_type": "openai",
                "backend_model": "gpt-4-turbo-preview",
            },
        )
        assert response.status_code == 201
        data = response.json()
        assert data["backend_model"] == "gpt-4-turbo-preview"


class TestUpdateRule:
    """Tests for PUT /api/settings/rules/{rule_id}."""

    async def test_update_rule(self, auth_client):
        """Updates an existing rule."""
        # Create rule first
        create_response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "test",
                "backend_type": "openai",
            },
        )
        rule_id = create_response.json()["id"]

        response = await auth_client.put(
            f"/api/settings/rules/{rule_id}",
            json={
                "model_pattern": "updated-pattern",
                "priority": 100,
            },
        )
        assert response.status_code == 200
        data = response.json()
        assert data["model_pattern"] == "updated-pattern"
        assert data["priority"] == 100

    async def test_update_rule_not_found(self, auth_client):
        """Returns 404 for nonexistent rule."""
        response = await auth_client.put(
            "/api/settings/rules/99999",
            json={"model_pattern": "test"},
        )
        assert response.status_code == 404

    async def test_update_rule_to_invalid_regex(self, auth_client):
        """Rejects update with invalid regex."""
        create_response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "test",
                "pattern_type": "regex",
                "backend_type": "openai",
            },
        )
        rule_id = create_response.json()["id"]

        response = await auth_client.put(
            f"/api/settings/rules/{rule_id}",
            json={"model_pattern": "[invalid"},
        )
        assert response.status_code == 400

    async def test_update_rule_invalid_pattern_type(self, auth_client):
        """Rejects update with invalid pattern type."""
        create_response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "test",
                "backend_type": "openai",
            },
        )
        rule_id = create_response.json()["id"]

        response = await auth_client.put(
            f"/api/settings/rules/{rule_id}",
            json={"pattern_type": "invalid_type"},
        )
        assert response.status_code == 422  # Pydantic validation error for invalid enum
        # Pydantic provides its own validation error message for enums

    async def test_update_rule_change_pattern_type_to_regex(self, auth_client):
        """Updates rule from exact to regex pattern type."""
        create_response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "gpt-4",
                "pattern_type": "exact",
                "backend_type": "openai",
            },
        )
        rule_id = create_response.json()["id"]

        response = await auth_client.put(
            f"/api/settings/rules/{rule_id}",
            json={
                "pattern_type": "regex",
                "model_pattern": "^gpt-[34].*",
            },
        )
        assert response.status_code == 200
        data = response.json()
        assert data["pattern_type"] == "regex"
        assert data["model_pattern"] == "^gpt-[34].*"

    async def test_update_rule_change_to_regex_with_invalid_pattern(self, auth_client):
        """Rejects update to regex with invalid pattern."""
        create_response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "test",
                "pattern_type": "exact",
                "backend_type": "openai",
            },
        )
        rule_id = create_response.json()["id"]

        response = await auth_client.put(
            f"/api/settings/rules/{rule_id}",
            json={
                "pattern_type": "regex",
                "model_pattern": "[invalid(",
            },
        )
        assert response.status_code == 400
        assert "Invalid regex" in response.json()["detail"]

    async def test_update_rule_enable_disable(self, auth_client):
        """Can enable/disable a rule."""
        create_response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "test",
                "backend_type": "openai",
            },
        )
        rule_id = create_response.json()["id"]

        response = await auth_client.put(
            f"/api/settings/rules/{rule_id}",
            json={"enabled": False},
        )
        assert response.status_code == 200
        assert response.json()["enabled"] is False


class TestDeleteRule:
    """Tests for DELETE /api/settings/rules/{rule_id}."""

    async def test_delete_rule(self, auth_client):
        """Deletes an existing rule."""
        create_response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "test",
                "backend_type": "openai",
            },
        )
        rule_id = create_response.json()["id"]

        response = await auth_client.delete(f"/api/settings/rules/{rule_id}")
        assert response.status_code == 204

        # Verify it's gone
        list_response = await auth_client.get("/api/settings/rules")
        assert list_response.json() == []

    async def test_delete_rule_not_found(self, auth_client):
        """Returns 404 for nonexistent rule."""
        response = await auth_client.delete("/api/settings/rules/99999")
        assert response.status_code == 404


class TestUpdateRulePriorities:
    """Tests for PUT /api/settings/rules/priorities."""

    async def test_batch_update_priorities(self, auth_client):
        """Updates multiple rule priorities at once."""
        # Create rules
        r1 = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "rule1",
                "backend_type": "openai",
                "priority": 10,
            },
        )
        r2 = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "rule2",
                "backend_type": "anthropic",
                "priority": 20,
            },
        )
        id1, id2 = r1.json()["id"], r2.json()["id"]

        # Update priorities
        response = await auth_client.put(
            "/api/settings/rules/priorities",
            json=[
                {"id": id1, "priority": 100},
                {"id": id2, "priority": 50},
            ],
        )
        assert response.status_code == 200
        assert response.json()["updated"] == 2

        # Verify new order
        list_response = await auth_client.get("/api/settings/rules")
        data = list_response.json()
        assert data[0]["id"] == id1  # Higher priority now
        assert data[0]["priority"] == 100

    async def test_batch_update_with_nonexistent_rule(self, auth_client):
        """Updates priorities silently skipping nonexistent rules."""
        r1 = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "rule1",
                "backend_type": "openai",
                "priority": 10,
            },
        )
        id1 = r1.json()["id"]

        # Include nonexistent rule ID
        response = await auth_client.put(
            "/api/settings/rules/priorities",
            json=[
                {"id": id1, "priority": 100},
                {"id": 99999, "priority": 50},  # Nonexistent
            ],
        )
        assert response.status_code == 200
        # Only existing rule should be updated
        assert response.json()["updated"] == 2  # Count includes the attempt

    async def test_batch_update_empty_list(self, auth_client):
        """Handles empty update list."""
        response = await auth_client.put(
            "/api/settings/rules/priorities",
            json=[],
        )
        assert response.status_code == 200
        assert response.json()["updated"] == 0


class TestTestRuleMatch:
    """Tests for POST /api/settings/rules/test."""

    async def test_exact_match(self, auth_client):
        """Tests exact pattern matching."""
        await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "gpt-4",
                "pattern_type": "exact",
                "backend_type": "openai",
            },
        )

        response = await auth_client.post(
            "/api/settings/rules/test",
            params={"model_name": "gpt-4"},
        )
        assert response.status_code == 200
        data = response.json()
        assert data["backend_type"] == "openai"
        assert data["pattern_matched"] == "gpt-4"

    async def test_exact_no_match(self, auth_client):
        """Tests exact pattern no match."""
        await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "gpt-4",
                "pattern_type": "exact",
                "backend_type": "openai",
            },
        )

        response = await auth_client.post(
            "/api/settings/rules/test",
            params={"model_name": "gpt-4-turbo"},
        )
        assert response.status_code == 200
        data = response.json()
        assert data["matched_rule_id"] is None
        assert data["backend_type"] == "local"

    async def test_prefix_match(self, auth_client):
        """Tests prefix pattern matching."""
        await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "claude-",
                "pattern_type": "prefix",
                "backend_type": "anthropic",
            },
        )

        response = await auth_client.post(
            "/api/settings/rules/test",
            params={"model_name": "claude-3-opus"},
        )
        assert response.status_code == 200
        data = response.json()
        assert data["backend_type"] == "anthropic"

    async def test_prefix_no_match(self, auth_client):
        """Tests prefix pattern no match."""
        await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "claude-",
                "pattern_type": "prefix",
                "backend_type": "anthropic",
            },
        )

        response = await auth_client.post(
            "/api/settings/rules/test",
            params={"model_name": "gpt-4"},
        )
        assert response.status_code == 200
        data = response.json()
        assert data["backend_type"] == "local"

    async def test_regex_match(self, auth_client):
        """Tests regex pattern matching."""
        await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "^gpt-[34].*",
                "pattern_type": "regex",
                "backend_type": "openai",
            },
        )

        response = await auth_client.post(
            "/api/settings/rules/test",
            params={"model_name": "gpt-4-turbo"},
        )
        assert response.status_code == 200
        data = response.json()
        assert data["backend_type"] == "openai"

    async def test_regex_no_match(self, auth_client):
        """Tests regex pattern no match."""
        await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "^gpt-[34].*",
                "pattern_type": "regex",
                "backend_type": "openai",
            },
        )

        response = await auth_client.post(
            "/api/settings/rules/test",
            params={"model_name": "claude-opus"},
        )
        assert response.status_code == 200
        data = response.json()
        assert data["backend_type"] == "local"

    async def test_regex_invalid_pattern_returns_no_match(self, auth_client):
        """Tests that invalid regex pattern stored in DB returns false for matches."""
        # This tests the _matches_pattern function's error handling
        # We need to bypass validation to create an invalid regex in the DB
        # In practice, this would be caught at creation time, but we test the safety net
        await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "valid-pattern",
                "pattern_type": "exact",
                "backend_type": "openai",
            },
        )

        # Test with a valid model name
        response = await auth_client.post(
            "/api/settings/rules/test",
            params={"model_name": "test-model"},
        )
        assert response.status_code == 200

    async def test_priority_order(self, auth_client):
        """Tests that higher priority rules match first."""
        # Lower priority rule
        await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "gpt-",
                "pattern_type": "prefix",
                "backend_type": "local",
                "priority": 10,
            },
        )
        # Higher priority rule
        await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "gpt-4",
                "pattern_type": "exact",
                "backend_type": "openai",
                "priority": 100,
            },
        )

        response = await auth_client.post(
            "/api/settings/rules/test",
            params={"model_name": "gpt-4"},
        )
        data = response.json()
        # Higher priority exact match wins
        assert data["backend_type"] == "openai"

    async def test_disabled_rules_ignored(self, auth_client):
        """Tests that disabled rules are not matched."""
        create_response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "gpt-4",
                "pattern_type": "exact",
                "backend_type": "openai",
            },
        )
        rule_id = create_response.json()["id"]

        # Disable the rule
        await auth_client.put(
            f"/api/settings/rules/{rule_id}",
            json={"enabled": False},
        )

        response = await auth_client.post(
            "/api/settings/rules/test",
            params={"model_name": "gpt-4"},
        )
        data = response.json()
        # Should fall back to local since rule is disabled
        assert data["matched_rule_id"] is None
        assert data["backend_type"] == "local"

    async def test_no_rules_defaults_to_local(self, auth_client):
        """Tests that when no rules exist, defaults to local backend."""
        response = await auth_client.post(
            "/api/settings/rules/test",
            params={"model_name": "any-model"},
        )
        assert response.status_code == 200
        data = response.json()
        assert data["matched_rule_id"] is None
        assert data["backend_type"] == "local"
        assert data["pattern_matched"] is None


# ============================================================================
# Pool Configuration Endpoint Tests
# ============================================================================


class TestGetPoolConfig:
    """Tests for GET /api/settings/pool."""

    async def test_get_default_config(self, auth_client):
        """Returns default config when none exists."""
        response = await auth_client.get("/api/settings/pool")
        assert response.status_code == 200
        data = response.json()
        assert data["memory_limit_mode"] == "percent"
        assert data["memory_limit_value"] == 80
        assert data["eviction_policy"] == "lru"
        assert data["preloaded_profiles"] == []

    async def test_get_existing_config(self, auth_client):
        """Returns existing config after update."""
        # Update config
        await auth_client.put(
            "/api/settings/pool",
            json={
                "memory_limit_mode": "gb",
                "memory_limit_value": 16,
                "eviction_policy": "lfu",
            },
        )

        response = await auth_client.get("/api/settings/pool")
        assert response.status_code == 200
        data = response.json()
        assert data["memory_limit_mode"] == "gb"
        assert data["memory_limit_value"] == 16
        assert data["eviction_policy"] == "lfu"
        assert data["preloaded_profiles"] == []


class TestUpdatePoolConfig:
    """Tests for PUT /api/settings/pool."""

    async def test_update_memory_mode_percent(self, auth_client):
        """Updates memory limit to percent mode."""
        response = await auth_client.put(
            "/api/settings/pool",
            json={"memory_limit_mode": "percent", "memory_limit_value": 90},
        )
        assert response.status_code == 200
        data = response.json()
        assert data["memory_limit_mode"] == "percent"
        assert data["memory_limit_value"] == 90

    async def test_update_memory_mode_gb(self, auth_client):
        """Updates memory limit to GB mode."""
        response = await auth_client.put(
            "/api/settings/pool",
            json={"memory_limit_mode": "gb", "memory_limit_value": 32},
        )
        assert response.status_code == 200
        data = response.json()
        assert data["memory_limit_mode"] == "gb"
        assert data["memory_limit_value"] == 32

    async def test_update_eviction_policy(self, auth_client):
        """Updates eviction policy."""
        for policy in ["lru", "lfu", "ttl"]:
            response = await auth_client.put(
                "/api/settings/pool",
                json={"eviction_policy": policy},
            )
            assert response.status_code == 200
            assert response.json()["eviction_policy"] == policy

    async def test_update_invalid_memory_mode(self, auth_client):
        """Rejects invalid memory mode."""
        response = await auth_client.put(
            "/api/settings/pool",
            json={"memory_limit_mode": "invalid"},
        )
        assert response.status_code == 422  # Pydantic validation error for invalid enum
        # Pydantic provides its own validation error structure for enums

    async def test_update_invalid_eviction_policy(self, auth_client):
        """Rejects invalid eviction policy."""
        response = await auth_client.put(
            "/api/settings/pool",
            json={"eviction_policy": "invalid"},
        )
        assert response.status_code == 422  # Pydantic validation error for invalid enum
        # Pydantic provides its own validation error structure for enums

    async def test_partial_update(self, auth_client):
        """Can update single field without affecting others."""
        # Set initial values
        await auth_client.put(
            "/api/settings/pool",
            json={
                "memory_limit_mode": "gb",
                "memory_limit_value": 16,
                "eviction_policy": "lfu",
            },
        )

        # Update only one field
        response = await auth_client.put(
            "/api/settings/pool",
            json={"memory_limit_value": 32},
        )
        assert response.status_code == 200
        data = response.json()
        # Other fields should be unchanged
        assert data["memory_limit_mode"] == "gb"
        assert data["memory_limit_value"] == 32
        assert data["eviction_policy"] == "lfu"

    async def test_update_creates_config_if_not_exists(self, auth_client):
        """Update creates default config if it doesn't exist."""
        # Don't call GET first (which would create the default)
        response = await auth_client.put(
            "/api/settings/pool",
            json={"memory_limit_value": 50},
        )
        assert response.status_code == 200
        data = response.json()
        # Should have default values except for what we updated
        assert data["memory_limit_value"] == 50
        assert data["memory_limit_mode"] == "percent"  # Default
        assert data["eviction_policy"] == "lru"  # Default


# ============================================================================
# API Key Security Tests
# ============================================================================


class TestApiKeySecurity:
    """Tests to verify API keys are never exposed in responses."""

    async def test_api_key_not_in_list_response(self, auth_client):
        """API key is not returned when listing providers."""
        await auth_client.post(
            "/api/settings/providers",
            json={
                "backend_type": "openai",
                "api_key": "sk-secret-key-12345",
            },
        )

        response = await auth_client.get("/api/settings/providers")
        data = response.json()[0]

        # Verify no key-related fields
        assert "api_key" not in data
        assert "encrypted_api_key" not in data
        assert "sk-secret" not in str(data)

    async def test_api_key_not_in_create_response(self, auth_client):
        """API key is not returned after creating provider."""
        response = await auth_client.post(
            "/api/settings/providers",
            json={
                "backend_type": "openai",
                "api_key": "sk-secret-key-12345",
            },
        )
        data = response.json()

        assert "api_key" not in data
        assert "encrypted_api_key" not in data
        assert "sk-secret" not in str(data)

    async def test_different_providers_have_different_encrypted_keys(self, auth_client):
        """Different API keys produce different encrypted values."""
        # This test verifies encryption is happening by checking
        # that we can create multiple providers with different keys

        await auth_client.post(
            "/api/settings/providers",
            json={"backend_type": "openai", "api_key": "sk-key-one"},
        )
        await auth_client.post(
            "/api/settings/providers",
            json={"backend_type": "anthropic", "api_key": "sk-key-two"},
        )

        response = await auth_client.get("/api/settings/providers")
        assert len(response.json()) == 2


# ============================================================================
# Timeout Configuration Endpoint Tests
# ============================================================================


class TestGetTimeoutSettings:
    """Tests for GET /api/settings/timeouts."""

    async def test_get_default_timeouts(self, auth_client):
        """Returns default timeout values when none are configured."""
        response = await auth_client.get("/api/settings/timeouts")
        assert response.status_code == 200
        data = response.json()
        assert data["chat_seconds"] == 900.0  # 15 min
        assert data["completions_seconds"] == 600.0  # 10 min
        assert data["embeddings_seconds"] == 120.0  # 2 min

    async def test_get_configured_timeouts(self, auth_client):
        """Returns configured timeout values after update."""
        # Update timeouts first
        await auth_client.put(
            "/api/settings/timeouts",
            json={
                "chat_seconds": 1800.0,
                "completions_seconds": 900.0,
                "embeddings_seconds": 60.0,
            },
        )

        response = await auth_client.get("/api/settings/timeouts")
        assert response.status_code == 200
        data = response.json()
        assert data["chat_seconds"] == 1800.0
        assert data["completions_seconds"] == 900.0
        assert data["embeddings_seconds"] == 60.0


class TestUpdateTimeoutSettings:
    """Tests for PUT /api/settings/timeouts."""

    async def test_update_all_timeouts(self, auth_client):
        """Updates all timeout values."""
        response = await auth_client.put(
            "/api/settings/timeouts",
            json={
                "chat_seconds": 1200.0,
                "completions_seconds": 800.0,
                "embeddings_seconds": 180.0,
            },
        )
        assert response.status_code == 200
        data = response.json()
        assert data["chat_seconds"] == 1200.0
        assert data["completions_seconds"] == 800.0
        assert data["embeddings_seconds"] == 180.0

    async def test_update_partial_timeouts(self, auth_client):
        """Updates only specified timeout values."""
        # Set initial values
        await auth_client.put(
            "/api/settings/timeouts",
            json={
                "chat_seconds": 1000.0,
                "completions_seconds": 500.0,
                "embeddings_seconds": 100.0,
            },
        )

        # Update only chat_seconds
        response = await auth_client.put(
            "/api/settings/timeouts",
            json={"chat_seconds": 2000.0},
        )
        assert response.status_code == 200
        data = response.json()
        assert data["chat_seconds"] == 2000.0
        # Other values should remain unchanged
        assert data["completions_seconds"] == 500.0
        assert data["embeddings_seconds"] == 100.0

    async def test_update_timeout_min_value(self, auth_client):
        """Tests minimum value validation."""
        # Chat and completions min is 60
        response = await auth_client.put(
            "/api/settings/timeouts",
            json={"chat_seconds": 59.0},
        )
        assert response.status_code == 422  # Validation error

        # Embeddings min is 30
        response = await auth_client.put(
            "/api/settings/timeouts",
            json={"embeddings_seconds": 29.0},
        )
        assert response.status_code == 422

    async def test_update_timeout_max_value(self, auth_client):
        """Tests maximum value validation."""
        # Chat and completions max is 7200
        response = await auth_client.put(
            "/api/settings/timeouts",
            json={"chat_seconds": 7201.0},
        )
        assert response.status_code == 422

        # Embeddings max is 600
        response = await auth_client.put(
            "/api/settings/timeouts",
            json={"embeddings_seconds": 601.0},
        )
        assert response.status_code == 422

    async def test_update_timeout_valid_boundary_values(self, auth_client):
        """Tests valid boundary values."""
        # Test minimum boundaries
        response = await auth_client.put(
            "/api/settings/timeouts",
            json={
                "chat_seconds": 60.0,
                "completions_seconds": 60.0,
                "embeddings_seconds": 30.0,
            },
        )
        assert response.status_code == 200
        data = response.json()
        assert data["chat_seconds"] == 60.0
        assert data["completions_seconds"] == 60.0
        assert data["embeddings_seconds"] == 30.0

        # Test maximum boundaries
        response = await auth_client.put(
            "/api/settings/timeouts",
            json={
                "chat_seconds": 7200.0,
                "completions_seconds": 7200.0,
                "embeddings_seconds": 600.0,
            },
        )
        assert response.status_code == 200
        data = response.json()
        assert data["chat_seconds"] == 7200.0
        assert data["completions_seconds"] == 7200.0
        assert data["embeddings_seconds"] == 600.0

    async def test_update_timeout_persists_across_requests(self, auth_client):
        """Verifies timeout values are persisted to the database."""
        # Update timeouts
        await auth_client.put(
            "/api/settings/timeouts",
            json={
                "chat_seconds": 3600.0,
                "completions_seconds": 1800.0,
                "embeddings_seconds": 300.0,
            },
        )

        # Get timeouts in a separate request
        response = await auth_client.get("/api/settings/timeouts")
        assert response.status_code == 200
        data = response.json()
        assert data["chat_seconds"] == 3600.0
        assert data["completions_seconds"] == 1800.0
        assert data["embeddings_seconds"] == 300.0


# ============================================================================
# Pool Status Endpoint Tests
# ============================================================================


class TestGetPoolStatus:
    """Tests for GET /api/settings/pool/status."""

    async def test_get_pool_status_initialized(self, auth_client):
        """Returns pool status when pool is initialized."""
        mock_status = {
            "memory_used_mb": 1024,
            "memory_limit_mb": 4096,
            "loaded_models": ["model1", "model2"],
            "eviction_policy": "lru",
        }

        with patch("mlx_manager.routers.settings.get_model_pool") as mock_get_pool:
            mock_pool = MagicMock()
            mock_pool.get_status.return_value = mock_status
            mock_get_pool.return_value = mock_pool

            response = await auth_client.get("/api/settings/pool/status")

        assert response.status_code == 200
        data = response.json()
        assert data["memory_used_mb"] == 1024
        assert data["loaded_models"] == ["model1", "model2"]

    async def test_get_pool_status_not_initialized(self, auth_client):
        """Returns 'not_initialized' when pool is not ready."""
        with patch("mlx_manager.routers.settings.get_model_pool") as mock_get_pool:
            mock_get_pool.side_effect = RuntimeError("Pool not initialized")

            response = await auth_client.get("/api/settings/pool/status")

        assert response.status_code == 200
        data = response.json()
        assert data["status"] == "not_initialized"


# ============================================================================
# Backend Router Refresh Exception Tests
# ============================================================================


class TestBackendRouterRefreshExceptions:
    """Tests for exception handling when backend router refresh fails."""

    async def test_create_provider_refresh_failure(self, auth_client):
        """Provider creation succeeds even if router refresh fails."""
        with patch("mlx_manager.routers.settings.get_backend_router") as mock_router:
            mock_router.side_effect = Exception("Router not available")

            response = await auth_client.post(
                "/api/settings/providers",
                json={"backend_type": "openai", "api_key": "sk-test123"},
            )

        # Should still succeed - refresh failure is just logged
        assert response.status_code == 200
        assert response.json()["backend_type"] == "openai"

    async def test_delete_provider_refresh_failure(self, auth_client):
        """Provider deletion succeeds even if router refresh fails."""
        # First create a provider
        await auth_client.post(
            "/api/settings/providers",
            json={"backend_type": "openai", "api_key": "sk-test"},
        )

        with patch("mlx_manager.routers.settings.get_backend_router") as mock_router:
            mock_router.side_effect = Exception("Router not available")

            response = await auth_client.delete("/api/settings/providers/openai")

        # Should still succeed
        assert response.status_code == 204

    async def test_create_rule_refresh_failure(self, auth_client):
        """Rule creation succeeds even if router refresh fails."""
        with patch("mlx_manager.routers.settings.get_backend_router") as mock_router:
            mock_router.side_effect = Exception("Router not available")

            response = await auth_client.post(
                "/api/settings/rules",
                json={
                    "model_pattern": "gpt-*",
                    "backend_type": "openai",
                },
            )

        # Should still succeed
        assert response.status_code == 201

    async def test_update_rule_refresh_failure(self, auth_client):
        """Rule update succeeds even if router refresh fails."""
        # Create rule first
        create_response = await auth_client.post(
            "/api/settings/rules",
            json={"model_pattern": "test", "backend_type": "openai"},
        )
        rule_id = create_response.json()["id"]

        with patch("mlx_manager.routers.settings.get_backend_router") as mock_router:
            mock_router.side_effect = Exception("Router not available")

            response = await auth_client.put(
                f"/api/settings/rules/{rule_id}",
                json={"priority": 100},
            )

        # Should still succeed
        assert response.status_code == 200

    async def test_delete_rule_refresh_failure(self, auth_client):
        """Rule deletion succeeds even if router refresh fails."""
        # Create rule first
        create_response = await auth_client.post(
            "/api/settings/rules",
            json={"model_pattern": "test", "backend_type": "openai"},
        )
        rule_id = create_response.json()["id"]

        with patch("mlx_manager.routers.settings.get_backend_router") as mock_router:
            mock_router.side_effect = Exception("Router not available")

            response = await auth_client.delete(f"/api/settings/rules/{rule_id}")

        # Should still succeed
        assert response.status_code == 204

    async def test_update_priorities_refresh_failure(self, auth_client):
        """Priority update succeeds even if router refresh fails."""
        # Create a rule
        r1 = await auth_client.post(
            "/api/settings/rules",
            json={"model_pattern": "rule1", "backend_type": "openai", "priority": 10},
        )
        rule_id = r1.json()["id"]

        with patch("mlx_manager.routers.settings.get_backend_router") as mock_router:
            mock_router.side_effect = Exception("Router not available")

            response = await auth_client.put(
                "/api/settings/rules/priorities",
                json=[{"id": rule_id, "priority": 100}],
            )

        # Should still succeed
        assert response.status_code == 200


# ============================================================================
# Test Provider Connection Edge Cases
# ============================================================================


class TestTestProviderConnectionEdgeCases:
    """Tests for edge cases in provider connection testing."""

    async def test_test_unsupported_backend_type(self, auth_client):
        """Test connection for unsupported backend type returns error."""
        # Create a provider with 'local' type (which doesn't support testing)
        # Actually 'local' is a valid BackendType but not supported for API testing
        await auth_client.post(
            "/api/settings/providers",
            json={"backend_type": "local", "api_key": "dummy-key"},
        )

        response = await auth_client.post("/api/settings/providers/local/test")

        assert response.status_code == 400
        assert "Unsupported backend type" in response.json()["detail"]


# ============================================================================
# Pool Configuration with Model Pool Integration
# ============================================================================


class TestPoolConfigWithModelPool:
    """Tests for pool configuration with actual model pool integration."""

    async def test_update_pool_with_percent_memory_mode(self, auth_client):
        """Updates pool and applies percent memory limit to running pool."""
        mock_pool = MagicMock()
        mock_pool.update_memory_limit = MagicMock()
        mock_pool.apply_preload_list = AsyncMock(return_value={"loaded": 1})

        with patch("mlx_manager.routers.settings.get_model_pool") as mock_get_pool:
            mock_get_pool.return_value = mock_pool

            response = await auth_client.put(
                "/api/settings/pool",
                json={
                    "memory_limit_mode": "percent",
                    "memory_limit_value": 75,
                },
            )

        assert response.status_code == 200
        # Verify memory limit was applied with percent mode
        mock_pool.update_memory_limit.assert_called_once()
        call_kwargs = mock_pool.update_memory_limit.call_args.kwargs
        assert "memory_pct" in call_kwargs
        assert call_kwargs["memory_pct"] == 0.75

    async def test_update_pool_with_gb_memory_mode(self, auth_client):
        """Updates pool and applies GB memory limit to running pool."""
        mock_pool = MagicMock()
        mock_pool.update_memory_limit = MagicMock()

        with patch("mlx_manager.routers.settings.get_model_pool") as mock_get_pool:
            mock_get_pool.return_value = mock_pool

            response = await auth_client.put(
                "/api/settings/pool",
                json={
                    "memory_limit_mode": "gb",
                    "memory_limit_value": 16,
                },
            )

        assert response.status_code == 200
        # Verify memory limit was applied with GB mode
        mock_pool.update_memory_limit.assert_called_once()
        call_kwargs = mock_pool.update_memory_limit.call_args.kwargs
        assert "memory_gb" in call_kwargs
        assert call_kwargs["memory_gb"] == 16

    async def test_update_pool_when_pool_not_initialized(self, auth_client):
        """Updates pool config even when pool is not initialized."""
        with patch("mlx_manager.routers.settings.get_model_pool") as mock_get_pool:
            mock_get_pool.side_effect = RuntimeError("Pool not initialized")

            response = await auth_client.put(
                "/api/settings/pool",
                json={"memory_limit_value": 50},
            )

        # Should succeed - settings are saved even if pool isn't running
        assert response.status_code == 200
        assert response.json()["memory_limit_value"] == 50


# ============================================================================
# Provider Defaults Endpoint Tests (uncovered lines 154-162)
# ============================================================================


class TestGetProviderDefaults:
    """Tests for GET /api/settings/providers/defaults."""

    async def test_get_provider_defaults(self, auth_client):
        """Returns default base URLs and API types for known providers."""
        response = await auth_client.get("/api/settings/providers/defaults")
        assert response.status_code == 200
        data = response.json()
        assert "providers" in data
        providers = data["providers"]
        assert isinstance(providers, list)
        assert len(providers) > 0

    async def test_provider_defaults_contains_expected_fields(self, auth_client):
        """Each provider entry has backend_type, base_url, and api_type fields."""
        response = await auth_client.get("/api/settings/providers/defaults")
        assert response.status_code == 200
        providers = response.json()["providers"]
        for provider in providers:
            assert "backend_type" in provider
            assert "base_url" in provider
            assert "api_type" in provider

    async def test_provider_defaults_includes_openai(self, auth_client):
        """OpenAI is present in provider defaults."""
        response = await auth_client.get("/api/settings/providers/defaults")
        assert response.status_code == 200
        providers = response.json()["providers"]
        backend_types = [p["backend_type"] for p in providers]
        assert "openai" in backend_types

    async def test_provider_defaults_requires_auth(self, client):
        """Requires authentication."""
        response = await client.get("/api/settings/providers/defaults")
        assert response.status_code == 401


# ============================================================================
# Routing Rules Profile ID Validation Tests
# ============================================================================


class TestCreateRuleWithProfileId:
    """Tests for profile_id validation in POST /api/settings/rules."""

    async def test_create_rule_with_nonexistent_profile_id(self, auth_client):
        """Rejects rule creation when profile_id references a nonexistent profile."""
        response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "gpt-4",
                "backend_type": "openai",
                "profile_id": 99999,
            },
        )
        assert response.status_code == 400
        assert "Profile with id=99999 not found" in response.json()["detail"]

    async def test_create_rule_with_valid_profile_id(self, auth_client):
        """Accepts rule creation when profile_id references an existing profile."""
        # Create a profile first via the API
        profile_response = await auth_client.post(
            "/api/profiles",
            json={
                "name": "Rule Test Profile",
                "model_id": 1,
                "inference": {"temperature": 0.7},
            },
        )
        assert profile_response.status_code == 201
        profile_id = profile_response.json()["id"]

        response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "gpt-4",
                "backend_type": "openai",
                "profile_id": profile_id,
            },
        )
        assert response.status_code == 201
        assert response.json()["profile_id"] == profile_id

    async def test_create_rule_with_null_profile_id(self, auth_client):
        """Allows rule creation with null profile_id (no validation needed)."""
        response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "gpt-4",
                "backend_type": "openai",
                "profile_id": None,
            },
        )
        assert response.status_code == 201
        assert response.json()["profile_id"] is None


class TestUpdateRuleWithProfileId:
    """Tests for profile_id validation in PUT /api/settings/rules/{rule_id}."""

    async def test_update_rule_with_nonexistent_profile_id(self, auth_client):
        """Rejects rule update when profile_id references a nonexistent profile."""
        # Create rule first
        create_response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "test",
                "backend_type": "openai",
            },
        )
        rule_id = create_response.json()["id"]

        response = await auth_client.put(
            f"/api/settings/rules/{rule_id}",
            json={"profile_id": 99999},
        )
        assert response.status_code == 400
        assert "Profile with id=99999 not found" in response.json()["detail"]

    async def test_update_rule_with_valid_profile_id(self, auth_client):
        """Accepts rule update when profile_id references an existing profile."""
        # Create a profile first
        profile_response = await auth_client.post(
            "/api/profiles",
            json={
                "name": "Update Rule Profile",
                "model_id": 1,
                "inference": {"temperature": 0.5},
            },
        )
        assert profile_response.status_code == 201
        profile_id = profile_response.json()["id"]

        # Create rule
        create_response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "test",
                "backend_type": "openai",
            },
        )
        rule_id = create_response.json()["id"]

        # Update with valid profile_id
        response = await auth_client.put(
            f"/api/settings/rules/{rule_id}",
            json={"profile_id": profile_id},
        )
        assert response.status_code == 200
        assert response.json()["profile_id"] == profile_id

    async def test_update_rule_with_null_profile_id_skips_validation(self, auth_client):
        """Null profile_id in update skips validation (no lookup needed)."""
        # Create rule with no profile_id
        create_response = await auth_client.post(
            "/api/settings/rules",
            json={
                "model_pattern": "test",
                "backend_type": "openai",
            },
        )
        rule_id = create_response.json()["id"]

        # Setting profile_id to None explicitly should succeed
        response = await auth_client.put(
            f"/api/settings/rules/{rule_id}",
            json={"profile_id": None},
        )
        assert response.status_code == 200
        assert response.json()["profile_id"] is None


# ============================================================================
# HuggingFace Token Endpoint Tests
# ============================================================================


class TestGetHuggingFaceStatus:
    """Tests for GET /api/settings/huggingface."""

    async def test_no_token_configured(self, auth_client):
        """Returns configured=False when no token exists."""
        response = await auth_client.get("/api/settings/huggingface")
        assert response.status_code == 200
        assert response.json() == {"configured": False}

    async def test_token_configured(self, auth_client):
        """Returns configured=True when a token is saved."""
        # Save a token first
        await auth_client.put(
            "/api/settings/huggingface",
            json={"token": "hf_test_token_12345"},
        )

        response = await auth_client.get("/api/settings/huggingface")
        assert response.status_code == 200
        assert response.json() == {"configured": True}

    async def test_requires_auth(self, client):
        """Requires authentication."""
        response = await client.get("/api/settings/huggingface")
        assert response.status_code == 401


class TestSaveHuggingFaceToken:
    """Tests for PUT /api/settings/huggingface."""

    async def test_save_new_token(self, auth_client):
        """Creates a new HuggingFace token entry."""
        response = await auth_client.put(
            "/api/settings/huggingface",
            json={"token": "hf_new_token_12345"},
        )
        assert response.status_code == 200
        assert response.json() == {"configured": True}

        # Verify it's configured
        status_response = await auth_client.get("/api/settings/huggingface")
        assert status_response.json() == {"configured": True}

    async def test_update_existing_token(self, auth_client):
        """Updates an existing HuggingFace token."""
        # Save initial token
        await auth_client.put(
            "/api/settings/huggingface",
            json={"token": "hf_initial_token"},
        )

        # Update with new token
        response = await auth_client.put(
            "/api/settings/huggingface",
            json={"token": "hf_updated_token"},
        )
        assert response.status_code == 200
        assert response.json() == {"configured": True}

    async def test_save_empty_token_rejected(self, auth_client):
        """Rejects empty or whitespace-only tokens."""
        response = await auth_client.put(
            "/api/settings/huggingface",
            json={"token": "   "},
        )
        assert response.status_code == 400
        assert "Token is required" in response.json()["detail"]

    async def test_requires_auth(self, client):
        """Requires authentication."""
        response = await client.put(
            "/api/settings/huggingface",
            json={"token": "hf_test"},
        )
        assert response.status_code == 401


class TestDeleteHuggingFaceToken:
    """Tests for DELETE /api/settings/huggingface."""

    async def test_delete_existing_token(self, auth_client):
        """Deletes an existing HuggingFace token."""
        # Save a token first
        await auth_client.put(
            "/api/settings/huggingface",
            json={"token": "hf_to_delete"},
        )

        response = await auth_client.delete("/api/settings/huggingface")
        assert response.status_code == 204

        # Verify it's gone
        status_response = await auth_client.get("/api/settings/huggingface")
        assert status_response.json() == {"configured": False}

    async def test_delete_nonexistent_token(self, auth_client):
        """Deleting when no token exists is a no-op (still 204)."""
        response = await auth_client.delete("/api/settings/huggingface")
        assert response.status_code == 204

    async def test_requires_auth(self, client):
        """Requires authentication."""
        response = await client.delete("/api/settings/huggingface")
        assert response.status_code == 401


class TestTestHuggingFaceToken:
    """Tests for POST /api/settings/huggingface/test."""

    async def test_no_token_configured(self, auth_client):
        """Returns 404 when no token is configured."""
        response = await auth_client.post("/api/settings/huggingface/test")
        assert response.status_code == 404
        assert "No HuggingFace token configured" in response.json()["detail"]

    async def test_success(self, auth_client):
        """Tests successful HuggingFace token validation."""
        # Save a token first
        await auth_client.put(
            "/api/settings/huggingface",
            json={"token": "hf_valid_token"},
        )

        mock_response = MagicMock()
        mock_response.status_code = 200
        mock_response.json.return_value = {"name": "testuser"}

        with patch("mlx_manager.routers.settings.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.return_value = mock_response
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            response = await auth_client.post("/api/settings/huggingface/test")
            assert response.status_code == 200
            data = response.json()
            assert data["success"] is True
            assert data["username"] == "testuser"

    async def test_invalid_token_401(self, auth_client):
        """Returns error when HF API returns 401 (invalid token)."""
        # Save a token first
        await auth_client.put(
            "/api/settings/huggingface",
            json={"token": "hf_invalid_token"},
        )

        mock_response = AsyncMock()
        mock_response.status_code = 401

        with patch("mlx_manager.routers.settings.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.return_value = mock_response
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            response = await auth_client.post("/api/settings/huggingface/test")
            assert response.status_code == 400
            assert "Invalid token" in response.json()["detail"]

    async def test_hf_api_other_error(self, auth_client):
        """Returns error when HF API returns unexpected status code."""
        await auth_client.put(
            "/api/settings/huggingface",
            json={"token": "hf_some_token"},
        )

        mock_response = AsyncMock()
        mock_response.status_code = 500

        with patch("mlx_manager.routers.settings.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.return_value = mock_response
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            response = await auth_client.post("/api/settings/huggingface/test")
            assert response.status_code == 400
            assert "status 500" in response.json()["detail"]

    async def test_connect_error(self, auth_client):
        """Returns error when HF API is unreachable."""
        await auth_client.put(
            "/api/settings/huggingface",
            json={"token": "hf_some_token"},
        )

        with patch("mlx_manager.routers.settings.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.side_effect = httpx.ConnectError("connection refused")
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            response = await auth_client.post("/api/settings/huggingface/test")
            assert response.status_code == 400
            assert "Could not connect" in response.json()["detail"]

    async def test_timeout(self, auth_client):
        """Returns error when HF API request times out."""
        await auth_client.put(
            "/api/settings/huggingface",
            json={"token": "hf_some_token"},
        )

        with patch("mlx_manager.routers.settings.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.side_effect = httpx.TimeoutException("timed out")
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            response = await auth_client.post("/api/settings/huggingface/test")
            assert response.status_code == 400
            assert "timed out" in response.json()["detail"]

    async def test_decryption_failure(self, auth_client):
        """Returns error when stored token cannot be decrypted."""
        # Save a valid token first
        await auth_client.put(
            "/api/settings/huggingface",
            json={"token": "hf_valid_token"},
        )

        # Mock decrypt to raise InvalidToken
        with patch(
            "mlx_manager.routers.settings.decrypt_api_key",
            side_effect=__import__(
                "mlx_manager.services.encryption_service", fromlist=["InvalidToken"]
            ).InvalidToken("bad token"),
        ):
            response = await auth_client.post("/api/settings/huggingface/test")
            assert response.status_code == 400
            assert "cannot be decrypted" in response.json()["detail"]

    async def test_requires_auth(self, client):
        """Requires authentication."""
        response = await client.post("/api/settings/huggingface/test")
        assert response.status_code == 401


# ============================================================================
# Provider Connection Tests - Uncovered Branches
# ============================================================================


class TestProviderConnectionUncoveredBranches:
    """Tests for branches in test_provider_connection not covered by existing tests."""

    async def test_anthropic_headers_used_when_api_type_is_anthropic(self, auth_client):
        """When provider's api_type is ANTHROPIC, x-api-key header is used (line 213)."""
        # Create an anthropic provider WITH explicit api_type so it's stored as ANTHROPIC
        await auth_client.post(
            "/api/settings/providers",
            json={
                "backend_type": "anthropic",
                "api_key": "sk-ant-valid-key",
                "api_type": "anthropic",
            },
        )

        mock_response = AsyncMock()
        mock_response.status_code = 200

        with patch("mlx_manager.routers.settings.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.get.return_value = mock_response
            mock_instance.__aenter__.return_value = mock_instance
            mock_instance.__aexit__.return_value = None
            mock_client.return_value = mock_instance

            response = await auth_client.post("/api/settings/providers/anthropic/test")

            assert response.status_code == 200
            # Verify x-api-key header was used (not Authorization: Bearer)
            call_args = mock_instance.get.call_args
            headers = call_args.kwargs.get("headers", {})
            assert "x-api-key" in headers
            assert "Authorization" not in headers

    async def test_invalid_token_raises_400(self, auth_client):
        """When decryption fails (InvalidToken), returns 400 (lines 194-196)."""
        from mlx_manager.services.encryption_service import InvalidToken as EncryptionInvalidToken

        # Create provider so it's in the DB
        await auth_client.post(
            "/api/settings/providers",
            json={"backend_type": "openai", "api_key": "sk-some-key"},
        )

        with patch(
            "mlx_manager.routers.settings.decrypt_api_key",
            side_effect=EncryptionInvalidToken("Cannot decrypt"),
        ):
            response = await auth_client.post("/api/settings/providers/openai/test")

        assert response.status_code == 400
        assert "decrypted" in response.json()["detail"].lower()

    async def test_credential_with_null_api_type_uses_openai_headers(self):
        """Credential with api_type=None falls back to OPENAI api type (line 204).

        Calls the router function directly to bypass Pydantic validation and test
        the legacy data path where a stored credential has api_type=None.
        """
        from mlx_manager.models import BackendType
        from mlx_manager.routers.settings import test_provider_connection

        mock_user = MagicMock()
        mock_user.status = "approved"

        # Create a mock credential with api_type=None (legacy data)
        mock_credential = MagicMock()
        mock_credential.api_type = None  # Legacy: no api_type stored
        mock_credential.encrypted_api_key = b"some-encrypted"
        mock_credential.base_url = None

        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = mock_credential

        mock_session = MagicMock()
        mock_session.execute = AsyncMock(return_value=mock_result)

        mock_response = AsyncMock()
        mock_response.status_code = 200

        with patch("mlx_manager.routers.settings.decrypt_api_key", return_value="sk-decrypted"):
            with patch("mlx_manager.routers.settings.httpx.AsyncClient") as mock_client:
                mock_instance = AsyncMock()
                mock_instance.get.return_value = mock_response
                mock_instance.__aenter__.return_value = mock_instance
                mock_instance.__aexit__.return_value = None
                mock_client.return_value = mock_instance

                result = await test_provider_connection(
                    current_user=mock_user,
                    backend_type=BackendType.OPENAI,
                    session=mock_session,
                )
                assert result == {"success": True}
                # Verify Authorization header used (OPENAI api type)
                call_args = mock_instance.get.call_args
                headers = call_args.kwargs.get("headers", {})
                assert "Authorization" in headers


# ============================================================================
# _matches_pattern function - regex exception handler (lines 466-468)
# ============================================================================


class TestMatchesPatternFunction:
    """Tests for _matches_pattern function's regex error handling."""

    def test_regex_pattern_with_invalid_regex_returns_false(self):
        """Invalid regex pattern returns False without raising exception (lines 466-468)."""
        from mlx_manager.routers.settings import _matches_pattern

        # This regex is pathological; calling re.match with it may raise re.error
        # We simulate by patching re.match to raise re.error
        with patch("mlx_manager.routers.settings.re.match", side_effect=Exception("re.error")):
            # The function catches re.error specifically; patch with re.error subclass
            pass

        # Test with genuinely invalid stored regex by monkeypatching re.match
        import re

        original_match = re.match

        def raising_match(pattern, string, *args, **kwargs):
            if pattern == "FORCE_ERROR":
                raise re.error("forced error")
            return original_match(pattern, string, *args, **kwargs)

        with patch("mlx_manager.routers.settings.re.match", side_effect=raising_match):
            result = _matches_pattern("some-model", "FORCE_ERROR", "regex")
            assert result is False

    def test_regex_pattern_error_caught_returns_false(self):
        """re.error in _matches_pattern is caught and returns False."""
        import re

        from mlx_manager.routers.settings import _matches_pattern

        with patch("mlx_manager.routers.settings.re.match", side_effect=re.error("bad pattern")):
            result = _matches_pattern("my-model", "[broken", "regex")
            assert result is False

    def test_matches_pattern_exact(self):
        """Exact matching works."""
        from mlx_manager.routers.settings import _matches_pattern

        assert _matches_pattern("gpt-4", "gpt-4", "exact") is True
        assert _matches_pattern("gpt-4-turbo", "gpt-4", "exact") is False

    def test_matches_pattern_prefix(self):
        """Prefix matching works."""
        from mlx_manager.routers.settings import _matches_pattern

        assert _matches_pattern("claude-3-opus", "claude-", "prefix") is True
        assert _matches_pattern("gpt-4", "claude-", "prefix") is False

    def test_matches_pattern_unknown_type_returns_false(self):
        """Unknown pattern type returns False."""
        from mlx_manager.routers.settings import _matches_pattern

        assert _matches_pattern("model", "pattern", "unknown_type") is False


# ============================================================================
# Dead Code Coverage - Line 77 (api_type None fallback in create_or_update_provider)
# ============================================================================


class TestCreateProviderApiTypeNoneFallback:
    """Tests for api_type=None fallback path in create_or_update_provider (line 77)."""

    async def test_api_type_none_falls_back_to_backend_mapping(self, auth_client):
        """When api_type is None on the data object, uses API_TYPE_FOR_BACKEND mapping (line 77).

        This is dead code in normal API flow (Pydantic prevents None), but we cover it
        by calling the router function directly with a mocked data object.
        """
        from mlx_manager.routers.settings import create_or_update_provider

        # Build a mock data object where api_type is explicitly None
        mock_data = MagicMock()
        mock_data.api_type = None
        mock_data.backend_type = "openai"
        mock_data.api_key = "sk-test-key"
        mock_data.name = ""
        mock_data.base_url = None

        mock_user = MagicMock()

        from unittest.mock import AsyncMock

        mock_session = MagicMock()
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = None
        mock_session.execute = AsyncMock(return_value=mock_result)
        mock_session.add = MagicMock()
        mock_session.commit = AsyncMock()
        mock_session.refresh = AsyncMock()

        with patch("mlx_manager.routers.settings.encrypt_api_key", return_value=b"encrypted"):
            with patch("mlx_manager.routers.settings.get_backend_router") as mock_router:
                mock_router.return_value.refresh_rules = AsyncMock()
                # Call the function directly - this bypasses Pydantic validation
                await create_or_update_provider(
                    current_user=mock_user,
                    data=mock_data,
                    session=mock_session,
                )
                # Verify that the function ran (api_type was resolved from mapping)
                assert mock_session.add.called


# ============================================================================
# Dead Code Coverage - Lines 280, 396, 529, 536 (validation guards)
# ============================================================================


class TestValidationGuardDeadCode:
    """Tests for validation guard branches that are dead code due to Pydantic enums.

    These tests call functions directly to cover branches that Pydantic normally prevents.
    """

    async def test_create_rule_invalid_pattern_type_direct_call(self):
        """create_rule raises 400 for invalid pattern_type when called directly (line 280)."""
        from fastapi import HTTPException

        from mlx_manager.models import BackendType
        from mlx_manager.routers.settings import create_rule

        mock_data = MagicMock()
        mock_data.pattern_type = "invalid_type"  # Not a valid PatternType
        mock_data.model_pattern = "test-pattern"
        mock_data.backend_type = BackendType.OPENAI
        mock_data.backend_model = None
        mock_data.fallback_backend = None
        mock_data.priority = 0

        mock_user = MagicMock()
        mock_session = MagicMock()

        with pytest.raises(HTTPException) as exc_info:
            await create_rule(
                current_user=mock_user,
                data=mock_data,
                session=mock_session,
            )
        assert exc_info.value.status_code == 400
        assert "pattern_type" in exc_info.value.detail

    async def test_update_rule_invalid_pattern_type_direct_call(self):
        """update_rule raises 400 for invalid pattern_type when called directly (line 396)."""
        from fastapi import HTTPException

        from mlx_manager.routers.settings import update_rule

        mock_data = MagicMock()
        mock_data.pattern_type = "invalid_type"
        mock_data.model_pattern = None

        mock_user = MagicMock()

        mock_rule = MagicMock()
        mock_rule.pattern_type = "exact"

        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = mock_rule

        mock_session = MagicMock()
        mock_session.execute = AsyncMock(return_value=mock_result)

        with pytest.raises(HTTPException) as exc_info:
            await update_rule(
                current_user=mock_user,
                rule_id=1,
                data=mock_data,
                session=mock_session,
            )
        assert exc_info.value.status_code == 400
        assert "pattern_type" in exc_info.value.detail

    async def test_update_pool_config_invalid_memory_mode_direct_call(self):
        """update_pool_config raises 400 for invalid memory_limit_mode when called directly
        (line 529)."""
        from fastapi import HTTPException

        from mlx_manager.routers.settings import update_pool_config

        mock_data = MagicMock()
        mock_data.memory_limit_mode = "invalid_mode"  # Not percent or gb
        mock_data.memory_limit_value = None
        mock_data.eviction_policy = None

        mock_user = MagicMock()
        mock_config = MagicMock()

        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = mock_config

        mock_session = MagicMock()
        mock_session.execute = AsyncMock(return_value=mock_result)

        with pytest.raises(HTTPException) as exc_info:
            await update_pool_config(
                current_user=mock_user,
                data=mock_data,
                session=mock_session,
            )
        assert exc_info.value.status_code == 400
        assert "memory_limit_mode" in exc_info.value.detail

    async def test_update_pool_config_invalid_eviction_policy_direct_call(self):
        """update_pool_config raises 400 for invalid eviction_policy when called directly
        (line 536)."""
        from fastapi import HTTPException

        from mlx_manager.routers.settings import update_pool_config

        mock_data = MagicMock()
        mock_data.memory_limit_mode = None
        mock_data.memory_limit_value = None
        mock_data.eviction_policy = "invalid_policy"  # Not lru, lfu, or ttl

        mock_user = MagicMock()
        mock_config = MagicMock()

        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = mock_config

        mock_session = MagicMock()
        mock_session.execute = AsyncMock(return_value=mock_result)

        with pytest.raises(HTTPException) as exc_info:
            await update_pool_config(
                current_user=mock_user,
                data=mock_data,
                session=mock_session,
            )
        assert exc_info.value.status_code == 400
        assert "eviction_policy" in exc_info.value.detail
