"""Additional tests for Secret Manager edge cases and coverage gaps.

These tests address code review findings:
- MEDIUM #10: Thread safety claims lack concurrency tests
- MEDIUM #11: Secret URL canonicalization not fully tested
- Coverage gaps in environment variable validation

NOTE: These tests require mca_sdk.security.secret_manager (Story 3-9).
Tests are skipped if module is not available.
"""

import os
import pytest
import threading
import time
from unittest.mock import Mock, patch

# Check if secret_manager module is available (Story 3-9)
try:
    from mca_sdk.security.secret_manager import SecretManagerClient
    from mca_sdk.security._testing import reset_secret_manager_for_testing

    SECRET_MANAGER_AVAILABLE = True
except ImportError:
    SECRET_MANAGER_AVAILABLE = False
    pytestmark = pytest.mark.skip(reason="secret_manager module not yet implemented (Story 3-9)")

from mca_sdk.config.settings import MCAConfig


@pytest.fixture(autouse=True)
def reset_singleton():
    """Reset singleton instance before each test."""
    reset_secret_manager_for_testing()
    yield
    reset_secret_manager_for_testing()


class TestEdgeCasesAndCoverage:
    """Additional tests for edge cases and coverage gaps."""

    @patch("mca_sdk.security.secret_manager.secretmanager.SecretManagerServiceClient")
    def test_ttl_configuration_via_all_env_vars(self, mock_sm_client):
        """Test all TTL environment variables are respected."""
        with patch.dict(
            os.environ,
            {
                "MCA_SECRET_CACHE_TTL": "7200",
                "MCA_SECRET_MAX_CACHE_TTL": "86400",
                "MCA_SECRET_FAILURE_CACHE_TTL": "20",
                "MCA_SECRET_DEBOUNCE_TTL": "3",
            },
            clear=False,
        ):
            client = SecretManagerClient(register_atexit=False)
            assert client._cache_ttl == 7200
            assert client.max_cache_ttl == 86400
            assert client.failure_cache_ttl == 20
            assert client.deterministic_error_debounce_ttl == 3

    @patch("mca_sdk.security.secret_manager.secretmanager.SecretManagerServiceClient")
    def test_ttl_env_var_invalid_values_use_defaults(self, mock_sm_client):
        """Test invalid environment variable values fallback to defaults."""
        with patch.dict(
            os.environ,
            {
                "MCA_SECRET_CACHE_TTL": "not-a-number",
                "MCA_SECRET_MAX_CACHE_TTL": "-100",
                "MCA_SECRET_FAILURE_CACHE_TTL": "invalid",
                "MCA_SECRET_DEBOUNCE_TTL": "-5",
            },
            clear=False,
        ):
            client = SecretManagerClient(register_atexit=False)
            assert client._cache_ttl == 3600  # Default
            assert client.max_cache_ttl == 604800  # Default
            assert client.failure_cache_ttl == 10  # Default
            assert client.deterministic_error_debounce_ttl == 1  # Default

    @patch("mca_sdk.security.secret_manager.secretmanager.SecretManagerServiceClient")
    def test_parse_ttl_with_query_params_edge_cases(self, mock_sm_client):
        """Test TTL parsing with various query parameter edge cases."""
        client = SecretManagerClient(register_atexit=False)

        # Negative TTL
        ttl = client._parse_ttl_from_url("secret://p/s?ttl=-100")
        assert ttl == 3600  # Falls back to default

        # Invalid string
        ttl = client._parse_ttl_from_url("secret://p/s?ttl=invalid")
        assert ttl == 3600  # Falls back to default

        # Excessive TTL (> max)
        ttl = client._parse_ttl_from_url("secret://p/s?ttl=999999999")
        assert ttl == 604800  # Clamped to max

    @patch("mca_sdk.security.secret_manager.secretmanager.SecretManagerServiceClient")
    def test_close_handles_missing_attributes(self, mock_sm_client):
        """Test close() handles partially initialized instances gracefully."""
        client = SecretManagerClient(register_atexit=False)

        # Remove attributes to simulate partial initialization
        delattr(client, "_client")
        delattr(client, "_cache")

        # Should not raise
        client.close()

    @patch("mca_sdk.security.secret_manager.secretmanager.SecretManagerServiceClient")
    def test_race_condition_invalidate_during_fetch(self, mock_sm_client):
        """Test invalidate() called concurrently during fetch."""
        mock_instance = Mock()
        mock_sm_client.return_value = mock_instance
        mock_response = Mock()
        mock_response.payload.data = b"test-value"
        mock_instance.access_secret_version.return_value = mock_response

        client = SecretManagerClient(register_atexit=False)

        # Pre-populate cache
        client.fetch_secret("secret://proj/secret/latest")

        results = []

        def invalidate_worker():
            results.append(client.invalidate("secret://proj/secret/latest"))

        def fetch_worker():
            try:
                results.append(client.fetch_secret("secret://proj/secret/latest"))
            except Exception as e:
                results.append(str(e))

        # Start concurrent operations
        threads = [
            threading.Thread(target=invalidate_worker),
            threading.Thread(target=fetch_worker),
        ]

        for t in threads:
            t.start()
        for t in threads:
            t.join()

        # Should complete without deadlock or exceptions
        assert len(results) == 2

    @patch("mca_sdk.security.secret_manager.secretmanager.SecretManagerServiceClient")
    def test_concurrent_clear_cache_during_fetch(self, mock_sm_client):
        """Test clear_cache() called concurrently during active fetches."""
        mock_instance = Mock()
        mock_sm_client.return_value = mock_instance
        mock_response = Mock()
        mock_response.payload.data = b"test-value"

        # Slow fetch to ensure concurrency
        def slow_fetch(*args, **kwargs):
            time.sleep(0.1)
            return mock_response

        mock_instance.access_secret_version.side_effect = slow_fetch

        client = SecretManagerClient(register_atexit=False)

        results = []

        def clear_worker():
            results.append(("clear", client.clear_cache()))

        def fetch_worker():
            try:
                val = client.fetch_secret("secret://proj/secret/latest")
                results.append(("fetch", val))
            except Exception as e:
                results.append(("error", str(e)))

        # Start concurrent operations
        threads = [
            threading.Thread(target=fetch_worker),
            threading.Thread(target=clear_worker),
        ]

        for t in threads:
            t.start()
        for t in threads:
            t.join()

        # Should complete without deadlock
        assert len(results) == 2


class TestIntegrationScenarios:
    """Integration-style tests (still mocked but closer to real usage)."""

    @pytest.mark.integration
    @patch("mca_sdk.security.secret_manager.secretmanager.SecretManagerServiceClient")
    def test_full_config_integration_workflow(self, mock_sm_client):
        """Test complete workflow: config load → secret resolution → usage.

        This test simulates real-world usage where:
        1. Config has secret:// URLs
        2. Secrets are fetched from GCP (mocked)
        3. Config is used for authentication

        NOTE: This is a mock-based integration test. For true integration testing
        with real GCP Secret Manager, set GOOGLE_APPLICATION_CREDENTIALS and run:
            pytest -m integration tests/integration/
        """
        # Mock GCP client
        mock_instance = Mock()
        mock_sm_client.return_value = mock_instance
        mock_response = Mock()
        mock_response.payload.data = b"actual-token-from-gcp"
        mock_instance.access_secret_version.return_value = mock_response

        # Simulate config loading with secret URL
        config_dict = {
            "service_name": "test-service",
            "registry_token": "secret://my-project/registry-token/latest",
        }

        # Resolve secrets (as MCAConfig.load() does)
        resolved = MCAConfig._resolve_secrets(config_dict, "MCA_")

        # Verify secret was resolved
        assert resolved["registry_token"] == "actual-token-from-gcp"

        # Verify GCP was called with correct resource name
        mock_instance.access_secret_version.assert_called_once()
        call_args = mock_instance.access_secret_version.call_args
        assert (
            "projects/my-project/secrets/registry-token/versions/latest"
            in call_args[1]["request"]["name"]
        )
