"""P0-staging：OSS 上传崩溃修复测试.

测试：
1. oss_bucket() 传递 connect_timeout 参数
2. upload_to_oss() 小文件走 put_object_from_file，大文件走分片上传
3. upload_to_oss() 超时保护（超过总超时返回 None）
4. upload_to_oss() 异常时返回 None
"""

from __future__ import annotations

import tempfile
import time
from pathlib import Path
from unittest.mock import MagicMock, patch

# ── oss_bucket connect_timeout 测试 ───────────────────────────────────────────


class TestOSSBucketConnectTimeout:
    """测试 oss_bucket() 传递 connect_timeout 参数."""

    def test_oss_bucket_has_connect_timeout(self):
        """oss_bucket 应传递 connect_timeout=10s 参数."""
        from video_processing.oss_helpers import oss_bucket

        mock_bucket_instance = MagicMock()
        with (
            patch(
                "video_processing.oss_helpers.oss_settings",
                return_value=("test-key", "test-secret", "oss-cn-hangzhou.aliyuncs.com", "test-bucket"),
            ),
            patch("video_processing.oss_helpers.oss2.Auth"),
            patch("video_processing.oss_helpers.oss2.Bucket", return_value=mock_bucket_instance) as mock_bucket_cls,
        ):
            bucket = oss_bucket()

            assert bucket is mock_bucket_instance
            # 验证 connect_timeout 关键字参数
            call_kwargs = mock_bucket_cls.call_args[1]
            assert "connect_timeout" in call_kwargs, "oss_bucket 应传递 connect_timeout 参数"
            assert (
                call_kwargs["connect_timeout"] == 10
            ), f"connect_timeout 应为 10，实际为 {call_kwargs['connect_timeout']}"

    def test_oss_bucket_no_config_returns_none(self):
        """OSS 配置缺失时返回 None."""
        from video_processing.oss_helpers import oss_bucket

        with patch("video_processing.oss_helpers.oss_settings", return_value=None):
            bucket = oss_bucket()
            assert bucket is None


# ── upload_to_oss 分片上传测试 ────────────────────────────────────────────────


class TestUploadToOSSMultipart:
    """测试 upload_to_oss() 根据文件大小选择上传方式."""

    def _create_temp_file(self, size_bytes: int) -> Path:
        """创建指定大小的临时文件."""
        tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
        tmp.write(b"x" * size_bytes)
        tmp.close()
        return Path(tmp.name)

    def test_small_file_uses_put_object(self):
        """小文件（<100MB）走 put_object_from_file."""
        from video_processing.oss_helpers import upload_to_oss

        small_file = self._create_temp_file(10 * 1024 * 1024)  # 10MB
        try:
            mock_bucket = MagicMock()

            with (
                patch(
                    "video_processing.oss_helpers.oss_settings",
                    return_value=("test-key", "test-secret", "oss-cn-hangzhou.aliyuncs.com", "test-bucket"),
                ),
                patch("video_processing.oss_helpers.oss2.Auth"),
                patch("video_processing.oss_helpers.oss2.Bucket", return_value=mock_bucket),
                patch("video_processing.oss_helpers.oss2.resumable_upload") as mock_resumable,
            ):
                url = upload_to_oss(small_file, "test/small.mp4")

                # 验证调用了 put_object_from_file
                mock_bucket.put_object_from_file.assert_called_once()
                # 验证没调用分片上传
                mock_resumable.assert_not_called()
                # 验证返回 URL
                assert url == "https://test-bucket.oss-cn-hangzhou.aliyuncs.com/test/small.mp4"
        finally:
            small_file.unlink()

    def test_large_file_uses_resumable_upload(self):
        """大文件（>=100MB）走 resumable_upload 分片上传."""
        from video_processing.oss_helpers import upload_to_oss

        large_file = self._create_temp_file(100 * 1024 * 1024)  # 100MB
        try:
            mock_bucket = MagicMock()

            with (
                patch(
                    "video_processing.oss_helpers.oss_settings",
                    return_value=("test-key", "test-secret", "oss-cn-hangzhou.aliyuncs.com", "test-bucket"),
                ),
                patch("video_processing.oss_helpers.oss2.Auth"),
                patch("video_processing.oss_helpers.oss2.Bucket", return_value=mock_bucket),
                patch("video_processing.oss_helpers.oss2.resumable_upload") as mock_resumable,
            ):
                url = upload_to_oss(large_file, "test/large.mp4")

                # 验证调用了分片上传
                mock_resumable.assert_called_once()
                # 验证没调用 put_object_from_file
                mock_bucket.put_object_from_file.assert_not_called()
                # 验证分片参数
                call_kwargs = mock_resumable.call_args[1]
                assert call_kwargs["multipart_threshold"] == 100 * 1024 * 1024
                assert call_kwargs["part_size"] == 8 * 1024 * 1024
                assert call_kwargs["num_threads"] == 3
                # 验证返回 URL
                assert url == "https://test-bucket.oss-cn-hangzhou.aliyuncs.com/test/large.mp4"
        finally:
            large_file.unlink()


# ── upload_to_oss 超时测试 ────────────────────────────────────────────────────


class TestUploadToOSSTimeout:
    """测试 upload_to_oss() 超时保护."""

    def test_upload_timeout_returns_none(self):
        """上传超过总超时时返回 None."""
        from video_processing.oss_helpers import upload_to_oss

        small_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
        small_file.write(b"x" * 1024)  # 1KB
        small_file.close()
        file_path = Path(small_file.name)

        def slow_upload(*args, **kwargs):
            """模拟慢速上传，超过超时时间."""
            time.sleep(2)

        mock_bucket = MagicMock()
        mock_bucket.put_object_from_file.side_effect = slow_upload

        try:
            with (
                patch(
                    "video_processing.oss_helpers.oss_settings",
                    return_value=("test-key", "test-secret", "oss-cn-hangzhou.aliyuncs.com", "test-bucket"),
                ),
                patch("video_processing.oss_helpers.oss2.Auth"),
                patch("video_processing.oss_helpers.oss2.Bucket", return_value=mock_bucket),
                patch("video_processing.oss_helpers.OSS_UPLOAD_TOTAL_TIMEOUT", 1),  # 1秒超时
            ):
                url = upload_to_oss(file_path, "test/slow.mp4")
                # 超时应返回 None
                assert url is None, "上传超时应返回 None"
        finally:
            file_path.unlink()

    def test_upload_exception_returns_none(self):
        """上传异常时返回 None."""
        from video_processing.oss_helpers import upload_to_oss

        small_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
        small_file.write(b"x" * 1024)
        small_file.close()
        file_path = Path(small_file.name)

        mock_bucket = MagicMock()
        mock_bucket.put_object_from_file.side_effect = RuntimeError("Network error")

        try:
            with (
                patch(
                    "video_processing.oss_helpers.oss_settings",
                    return_value=("test-key", "test-secret", "oss-cn-hangzhou.aliyuncs.com", "test-bucket"),
                ),
                patch("video_processing.oss_helpers.oss2.Auth"),
                patch("video_processing.oss_helpers.oss2.Bucket", return_value=mock_bucket),
            ):
                url = upload_to_oss(file_path, "test/error.mp4")
                assert url is None, "上传异常应返回 None"
        finally:
            file_path.unlink()

    def test_upload_no_bucket_returns_none(self):
        """OSS 未配置时返回 None."""
        from video_processing.oss_helpers import upload_to_oss

        small_file = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
        small_file.write(b"x" * 1024)
        small_file.close()
        file_path = Path(small_file.name)

        try:
            with patch("video_processing.oss_helpers.oss_settings", return_value=None):
                url = upload_to_oss(file_path, "test/noconfig.mp4")
                assert url is None
        finally:
            file_path.unlink()
