"""
Tests for storage configuration file loader.

TDD Phase 1: RED - Write tests first, then implement to pass.
"""

import os
import shutil
import tempfile
from pathlib import Path
from unittest.mock import patch

import pytest


@pytest.fixture
def temp_dir() -> Path:
    """Create a temporary directory for tests."""
    temp_dir = Path(tempfile.mkdtemp())
    yield temp_dir
    shutil.rmtree(temp_dir, ignore_errors=True)


class TestEnvVarSubstitution:
    """Tests for environment variable substitution in config values."""

    def test_substitute_simple_env_var(self) -> None:
        """Test substituting a simple environment variable."""
        from shared.storage.config_loader import substitute_env_vars

        with patch.dict(os.environ, {"MY_VAR": "my_value"}):
            result = substitute_env_vars("${MY_VAR}")
            assert result == "my_value"

    def test_substitute_env_var_with_default(self) -> None:
        """Test substituting env var with default when var is not set."""
        from shared.storage.config_loader import substitute_env_vars

        # Ensure var is not set
        os.environ.pop("UNSET_VAR", None)

        result = substitute_env_vars("${UNSET_VAR:-default_value}")
        assert result == "default_value"

    def test_substitute_env_var_ignores_default_when_set(self) -> None:
        """Test that default is ignored when env var is set."""
        from shared.storage.config_loader import substitute_env_vars

        with patch.dict(os.environ, {"SET_VAR": "actual_value"}):
            result = substitute_env_vars("${SET_VAR:-default_value}")
            assert result == "actual_value"

    def test_substitute_multiple_env_vars(self) -> None:
        """Test substituting multiple env vars in one string."""
        from shared.storage.config_loader import substitute_env_vars

        with patch.dict(os.environ, {"HOST": "localhost", "PORT": "5432"}):
            result = substitute_env_vars("postgres://${HOST}:${PORT}/db")
            assert result == "postgres://localhost:5432/db"

    def test_substitute_preserves_non_env_text(self) -> None:
        """Test that non-env-var text is preserved."""
        from shared.storage.config_loader import substitute_env_vars

        with patch.dict(os.environ, {"VAR": "value"}):
            result = substitute_env_vars("prefix_${VAR}_suffix")
            assert result == "prefix_value_suffix"

    def test_substitute_empty_string_when_not_set_and_no_default(self) -> None:
        """Test that empty string is returned when var not set and no default."""
        from shared.storage.config_loader import substitute_env_vars

        os.environ.pop("MISSING_VAR", None)

        result = substitute_env_vars("${MISSING_VAR}")
        assert result == ""


class TestLoadStorageConfigYaml:
    """Tests for loading storage configuration from YAML files."""

    def test_load_local_backend_config(self, temp_dir: Path) -> None:
        """Test loading configuration for local backend."""
        from shared.storage.config_loader import load_storage_config

        config_path = temp_dir / "storage.yaml"
        config_path.write_text("""
backend: local
presigned_url_expiry: 3600

local:
  base_path: ./data/storage
""")

        config = load_storage_config(config_path)

        assert config.backend_type == "local"
        assert config.presigned_url_expiry == 3600
        assert config.local is not None
        assert config.local.base_path == Path("./data/storage")

    def test_load_azure_backend_config(self, temp_dir: Path) -> None:
        """Test loading configuration for Azure backend."""
        from shared.storage.config_loader import load_storage_config

        config_path = temp_dir / "storage.yaml"
        config_path.write_text("""
backend: azure_blob
presigned_url_expiry: 7200

azure:
  connection_string: DefaultEndpointsProtocol=https;AccountName=test
  container_name: documents
  create_container: true
""")

        config = load_storage_config(config_path)

        assert config.backend_type == "azure_blob"
        assert config.presigned_url_expiry == 7200
        assert config.azure is not None
        assert config.azure.connection_string == "DefaultEndpointsProtocol=https;AccountName=test"
        assert config.azure.container_name == "documents"
        assert config.azure.create_container is True

    def test_load_s3_backend_config(self, temp_dir: Path) -> None:
        """Test loading configuration for S3 backend."""
        from shared.storage.config_loader import load_storage_config

        config_path = temp_dir / "storage.yaml"
        config_path.write_text("""
backend: s3
presigned_url_expiry: 1800

s3:
  bucket_name: my-bucket
  region_name: us-west-2
  endpoint_url: http://localhost:9000
  create_bucket: false
""")

        config = load_storage_config(config_path)

        assert config.backend_type == "s3"
        assert config.presigned_url_expiry == 1800
        assert config.s3 is not None
        assert config.s3.bucket_name == "my-bucket"
        assert config.s3.region_name == "us-west-2"
        assert config.s3.endpoint_url == "http://localhost:9000"
        assert config.s3.create_bucket is False

    def test_load_config_with_env_var_substitution(self, temp_dir: Path) -> None:
        """Test that environment variables are substituted in config."""
        from shared.storage.config_loader import load_storage_config

        config_path = temp_dir / "storage.yaml"
        config_path.write_text("""
backend: ${STORAGE_BACKEND:-local}

local:
  base_path: ${STORAGE_PATH:-./default/path}
""")

        with patch.dict(os.environ, {"STORAGE_BACKEND": "local", "STORAGE_PATH": "/custom/path"}):
            config = load_storage_config(config_path)

            assert config.backend_type == "local"
            assert config.local is not None
            assert config.local.base_path == Path("/custom/path")

    def test_load_config_file_not_found_raises(self, temp_dir: Path) -> None:
        """Test that FileNotFoundError is raised for missing config file."""
        from shared.storage.config_loader import load_storage_config

        with pytest.raises(FileNotFoundError):
            load_storage_config(temp_dir / "nonexistent.yaml")

    def test_load_config_invalid_yaml_raises(self, temp_dir: Path) -> None:
        """Test that ValueError is raised for invalid YAML."""
        from shared.storage.config_loader import load_storage_config

        config_path = temp_dir / "storage.yaml"
        config_path.write_text("invalid: yaml: content: [")

        with pytest.raises(ValueError, match="Invalid"):
            load_storage_config(config_path)

    def test_load_config_missing_backend_raises(self, temp_dir: Path) -> None:
        """Test that ValueError is raised when backend is missing."""
        from shared.storage.config_loader import load_storage_config

        config_path = temp_dir / "storage.yaml"
        config_path.write_text("""
local:
  base_path: ./data
""")

        with pytest.raises(ValueError, match="backend"):
            load_storage_config(config_path)

    def test_load_config_default_presigned_url_expiry(self, temp_dir: Path) -> None:
        """Test default presigned_url_expiry when not specified."""
        from shared.storage.config_loader import load_storage_config

        config_path = temp_dir / "storage.yaml"
        config_path.write_text("""
backend: local

local:
  base_path: ./data
""")

        config = load_storage_config(config_path)

        assert config.presigned_url_expiry == 3600  # Default value


class TestStorageFileConfig:
    """Tests for StorageFileConfig dataclass."""

    def test_storage_file_config_is_immutable(self) -> None:
        """Test that StorageFileConfig is frozen (immutable)."""
        from shared.storage.config_loader import StorageFileConfig

        config = StorageFileConfig(backend_type="local")

        with pytest.raises(AttributeError):
            config.backend_type = "azure_blob"  # type: ignore

    def test_storage_file_config_defaults(self) -> None:
        """Test StorageFileConfig default values."""
        from shared.storage.config_loader import StorageFileConfig

        config = StorageFileConfig(backend_type="local")

        assert config.backend_type == "local"
        assert config.local is None
        assert config.azure is None
        assert config.s3 is None
        assert config.presigned_url_expiry == 3600


class TestLocalConfig:
    """Tests for LocalConfig dataclass."""

    def test_local_config_creation(self) -> None:
        """Test creating LocalConfig."""
        from shared.storage.config_loader import LocalConfig

        config = LocalConfig(base_path=Path("/data/storage"))

        assert config.base_path == Path("/data/storage")

    def test_local_config_is_immutable(self) -> None:
        """Test that LocalConfig is frozen."""
        from shared.storage.config_loader import LocalConfig

        config = LocalConfig(base_path=Path("/data"))

        with pytest.raises(AttributeError):
            config.base_path = Path("/other")  # type: ignore


class TestAzureConfig:
    """Tests for AzureConfig dataclass."""

    def test_azure_config_creation(self) -> None:
        """Test creating AzureConfig."""
        from shared.storage.config_loader import AzureConfig

        config = AzureConfig(
            connection_string="test_connection",
            container_name="test_container",
            create_container=True,
        )

        assert config.connection_string == "test_connection"
        assert config.container_name == "test_container"
        assert config.create_container is True

    def test_azure_config_defaults(self) -> None:
        """Test AzureConfig default values."""
        from shared.storage.config_loader import AzureConfig

        config = AzureConfig(
            connection_string="conn",
            container_name="container",
        )

        assert config.create_container is False

    def test_azure_config_is_immutable(self) -> None:
        """Test that AzureConfig is frozen."""
        from shared.storage.config_loader import AzureConfig

        config = AzureConfig(
            connection_string="conn",
            container_name="container",
        )

        with pytest.raises(AttributeError):
            config.container_name = "other"  # type: ignore


class TestS3Config:
    """Tests for S3Config dataclass."""

    def test_s3_config_creation(self) -> None:
        """Test creating S3Config."""
        from shared.storage.config_loader import S3Config

        config = S3Config(
            bucket_name="my-bucket",
            region_name="us-east-1",
            access_key_id="AKIAIOSFODNN7EXAMPLE",
            secret_access_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
            endpoint_url="http://localhost:9000",
            create_bucket=True,
        )

        assert config.bucket_name == "my-bucket"
        assert config.region_name == "us-east-1"
        assert config.access_key_id == "AKIAIOSFODNN7EXAMPLE"
        assert config.secret_access_key == "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
        assert config.endpoint_url == "http://localhost:9000"
        assert config.create_bucket is True

    def test_s3_config_minimal(self) -> None:
        """Test S3Config with only required fields."""
        from shared.storage.config_loader import S3Config

        config = S3Config(bucket_name="bucket")

        assert config.bucket_name == "bucket"
        assert config.region_name is None
        assert config.access_key_id is None
        assert config.secret_access_key is None
        assert config.endpoint_url is None
        assert config.create_bucket is False

    def test_s3_config_is_immutable(self) -> None:
        """Test that S3Config is frozen."""
        from shared.storage.config_loader import S3Config

        config = S3Config(bucket_name="bucket")

        with pytest.raises(AttributeError):
            config.bucket_name = "other"  # type: ignore
