# -*- coding: utf-8 -*-
"""Location: ./tests/unit/mcpgateway/test_well_known.py
Copyright 2026
SPDX-License-Identifier: Apache-2.0
Authors: Mihai Criveti

Test cases for well-known URI endpoints.
"""

# Standard
from unittest.mock import patch

# Third-Party
from fastapi.testclient import TestClient
import pytest

# Note: this test needs the admin API mounted on main.app. The
# `main_app_with_admin_api` session fixture (tests/conftest.py) reloads
# mcpgateway.main with the admin flag flipped on.


class TestWellKnownEndpoints:
    """Test suite for well-known URI endpoints."""

    @pytest.fixture
    def client(self, main_app_with_admin_api):
        """Create a test client for the FastAPI app."""
        return TestClient(main_app_with_admin_api)

    def test_robots_txt_default(self, client):
        """Test default robots.txt blocks all crawlers."""
        response = client.get("/.well-known/robots.txt")
        assert response.status_code == 200
        assert "User-agent: *" in response.text
        assert "Disallow: /" in response.text
        assert response.headers["content-type"] == "text/plain; charset=utf-8"
        assert "X-Robots-Tag" in response.headers
        assert response.headers["X-Robots-Tag"] == "noindex, nofollow"

    def test_security_txt_not_configured(self, client):
        """Test security.txt returns 404 when not configured."""
        response = client.get("/.well-known/security.txt")
        assert response.status_code == 404
        assert "security.txt not configured" in response.text

    def test_unknown_well_known_file(self, client):
        """Test unknown well-known file returns 404."""
        response = client.get("/.well-known/unknown.txt")
        assert response.status_code == 404

    def test_known_but_unconfigured_file(self, client):
        """Test known well-known URI that is not configured returns helpful 404."""
        response = client.get("/.well-known/change-password")
        assert response.status_code == 404
        assert "change-password is not configured" in response.text
        assert "Change password URL" in response.text

    def test_well_known_path_normalization(self, client):
        """Test that paths are properly normalized (removing leading slashes)."""
        # Test with leading slash
        response = client.get("/.well-known//robots.txt")
        assert response.status_code == 200
        assert "User-agent: *" in response.text


class TestSecurityTxtValidation:
    """Test security.txt validation functionality."""

    def test_validate_security_txt_empty(self):
        """Test validation with empty content."""
        # First-Party
        from mcpgateway.routers.well_known import validate_security_txt

        result = validate_security_txt("")
        assert result is None

        result = validate_security_txt(None)
        assert result is None

    def test_validate_security_txt_adds_expires(self):
        """Test that validation adds Expires field."""
        # First-Party
        from mcpgateway.routers.well_known import validate_security_txt

        content = "Contact: security@example.com"
        result = validate_security_txt(content)

        assert result is not None
        assert "Contact: security@example.com" in result
        assert "Expires:" in result
        assert "# Security contact information for ContextForge" in result

    def test_validate_security_txt_preserves_expires(self):
        """Test that validation preserves existing Expires field."""
        # First-Party
        from mcpgateway.routers.well_known import validate_security_txt

        content = "Contact: security@example.com\nExpires: 2025-12-31T23:59:59Z"
        result = validate_security_txt(content)

        assert result is not None
        assert "Expires: 2025-12-31T23:59:59Z" in result
        # Should not add a second Expires field
        assert result.count("Expires:") == 1

    def test_validate_security_txt_preserves_comments(self):
        """Test that validation preserves existing comments."""
        # First-Party
        from mcpgateway.routers.well_known import validate_security_txt

        content = "# Custom security information\nContact: security@example.com"
        result = validate_security_txt(content)

        assert result is not None
        assert "# Custom security information" in result
        assert "Contact: security@example.com" in result
        assert "Expires:" in result


class TestWellKnownDisabled:
    """Test well-known endpoints when disabled."""

    @pytest.fixture
    def client(self, main_app_with_admin_api):
        """Create a test client for the FastAPI app."""
        return TestClient(main_app_with_admin_api)

    @patch("mcpgateway.routers.well_known.settings")
    def test_well_known_disabled_returns_404(self, mock_settings, client):
        """Test that requests return 404 when well_known_enabled is False."""
        # Configure settings to disable well-known
        mock_settings.well_known_enabled = False

        # Test various well-known files should all return 404
        response = client.get("/.well-known/robots.txt")
        assert response.status_code == 404
        assert "Not found" in response.text

        response = client.get("/.well-known/security.txt")
        assert response.status_code == 404
        assert "Not found" in response.text

        response = client.get("/.well-known/any-file.txt")
        assert response.status_code == 404
        assert "Not found" in response.text


class TestSecurityTxtWithContent:
    """Test security.txt with various content configurations."""

    @pytest.fixture
    def client(self, main_app_with_admin_api):
        """Create a test client for the FastAPI app."""
        return TestClient(main_app_with_admin_api)

    @patch("mcpgateway.routers.well_known.settings")
    def test_security_txt_enabled_with_empty_content(self, mock_settings, client):
        """Test security.txt enabled but with empty content returns 404."""
        # Configure settings for security.txt enabled with empty content
        mock_settings.well_known_enabled = True
        mock_settings.well_known_security_txt_enabled = True
        mock_settings.well_known_security_txt = ""
        mock_settings.well_known_cache_max_age = 3600

        response = client.get("/.well-known/security.txt")
        assert response.status_code == 404
        assert "security.txt not configured" in response.text

    @patch("mcpgateway.routers.well_known.settings")
    def test_security_txt_enabled_with_none_content(self, mock_settings, client):
        """Test security.txt enabled but with None content returns 404."""
        # Configure settings for security.txt enabled with None content
        mock_settings.well_known_enabled = True
        mock_settings.well_known_security_txt_enabled = True
        mock_settings.well_known_security_txt = None
        mock_settings.well_known_cache_max_age = 3600

        response = client.get("/.well-known/security.txt")
        assert response.status_code == 404
        assert "security.txt not configured" in response.text

    @patch("mcpgateway.routers.well_known.settings")
    def test_security_txt_enabled_with_valid_content(self, mock_settings, client):
        """Test security.txt enabled with valid content returns content."""
        # Configure settings for security.txt enabled with valid content
        mock_settings.well_known_enabled = True
        mock_settings.well_known_security_txt_enabled = True
        mock_settings.well_known_security_txt = "Contact: security@example.com"
        mock_settings.well_known_cache_max_age = 3600

        response = client.get("/.well-known/security.txt")
        assert response.status_code == 200
        assert "Contact: security@example.com" in response.text
        assert "Expires:" in response.text
        assert response.headers["content-type"] == "text/plain; charset=utf-8"
        assert "Cache-Control" in response.headers
        assert "public, max-age=3600" in response.headers["Cache-Control"]


class TestCustomWellKnownFiles:
    """Test custom well-known files functionality."""

    @pytest.fixture
    def client(self, main_app_with_admin_api):
        """Create a test client for the FastAPI app."""
        return TestClient(main_app_with_admin_api)

    @patch("mcpgateway.routers.well_known.settings")
    def test_custom_well_known_file_known_type(self, mock_settings, client):
        """Test custom well-known file with known content type."""
        # Configure settings with custom file that has a known content type
        mock_settings.well_known_enabled = True
        mock_settings.custom_well_known_files = {"ai.txt": "User-agent: *\nDisallow: /private/"}
        mock_settings.well_known_cache_max_age = 7200

        response = client.get("/.well-known/ai.txt")
        assert response.status_code == 200
        assert "User-agent: *" in response.text
        assert "Disallow: /private/" in response.text
        assert response.headers["content-type"] == "text/plain; charset=utf-8"
        assert "Cache-Control" in response.headers
        assert "public, max-age=7200" in response.headers["Cache-Control"]

    @patch("mcpgateway.routers.well_known.settings")
    def test_custom_well_known_file_unknown_type(self, mock_settings, client):
        """Test custom well-known file with unknown content type."""
        # Configure settings with custom file that's not in the registry
        mock_settings.well_known_enabled = True
        mock_settings.custom_well_known_files = {"custom-file.txt": "This is a custom well-known file"}
        mock_settings.well_known_cache_max_age = 1800

        response = client.get("/.well-known/custom-file.txt")
        assert response.status_code == 200
        assert "This is a custom well-known file" in response.text
        assert response.headers["content-type"] == "text/plain; charset=utf-8"
        assert "Cache-Control" in response.headers
        assert "public, max-age=1800" in response.headers["Cache-Control"]


class TestWellKnownAdminEndpoint:
    """Test admin well-known status endpoint."""

    @pytest.fixture
    def auth_client(self, main_app_with_admin_api):
        """Create a test client with auth dependency override."""
        # First-Party
        from mcpgateway.config import settings
        from mcpgateway.utils.verify_credentials import require_auth

        # Disable auth_required so AdminAuthMiddleware skips its check
        original_auth_required = settings.auth_required
        settings.auth_required = False

        main_app_with_admin_api.dependency_overrides[require_auth] = lambda: "test_user"
        client = TestClient(main_app_with_admin_api)
        yield client
        main_app_with_admin_api.dependency_overrides.pop(require_auth, None)
        settings.auth_required = original_auth_required

    @patch("mcpgateway.routers.well_known.settings")
    def test_admin_well_known_status_basic(self, mock_settings, auth_client):
        """Test admin well-known status endpoint with basic configuration."""
        # Configure basic settings
        mock_settings.well_known_enabled = True
        mock_settings.well_known_security_txt_enabled = False
        mock_settings.custom_well_known_files = {}
        mock_settings.well_known_cache_max_age = 3600

        response = auth_client.get("/admin/well-known")
        assert response.status_code == 200

        data = response.json()
        assert data["enabled"] is True
        assert data["cache_max_age"] == 3600
        assert "configured_files" in data
        assert "supported_files" in data

        # Should always have robots.txt
        configured_files = data["configured_files"]
        robots_file = next((f for f in configured_files if f["path"] == "/.well-known/robots.txt"), None)
        assert robots_file is not None
        assert robots_file["enabled"] is True
        assert robots_file["description"] == "Robot exclusion standard"
        assert robots_file["cache_max_age"] == 3600

    @patch("mcpgateway.routers.well_known.settings")
    def test_admin_well_known_status_with_security_txt(self, mock_settings, auth_client):
        """Test admin well-known status endpoint with security.txt enabled."""
        # Configure settings with security.txt enabled
        mock_settings.well_known_enabled = True
        mock_settings.well_known_security_txt_enabled = True
        mock_settings.custom_well_known_files = {}
        mock_settings.well_known_cache_max_age = 7200

        response = auth_client.get("/admin/well-known")
        assert response.status_code == 200

        data = response.json()
        configured_files = data["configured_files"]

        # Should have security.txt listed
        security_file = next((f for f in configured_files if f["path"] == "/.well-known/security.txt"), None)
        assert security_file is not None
        assert security_file["enabled"] is True
        assert security_file["description"] == "Security contact information"
        assert security_file["cache_max_age"] == 7200

    @patch("mcpgateway.routers.well_known.settings")
    def test_admin_well_known_status_with_custom_files(self, mock_settings, auth_client):
        """Test admin well-known status endpoint with custom files."""
        # Configure settings with custom files
        mock_settings.well_known_enabled = True
        mock_settings.well_known_security_txt_enabled = False
        mock_settings.custom_well_known_files = {"custom1.txt": "Custom content 1", "custom2.txt": "Custom content 2"}
        mock_settings.well_known_cache_max_age = 1800

        response = auth_client.get("/admin/well-known")
        assert response.status_code == 200

        data = response.json()
        configured_files = data["configured_files"]

        # Should have custom files listed
        custom1_file = next((f for f in configured_files if f["path"] == "/.well-known/custom1.txt"), None)
        assert custom1_file is not None
        assert custom1_file["enabled"] is True
        assert custom1_file["description"] == "Custom well-known file"
        assert custom1_file["cache_max_age"] == 1800

        custom2_file = next((f for f in configured_files if f["path"] == "/.well-known/custom2.txt"), None)
        assert custom2_file is not None
        assert custom2_file["enabled"] is True
        assert custom2_file["description"] == "Custom well-known file"
        assert custom2_file["cache_max_age"] == 1800


class TestWellKnownRegistry:
    """Test well-known URI registry functionality."""

    def test_registry_contains_standard_files(self):
        """Test that registry contains expected standard files."""
        # First-Party
        from mcpgateway.routers.well_known import WELL_KNOWN_REGISTRY

        expected_files = ["robots.txt", "security.txt", "ai.txt", "dnt-policy.txt", "change-password"]

        for file in expected_files:
            assert file in WELL_KNOWN_REGISTRY
            assert "content_type" in WELL_KNOWN_REGISTRY[file]
            assert "description" in WELL_KNOWN_REGISTRY[file]
            assert "rfc" in WELL_KNOWN_REGISTRY[file]

    def test_registry_content_types(self):
        """Test that registry has correct content types."""
        # First-Party
        from mcpgateway.routers.well_known import WELL_KNOWN_REGISTRY

        # Most should be text/plain
        text_files = ["robots.txt", "security.txt", "ai.txt", "dnt-policy.txt", "change-password"]

        for file in text_files:
            assert WELL_KNOWN_REGISTRY[file]["content_type"] == "text/plain"


# NOTE: RFC 9728 compliant OAuth Protected Resource tests are in test_well_known_rfc9728.py
# The deprecated query-parameter endpoint now returns 404 (tested in routers/test_well_known.py)


class TestServerRouterOAuthProtectedResource:
    """Test deprecated server-scoped OAuth Protected Resource endpoint."""

    @pytest.fixture
    def client(self, main_app_with_admin_api):
        """Create a test client for the FastAPI app."""
        return TestClient(main_app_with_admin_api)

    def test_server_oauth_protected_resource_deprecated_redirects(self, client):
        """Test that deprecated server-scoped endpoint returns 301 redirect to RFC 9728 path."""
        # The deprecated endpoint now returns 301 redirect regardless of server state
        response = client.get("/servers/test-server-id/.well-known/oauth-protected-resource", follow_redirects=False)
        assert response.status_code == 301
        assert "Location" in response.headers
        redirect_url = response.headers["Location"]
        assert "/.well-known/oauth-protected-resource/servers/test-server-id/mcp" in redirect_url
