#!/usr/bin/env python3
"""
Storage provider regression tests.

These tests keep the automated suite deterministic by treating real MinIO/OSS
as optional integration surfaces. Core automation validates fake storage,
factory/env selection, and provider construction semantics via mocks.
"""

from __future__ import annotations

import asyncio
import os
import sys
import tempfile
from pathlib import Path
from unittest.mock import Mock, patch

from test_support import install_sdk_dependency_stubs

install_sdk_dependency_stubs()

sys.path.insert(0, str(Path(__file__).parent.parent))

from sandai.operator.storage import (
    FakeStorageProvider,
    MinIOStorageProvider,
    StorageConfigurationError,
    create_default_storage_provider,
    create_storage_provider,
    get_storage_type_from_env,
)


def test_fake_storage_provider() -> bool:
    provider = FakeStorageProvider()
    assert provider.storage_type == "fake"

    with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
        f.write("fake storage content")
        test_file_path = f.name

    try:
        async def roundtrip():
            uri, meta = await provider.upload_file(test_file_path)
            with tempfile.TemporaryDirectory() as temp_dir:
                download_path = os.path.join(temp_dir, "downloaded.txt")
                downloaded = await provider.download_file(uri, download_path)
                return uri, meta, downloaded, Path(download_path).read_text(encoding="utf-8")

        uri, meta, downloaded, content = asyncio.run(roundtrip())
        assert uri.startswith("fake://")
        assert meta["type"] == "fake"
        assert downloaded.endswith("downloaded.txt")
        assert "Mock file content" in content
        return True
    finally:
        if os.path.exists(test_file_path):
            os.unlink(test_file_path)


def test_minio_provider_uses_boto_client_without_real_network() -> bool:
    with patch("boto3.client") as mock_boto3:
        mock_s3_client = Mock()
        mock_boto3.return_value = mock_s3_client

        provider = MinIOStorageProvider()

        assert provider.storage_type == "minio"
        mock_boto3.assert_called_once()
        mock_s3_client.head_bucket.assert_not_called()

        with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
            f.write("minio upload")
            temp_path = f.name

        try:
            mock_s3_client.head_bucket.return_value = {}
            mock_s3_client.upload_file.return_value = None
            uri, meta = asyncio.run(provider.upload_file(temp_path))
            assert uri.startswith("s3://")
            assert meta["type"] == "s3"
            mock_s3_client.head_bucket.assert_called_once()
            mock_s3_client.upload_file.assert_called_once()
        finally:
            if os.path.exists(temp_path):
                os.unlink(temp_path)
        return True


def test_storage_provider_factory() -> bool:
    fake_provider = create_storage_provider("fake")
    assert fake_provider.storage_type == "fake"

    with patch("boto3.client") as mock_boto3:
        mock_s3_client = Mock()
        mock_s3_client.head_bucket.return_value = {}
        mock_boto3.return_value = mock_s3_client

        minio_provider = create_storage_provider("minio")
        assert minio_provider.storage_type == "minio"

    try:
        create_storage_provider("invalid")
        raise AssertionError("expected StorageConfigurationError for invalid storage type")
    except StorageConfigurationError:
        pass

    auto_provider = create_storage_provider()
    assert auto_provider.storage_type == "fake"
    return True


def test_environment_storage_type() -> bool:
    with patch.dict(os.environ, {}, clear=True):
        assert get_storage_type_from_env() is None

    test_cases = [
        ("fake", "fake"),
        ("minio", "minio"),
        ("FAKE", "fake"),
        ("MinIO", "minio"),
    ]
    for env_value, expected in test_cases:
        with patch.dict(os.environ, {"SANDAI_OPERATOR_STORAGE_TYPE": env_value}):
            assert get_storage_type_from_env() == expected

    with patch.dict(os.environ, {"SANDAI_OPERATOR_STORAGE_TYPE": "invalid"}):
        try:
            get_storage_type_from_env()
            raise AssertionError("expected StorageConfigurationError for invalid env storage type")
        except StorageConfigurationError:
            pass

    return True


def test_default_storage_provider_prefers_fake_unless_env_requests_otherwise() -> bool:
    with patch.dict(os.environ, {}, clear=True):
        provider = create_default_storage_provider()
        assert provider.storage_type == "fake"

    with patch.dict(os.environ, {"SANDAI_OPERATOR_STORAGE_TYPE": "fake"}):
        provider = create_default_storage_provider()
        assert provider.storage_type == "fake"

    with patch.dict(os.environ, {"SANDAI_OPERATOR_STORAGE_TYPE": "minio"}):
        with patch("boto3.client") as mock_boto3:
            mock_s3_client = Mock()
            mock_s3_client.head_bucket.return_value = {}
            mock_boto3.return_value = mock_s3_client
            provider = create_default_storage_provider()
            assert provider.storage_type == "minio"

    return True


def main() -> int:
    tests = [
        ("fake_provider", test_fake_storage_provider),
        ("minio_constructor", test_minio_provider_uses_boto_client_without_real_network),
        ("factory", test_storage_provider_factory),
        ("env", test_environment_storage_type),
        ("default_provider", test_default_storage_provider_prefers_fake_unless_env_requests_otherwise),
    ]

    passed = 0
    failed = 0

    print("Storage provider regression tests")
    print("=" * 60)
    for name, test_func in tests:
        try:
            test_func()
            print(f"PASS {name}")
            passed += 1
        except Exception as exc:
            print(f"FAIL {name}: {exc}")
            import traceback
            traceback.print_exc()
            failed += 1
    print("=" * 60)
    print(f"passed={passed} failed={failed}")
    return 0 if failed == 0 else 1


if __name__ == "__main__":
    raise SystemExit(main())
