# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
import functools
import pytest
import conftest
from devtools_testutils import EnvironmentVariableLoader, recorded_by_proxy
from testcase import AppConfigTestCase, set_test_settings
from test_constants import APPCONFIGURATION_ENDPOINT_STRING
from azure.appconfiguration.provider._models import SettingSelector
from azure.appconfiguration.provider._constants import NULL_CHAR, FEATURE_MANAGEMENT_KEY, FEATURE_FLAG_KEY
from azure.appconfiguration.provider import WatchKey
from azure.appconfiguration import (
    ConfigurationSetting,
    FeatureFlagConfigurationSetting,
)

AppConfigProviderPreparer = functools.partial(
    EnvironmentVariableLoader,
    "appconfiguration",
    appconfiguration_endpoint_string=APPCONFIGURATION_ENDPOINT_STRING,
)


class TestSnapshotSupport:
    """Tests for snapshot functionality in SettingSelector."""

    def test_setting_selector_with_snapshot_name(self):
        """Test SettingSelector with snapshot_name parameter."""
        selector = SettingSelector(snapshot_name="my-snapshot")
        assert selector.snapshot_name == "my-snapshot"
        assert selector.key_filter is None
        assert selector.label_filter == NULL_CHAR
        assert selector.tag_filters is None

    def test_setting_selector_snapshot_name_with_key_filter_raises_error(self):
        """Test that SettingSelector raises ValueError when both snapshot_name and key_filter are provided."""
        with pytest.raises(
            ValueError,
            match=r"Cannot specify both snapshot_name and key_filter\. "
            r"When using snapshots, all other filtering parameters are ignored\.",
        ):
            SettingSelector(snapshot_name="my-snapshot", key_filter="*")

    def test_setting_selector_snapshot_name_with_label_filter_raises_error(self):
        """Test that SettingSelector raises ValueError when both snapshot_name and label_filter are provided."""
        with pytest.raises(
            ValueError,
            match=r"Cannot specify both snapshot_name and label_filter\. "
            r"When using snapshots, all other filtering parameters are ignored\.",
        ):
            SettingSelector(snapshot_name="my-snapshot", label_filter="prod")

    def test_setting_selector_snapshot_name_with_tag_filters_raises_error(self):
        """Test that SettingSelector raises ValueError when both snapshot_name and tag_filters are provided."""
        with pytest.raises(
            ValueError,
            match=r"Cannot specify both snapshot_name and tag_filters\. "
            r"When using snapshots, all other filtering parameters are ignored\.",
        ):
            SettingSelector(snapshot_name="my-snapshot", tag_filters=["env=prod"])

    def test_setting_selector_requires_key_filter_or_snapshot_name(self):
        """Test that SettingSelector requires either key_filter or snapshot_name."""
        with pytest.raises(ValueError, match=r"Either key_filter or snapshot_name must be specified\."):
            SettingSelector()

    def test_setting_selector_valid_combinations(self):
        """Test valid combinations of SettingSelector parameters."""
        # Valid: key_filter only
        selector = SettingSelector(key_filter="*")
        assert selector.key_filter == "*"
        assert selector.snapshot_name is None

        # Valid: snapshot_name only
        selector = SettingSelector(snapshot_name="my-snapshot")
        assert selector.snapshot_name == "my-snapshot"
        assert selector.key_filter is None

        # Valid: key_filter with other filters (no snapshot_name)
        selector = SettingSelector(key_filter="*", label_filter="prod", tag_filters=["env=prod"])
        assert selector.key_filter == "*"
        assert selector.label_filter == "prod"
        assert selector.tag_filters == ["env=prod"]
        assert selector.snapshot_name is None


class TestSnapshotProviderIntegration(AppConfigTestCase):
    """Integration tests for snapshot functionality with recorded tests."""

    def test_setting_selector_multiple_combinations(self):
        """Test using multiple selectors with mix of snapshot and regular selectors."""
        # This should be valid - mixing snapshot and regular selectors
        selectors = [
            SettingSelector(snapshot_name="config-snapshot"),  # Load from snapshot
            SettingSelector(key_filter="runtime.*"),  # Load runtime configs from current state
        ]

        # Validate each selector individually
        assert selectors[0].snapshot_name == "config-snapshot"
        assert selectors[0].key_filter is None

        assert selectors[1].key_filter == "runtime.*"
        assert selectors[1].snapshot_name is None

    def test_snapshot_with_different_names(self):
        """Test snapshot selectors with different snapshot names."""
        # Different snapshot names should be valid
        snapshot1 = SettingSelector(snapshot_name="prod-config-v1.0")
        snapshot2 = SettingSelector(snapshot_name="staging-config-v2.1")

        assert snapshot1.snapshot_name == "prod-config-v1.0"
        assert snapshot2.snapshot_name == "staging-config-v2.1"

        # Both should have no other filters
        for selector in [snapshot1, snapshot2]:
            assert selector.key_filter is None
            assert selector.label_filter == NULL_CHAR
            assert selector.tag_filters is None

    @AppConfigProviderPreparer()
    @recorded_by_proxy
    def test_load_provider_with_snapshot_not_found(self, appconfiguration_endpoint_string):
        """Test loading provider with a non-existent snapshot returns error."""
        # Try to load from a non-existent snapshot
        provider = self.create_client(
            endpoint=appconfiguration_endpoint_string,
            selects=[SettingSelector(snapshot_name="non-existent-snapshot")],
        )
        assert len(provider._dict) == 0, "Provider should have no settings when snapshot is not found"

    @AppConfigProviderPreparer()
    @recorded_by_proxy
    def test_load_provider_with_regular_selectors(self, appconfiguration_endpoint_string):
        """Test loading provider with regular selectors works (baseline test)."""
        # This should work - regular selector loading
        provider = self.create_client(
            endpoint=appconfiguration_endpoint_string,
            selects=[SettingSelector(key_filter="message")],  # Regular selector
        )

        # Verify we can access the configuration (message is set up by setup_configs)
        assert "message" in provider

    @pytest.mark.live_test_only  # Needed to fix an azure core dependency compatibility issue
    @AppConfigProviderPreparer()
    @recorded_by_proxy
    def test_create_snapshot_and_load_provider(self, appconfiguration_endpoint_string):
        """Test loading provider from a pre-created snapshot."""
        snapshot_name = conftest.snapshot_names["snapshot"]

        # Load provider using the snapshot with refresh enabled and feature flags enabled
        provider = self.create_client(
            endpoint=appconfiguration_endpoint_string,
            selects=[
                SettingSelector(snapshot_name=snapshot_name),  # Snapshot data (includes feature flags)
                SettingSelector(key_filter="refresh_test_key"),  # Non-snapshot key for refresh testing
            ],
            refresh_on=[WatchKey("refresh_test_key")],  # Watch non-snapshot key for refresh
            refresh_interval=1,  # Short refresh interval for testing
        )

        # Get the underlying SDK client from the provider
        sdk_client = provider._replica_client_manager._original_client._client

        # Verify all snapshot settings are loaded
        assert provider["snapshot_test_key1"] == "snapshot_test_value1"
        assert provider["snapshot_test_key2"] == "snapshot_test_value2"
        assert provider["snapshot_test_json"]["nested"] == "snapshot_value"
        assert provider["refresh_test_key"] == "original_refresh_value"

        # Verify that snapshot settings and refresh key are loaded
        snapshot_keys = [key for key in provider.keys() if key.startswith("snapshot_test_")]
        assert len(snapshot_keys) == 3

        # Verify feature flags from snapshots are NOT loaded as feature flags
        # (snapshots don't support feature flag loading, only regular selects do)
        assert FEATURE_MANAGEMENT_KEY not in provider, "Feature flags should not be loaded from snapshots"

        try:
            # Test snapshot immutability: modify the original settings
            modified_settings = [
                ConfigurationSetting(
                    key="snapshot_test_key1", value="MODIFIED_VALUE1", label=NULL_CHAR  # Changed value
                ),
                ConfigurationSetting(
                    key="snapshot_test_key2", value="MODIFIED_VALUE2", label=NULL_CHAR  # Changed value
                ),
                ConfigurationSetting(
                    key="snapshot_test_json",
                    value='{"nested": "MODIFIED_VALUE"}',  # Changed nested value
                    label=NULL_CHAR,
                    content_type="application/json",
                ),
                ConfigurationSetting(
                    key="refresh_test_key",
                    value="updated_refresh_value",  # Changed value to trigger refresh
                    label=NULL_CHAR,
                ),
            ]

            # Update the original settings with new values and add a new key
            new_key = ConfigurationSetting(key="new_key_added_after_load", value="new_value", label=NULL_CHAR)
            set_test_settings(sdk_client, modified_settings + [new_key])

            # Expire the refresh timer so refresh will check for changes
            provider._refresh_timer._next_refresh_time = 0

            # Refresh the existing provider (snapshots should remain immutable, but non-snapshot keys should update)
            provider.refresh()

            # Verify the snapshot still contains the original values after refresh (immutability)
            assert provider["snapshot_test_key1"] == "snapshot_test_value1"  # Original value
            assert provider["snapshot_test_key2"] == "snapshot_test_value2"  # Original value
            assert provider["snapshot_test_json"]["nested"] == "snapshot_value"  # Original value

            # Verify the non-snapshot key was updated during refresh
            assert provider["refresh_test_key"] == "updated_refresh_value"  # Updated value

            # Verify new keys are NOT added during refresh (only watched keys trigger full reload)
            assert "new_key_added_after_load" not in provider  # New key should not be loaded

            # Verify that loading without snapshot gets the modified values
            provider_current = self.create_client(
                endpoint=appconfiguration_endpoint_string,
                selects=[SettingSelector(key_filter="snapshot_test_*")],
            )

            # Current values should be the modified ones
            assert provider_current["snapshot_test_key1"] == "MODIFIED_VALUE1"  # Modified value
            assert provider_current["snapshot_test_key2"] == "MODIFIED_VALUE2"  # Modified value
            assert provider_current["snapshot_test_json"]["nested"] == "MODIFIED_VALUE"  # Modified value
        finally:
            # Restore modified settings to original values for other tests
            restored_settings = [
                ConfigurationSetting(key="snapshot_test_key1", value="snapshot_test_value1", label=NULL_CHAR),
                ConfigurationSetting(key="snapshot_test_key2", value="snapshot_test_value2", label=NULL_CHAR),
                ConfigurationSetting(
                    key="snapshot_test_json",
                    value='{"nested": "snapshot_value"}',
                    label=NULL_CHAR,
                    content_type="application/json",
                ),
                ConfigurationSetting(key="refresh_test_key", value="original_refresh_value", label=NULL_CHAR),
            ]
            set_test_settings(sdk_client, restored_settings)

    @pytest.mark.live_test_only  # Needed to fix an azure core dependency compatibility issue
    @AppConfigProviderPreparer()
    @recorded_by_proxy
    def test_create_snapshot_and_load_provider_with_feature_flags(self, appconfiguration_endpoint_string):
        """Test loading provider with feature flags from a pre-created snapshot."""
        snapshot_name = conftest.snapshot_names["ff_snapshot"]

        # Load provider using snapshot for config settings and regular selectors for feature flags
        provider = self.create_client(
            endpoint=appconfiguration_endpoint_string,
            feature_flag_enabled=True,  # Enable feature flags
            feature_flag_selectors=[
                SettingSelector(snapshot_name=snapshot_name),  # Load feature flags from snapshot
            ],
        )

        # Get the underlying SDK client from the provider
        sdk_client = provider._replica_client_manager._original_client._client

        # Verify snapshot configuration settings are loaded
        assert provider["ff_snapshot_test_key1"] == "ff_snapshot_test_value1"
        assert provider["ff_snapshot_test_key2"] == "ff_snapshot_test_value2"

        # Verify feature flags loaded via regular selectors ARE loaded
        feature_flags = provider.get(FEATURE_MANAGEMENT_KEY, {}).get(FEATURE_FLAG_KEY, [])
        feature_flag_ids = {ff["id"]: ff["enabled"] for ff in feature_flags}

        # Regular feature flags should be loaded
        assert "RegularFeature" not in feature_flag_ids, "RegularFeature should not be loaded via regular selector"
        assert "RegularFeatureDisabled" not in feature_flag_ids, "RegularFeatureDisabled should not be loaded"

        # Snapshot-only feature flag should be loaded as a feature flag
        assert "SnapshotOnlyFeature" in feature_flag_ids, "SnapshotOnlyFeature should be loaded as FF from snapshot"

        # Verify exactly 1 feature flag is loaded (the snapshot-only one)
        assert len(feature_flags) == 1, f"Expected 1 feature flag, got {len(feature_flags)}"

        try:
            # Modify the feature flags in the snapshot
            modified_feature_flags = [
                FeatureFlagConfigurationSetting(
                    feature_id="SnapshotOnlyFeature",
                    enabled=False,  # Changed from True to False
                    label=NULL_CHAR,
                ),
            ]

            set_test_settings(sdk_client, modified_feature_flags)

            # Load a fresh provider without snapshot to verify current feature flag values
            provider_current = self.create_client(
                endpoint=appconfiguration_endpoint_string,
                feature_flag_enabled=True,
                feature_flag_selectors=[
                    SettingSelector(snapshot_name=snapshot_name),  # Load feature flags from snapshot
                ],
            )

            # Current feature flag values should be the original ones from snapshot (immutable)
            current_feature_flags = provider_current.get(FEATURE_MANAGEMENT_KEY, {}).get(FEATURE_FLAG_KEY, [])
            current_ff_ids = {ff["id"]: ff["enabled"] for ff in current_feature_flags}
            assert current_ff_ids.get("SnapshotOnlyFeature") is True  # Original value from snapshot (not modified)
        finally:
            # Restore modified feature flag to original value for other tests
            set_test_settings(
                sdk_client,
                [FeatureFlagConfigurationSetting(feature_id="SnapshotOnlyFeature", enabled=True, label=NULL_CHAR)],
            )
