"""
Unit tests for fabias.secrets module using mocked HTTP calls.

Tests Key Vault client, module-level API, and authentication.
"""

from unittest.mock import Mock, patch

import pytest
import responses

from fabias._shared.auth import ServicePrincipalAuth
from fabias._shared.exceptions import FabiasError
from fabias.integrations.keyvault import KeyVaultClient, client, get, list, reset, set, vault


class TestKeyVaultClient:
    """Test KeyVaultClient class"""

    @responses.activate
    def test_get_secret_via_rest_api(self):
        """Test retrieving a secret via REST API"""
        # Mock token endpoint
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/test-tenant/oauth2/v2.0/token",
            json={"access_token": "test-token", "expires_in": 3600},
            status=200,
        )

        # Mock Key Vault secret endpoint
        responses.add(
            responses.GET,
            "https://test-vault.vault.azure.net/secrets/test-key?api-version=7.3",
            json={"value": "secret-value-123"},
            status=200,
        )

        auth = ServicePrincipalAuth(
            tenant_id="test-tenant", client_id="test-client", client_secret="test-secret"
        )
        client = KeyVaultClient(vault_url="https://test-vault.vault.azure.net/", auth=auth)

        result = client.getSecret("test-key")
        assert result == "secret-value-123"

    @responses.activate
    def test_get_secret_normalizes_vault_url(self):
        """Test that vault URL is normalized with trailing slash"""
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/test-tenant/oauth2/v2.0/token",
            json={"access_token": "test-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.GET,
            "https://test-vault.vault.azure.net/secrets/key1?api-version=7.3",
            json={"value": "value1"},
            status=200,
        )

        auth = ServicePrincipalAuth(
            tenant_id="test-tenant", client_id="test-client", client_secret="test-secret"
        )
        # No trailing slash
        client = KeyVaultClient(vault_url="https://test-vault.vault.azure.net", auth=auth)

        assert client.vaultUrl == "https://test-vault.vault.azure.net/"
        result = client.getSecret("key1")
        assert result == "value1"

    @responses.activate
    def test_set_secret(self):
        """Test setting a secret in Key Vault"""
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/test-tenant/oauth2/v2.0/token",
            json={"access_token": "test-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.PUT,
            "https://test-vault.vault.azure.net/secrets/new-key?api-version=7.3",
            json={"value": "new-value"},
            status=200,
        )

        auth = ServicePrincipalAuth(
            tenant_id="test-tenant", client_id="test-client", client_secret="test-secret"
        )
        client = KeyVaultClient(vault_url="https://test-vault.vault.azure.net/", auth=auth)

        # Capture print output
        import io
        import sys

        captured_output = io.StringIO()
        sys.stdout = captured_output

        client.setSecret("new-key", "new-value")

        sys.stdout = sys.__stdout__
        output = captured_output.getvalue()
        assert 'Key Vault secret "new-key" modified.' in output

    @responses.activate
    def test_list_secrets(self):
        """Test listing all secrets"""
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/test-tenant/oauth2/v2.0/token",
            json={"access_token": "test-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.GET,
            "https://test-vault.vault.azure.net/secrets?api-version=7.3",
            json={
                "value": [
                    {"id": "https://test-vault.vault.azure.net/secrets/key1"},
                    {"id": "https://test-vault.vault.azure.net/secrets/key2"},
                ]
            },
            status=200,
        )

        auth = ServicePrincipalAuth(
            tenant_id="test-tenant", client_id="test-client", client_secret="test-secret"
        )
        client = KeyVaultClient(vault_url="https://test-vault.vault.azure.net/", auth=auth)

        result = client.listSecrets()
        assert len(result) == 2
        assert result[0]["id"] == "https://test-vault.vault.azure.net/secrets/key1"

    @responses.activate
    def test_client_with_service_principal_credentials(self):
        """Test client initialization with service principal credentials"""
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/tenant-id/oauth2/v2.0/token",
            json={"access_token": "test-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.GET,
            "https://vault.vault.azure.net/secrets/test?api-version=7.3",
            json={"value": "test-value"},
            status=200,
        )

        # Pass credentials directly instead of auth object
        client = KeyVaultClient(
            vault_url="https://vault.vault.azure.net/",
            tenant_id="tenant-id",
            client_id="client-id",
            client_secret="client-secret",
        )

        result = client.getSecret("test")
        assert result == "test-value"

    @patch("fabias.integrations.keyvault.runtime")
    def test_get_secret_fabric_notebookutils_fallback(self, mock_runtime):
        """Test that Fabric runtime falls back to REST API if notebookutils fails"""
        mock_runtime.isFabric = True

        # Mock notebookutils that raises exception
        mock_nb = Mock()
        mock_nb.credentials.getSecret.side_effect = Exception("NotebookUtils failed")
        mock_runtime.notebookutils = mock_nb

        # Mock REST API response
        with responses.RequestsMock() as rsps:
            rsps.add(
                responses.POST,
                "https://login.microsoftonline.com/test-tenant/oauth2/v2.0/token",
                json={"access_token": "test-token", "expires_in": 3600},
                status=200,
            )

            rsps.add(
                responses.GET,
                "https://vault.vault.azure.net/secrets/test-key?api-version=7.3",
                json={"value": "fallback-value"},
                status=200,
            )

            auth = ServicePrincipalAuth(
                tenant_id="test-tenant", client_id="test-client", client_secret="test-secret"
            )
            client = KeyVaultClient(vault_url="https://vault.vault.azure.net/", auth=auth)

            result = client.getSecret("test-key")
            assert result == "fallback-value"


class TestModuleLevelAPI:
    """Test module-level singleton API"""

    def setup_method(self):
        """Reset module state before each test"""
        reset()

    def teardown_method(self):
        """Reset module state after each test"""
        reset()

    @responses.activate
    def test_configure_and_get(self):
        """Test client() then get() workflow"""
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/tenant-id/oauth2/v2.0/token",
            json={"access_token": "test-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.GET,
            "https://vault.vault.azure.net/secrets/my-key?api-version=7.3",
            json={"value": "my-value"},
            status=200,
        )

        client(
            vault_url="https://vault.vault.azure.net/",
            tenant_id="tenant-id",
            client_id="client-id",
            client_secret="client-secret",
        )

        result = get("my-key")
        assert result == "my-value"
        assert vault() == "https://vault.vault.azure.net/"

    @responses.activate
    def test_configure_and_set(self):
        """Test client() then set() workflow"""
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/tenant-id/oauth2/v2.0/token",
            json={"access_token": "test-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.PUT,
            "https://vault.vault.azure.net/secrets/new-key?api-version=7.3",
            json={"value": "new-value"},
            status=200,
        )

        client(
            vault_url="https://vault.vault.azure.net/",
            tenant_id="tenant-id",
            client_id="client-id",
            client_secret="client-secret",
        )

        import io
        import sys

        captured_output = io.StringIO()
        sys.stdout = captured_output

        set("new-key", "new-value")

        sys.stdout = sys.__stdout__
        output = captured_output.getvalue()
        assert 'Key Vault secret "new-key" modified.' in output

    @responses.activate
    def test_configure_and_list(self):
        """Test client() then list() workflow"""
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/tenant-id/oauth2/v2.0/token",
            json={"access_token": "test-token", "expires_in": 3600},
            status=200,
        )

        responses.add(
            responses.GET,
            "https://vault.vault.azure.net/secrets?api-version=7.3",
            json={
                "value": [
                    {"id": "https://vault.vault.azure.net/secrets/key1"},
                    {"id": "https://vault.vault.azure.net/secrets/key2"},
                ]
            },
            status=200,
        )

        client(
            vault_url="https://vault.vault.azure.net/",
            tenant_id="tenant-id",
            client_id="client-id",
            client_secret="client-secret",
        )

        result = list()
        assert len(result) == 2

    def test_get_without_configure_raises_error(self):
        """Test that calling get() without client() raises FabiasError"""
        with pytest.raises(FabiasError) as exc_info:
            get("some-key")
        assert "not configured" in str(exc_info.value).lower()

    def test_set_without_configure_raises_error(self):
        """Test that calling set() without client() raises FabiasError"""
        with pytest.raises(FabiasError) as exc_info:
            set("some-key", "some-value")
        assert "not configured" in str(exc_info.value).lower()

    def test_list_without_configure_raises_error(self):
        """Test that calling list() without client() raises FabiasError"""
        with pytest.raises(FabiasError) as exc_info:
            list()
        assert "not configured" in str(exc_info.value).lower()

    def test_vault_before_configure_returns_none(self):
        """Test that vault() returns None before client()"""
        assert vault() is None

    @responses.activate
    def test_reset_clears_configuration(self):
        """Test that reset() clears the module configuration"""
        responses.add(
            responses.POST,
            "https://login.microsoftonline.com/tenant-id/oauth2/v2.0/token",
            json={"access_token": "test-token", "expires_in": 3600},
            status=200,
        )

        client(
            vault_url="https://vault.vault.azure.net/",
            tenant_id="tenant-id",
            client_id="client-id",
            client_secret="client-secret",
        )

        assert vault() == "https://vault.vault.azure.net/"

        reset()

        assert vault() is None

        with pytest.raises(FabiasError):
            get("test-key")
