"""P0-2 修复：OSS 凭证验证 + 启动诊断。

验证：
1. 非开发环境 OSS_ACCESS_KEY_ID/SECRET 为空时启动失败
2. 开发环境允许空凭证
3. diagnose() 方法正确输出配置状态
"""

import logging
import os
import sys
from unittest.mock import MagicMock, patch

import pytest


def _fresh_settings(env: str):
    """清除配置缓存，以指定 APP_ENV 重新创建设置实例。

    为非开发环境预设 OSS 环境变量，确保实例化能成功完成。
    测试方法内可根据需要清除这些变量来测试验证器。
    """
    import os

    os.environ["APP_ENV"] = env
    # 非开发环境下，为模块级导入提供有效凭证（避免导入时验证失败）
    if env != "development":
        os.environ.setdefault("OSS_ACCESS_KEY_ID", "test-key-for-import")
        os.environ.setdefault("OSS_ACCESS_KEY_SECRET", "test-secret-for-import")
    # 清空统一配置缓存，让测试方法自行控制实例化
    from packages.config import APISettings as Settings
    from packages.config import reload_settings_cache

    reload_settings_cache()
    return Settings


class TestOSSCredentialValidation:
    """测试 OSS 凭证验证器（直接调用验证器类方法）。"""

    def test_empty_oss_key_id_rejected_in_staging(self):
        """非开发环境 OSS_ACCESS_KEY_ID 为空应报错。"""
        Settings = _fresh_settings("staging")
        with pytest.raises(Exception, match="OSS_ACCESS_KEY_ID"):
            Settings.validate_oss_access_key_id("")

    def test_empty_oss_key_secret_rejected_in_staging(self):
        """非开发环境 OSS_ACCESS_KEY_SECRET 为空应报错。"""
        Settings = _fresh_settings("staging")
        with pytest.raises(Exception, match="OSS_ACCESS_KEY_SECRET"):
            Settings.validate_oss_access_key_secret("")

    def test_empty_oss_credentials_allowed_in_development(self):
        """开发环境允许空 OSS 凭证。"""
        Settings = _fresh_settings("development")
        assert Settings.validate_oss_access_key_id("") == ""
        assert Settings.validate_oss_access_key_secret("") == ""

    def test_valid_credentials_pass_validation(self):
        """有效凭证应通过验证。"""
        Settings = _fresh_settings("staging")
        assert Settings.validate_oss_access_key_id("test-key-id") == "test-key-id"
        assert Settings.validate_oss_access_key_secret("test-key-secret") == "test-key-secret"

    def test_valid_credentials_instantiation_succeeds(self):
        """有效凭证应能成功创建 Settings 实例。"""
        os.environ.pop("OSS_ACCESS_KEY_ID", None)
        os.environ.pop("OSS_ACCESS_KEY_SECRET", None)
        Settings = _fresh_settings("staging")
        os.environ["OSS_ACCESS_KEY_ID"] = "test-key-id"
        os.environ["OSS_ACCESS_KEY_SECRET"] = "test-key-secret"
        s = Settings(_env_file=None)
        assert s.OSS_ACCESS_KEY_ID == "test-key-id"
        assert s.OSS_ACCESS_KEY_SECRET == "test-key-secret"


class TestOSSDiagnose:
    """测试 SharedStorageService.diagnose() 方法。"""

    @patch("packages.shared.storage.oss2", None)
    @patch("packages.shared.storage.get_shared_settings")
    def test_diagnose_logs_error_when_bucket_none(self, mock_settings, caplog):
        """bucket=None 时 diagnose 应输出 ERROR 日志。"""
        from packages.shared.storage import SharedStorageService

        mock_settings.return_value.oss_bucket_name = "test-bucket"
        mock_settings.return_value.oss_endpoint = "oss-cn-test.com"
        mock_settings.return_value.oss_access_key_id = ""
        mock_settings.return_value.oss_access_key_secret = ""

        service = SharedStorageService()
        assert service.bucket is None

        with caplog.at_level(logging.ERROR, logger="packages.shared.storage"):
            service.diagnose()

        assert any("❌" in record.message for record in caplog.records)

    @patch("packages.shared.storage.oss2")
    @patch("packages.shared.storage.get_shared_settings")
    def test_diagnose_logs_success_when_bucket_configured(self, mock_settings, mock_oss2, caplog):
        """bucket 已配置时 diagnose 应输出成功日志。"""
        from packages.shared.storage import SharedStorageService

        mock_settings.return_value.oss_bucket_name = "test-bucket"
        mock_settings.return_value.oss_endpoint = "oss-cn-test.com"
        mock_settings.return_value.oss_access_key_id = "test-key-id"
        mock_settings.return_value.oss_access_key_secret = "test-key-secret"
        mock_oss2.Bucket.return_value = MagicMock()

        service = SharedStorageService()
        assert service.bucket is not None

        with caplog.at_level(logging.INFO, logger="packages.shared.storage"):
            service.diagnose()

        assert any("OSS诊断" in record.message for record in caplog.records)


class TestOSSHTTPSEndpoint:
    """测试 P0-2 真正根因：sign_url 必须返回 HTTPS URL。"""

    @patch("packages.shared.storage.oss2")
    @patch("packages.shared.storage.get_shared_settings")
    def test_endpoint_without_scheme_gets_https_prefix(self, mock_settings, mock_oss2):
        """endpoint 无 scheme 时应自动加 https://，确保 sign_url 生成 HTTPS URL。"""
        from packages.shared.storage import SharedStorageService

        mock_settings.return_value.oss_bucket_name = "test-bucket"
        mock_settings.return_value.oss_endpoint = "oss-cn-hangzhou.aliyuncs.com"
        mock_settings.return_value.oss_access_key_id = "test-key-id"
        mock_settings.return_value.oss_access_key_secret = "test-key-secret"
        mock_oss2.Bucket.return_value = MagicMock()

        SharedStorageService()

        # 验证传给 oss2.Bucket 的 endpoint 带了 https://
        call_args = mock_oss2.Bucket.call_args
        endpoint_passed = call_args[0][1]  # 第二个位置参数
        assert endpoint_passed == "https://oss-cn-hangzhou.aliyuncs.com"

    @patch("packages.shared.storage.oss2")
    @patch("packages.shared.storage.get_shared_settings")
    def test_endpoint_with_existing_https_not_doubled(self, mock_settings, mock_oss2):
        """endpoint 已有 https:// 时不应重复添加。"""
        from packages.shared.storage import SharedStorageService

        mock_settings.return_value.oss_bucket_name = "test-bucket"
        mock_settings.return_value.oss_endpoint = "https://oss-cn-hangzhou.aliyuncs.com"
        mock_settings.return_value.oss_access_key_id = "test-key-id"
        mock_settings.return_value.oss_access_key_secret = "test-key-secret"
        mock_oss2.Bucket.return_value = MagicMock()

        SharedStorageService()

        call_args = mock_oss2.Bucket.call_args
        endpoint_passed = call_args[0][1]
        assert endpoint_passed == "https://oss-cn-hangzhou.aliyuncs.com"
