"""Analysis 存储迁移测试：Redis 缓存 + MySQL/SQLite 降级。"""
import json
from datetime import datetime
from unittest.mock import MagicMock, patch

import pytest

from app.storage_manager import StorageManager


class TestStorageManagerCache:
    """StorageManager 通用缓存方法测试。"""

    def test_cache_get_returns_redis_value_on_hit(self):
        """Redis 命中时返回反序列化数据。"""
        redis_client = MagicMock()
        redis_client.get.return_value = json.dumps({"symbol": "AG2606", "score": 85})
        manager = StorageManager(redis_client=redis_client, mysql_engine=MagicMock())

        result = manager.cache_get("analysis:ai_analysis_cache:1")

        assert result == {"symbol": "AG2606", "score": 85}
        redis_client.get.assert_called_once_with("analysis:ai_analysis_cache:1")

    def test_cache_get_returns_none_when_redis_unavailable(self):
        """Redis 不可用时返回 None。"""
        manager = StorageManager(redis_client=None, mysql_engine=MagicMock())

        result = manager.cache_get("analysis:ai_analysis_cache:1")

        assert result is None

    def test_cache_get_returns_none_on_miss(self):
        """Redis 未命中时返回 None。"""
        redis_client = MagicMock()
        redis_client.get.return_value = None
        manager = StorageManager(redis_client=redis_client, mysql_engine=MagicMock())

        result = manager.cache_get("analysis:ai_analysis_cache:1")

        assert result is None

    def test_cache_set_writes_to_redis_with_ttl(self):
        """cache_set 使用 TTL 写入 Redis。"""
        redis_client = MagicMock()
        manager = StorageManager(redis_client=redis_client, mysql_engine=MagicMock())
        value = {"symbol": "AG2606", "score": 85}

        manager.cache_set("analysis:ai_analysis_cache:1", value, ttl=300)

        redis_client.setex.assert_called_once_with(
            "analysis:ai_analysis_cache:1", 300, json.dumps(value, ensure_ascii=False)
        )

    def test_cache_set_returns_false_when_redis_unavailable(self):
        """Redis 不可用时 cache_set 返回 False。"""
        manager = StorageManager(redis_client=None, mysql_engine=MagicMock())

        result = manager.cache_set("analysis:ai_analysis_cache:1", {"score": 85}, ttl=300)

        assert result is False

    def test_cache_delete_removes_redis_key(self):
        """cache_delete 删除 Redis 键。"""
        redis_client = MagicMock()
        manager = StorageManager(redis_client=redis_client, mysql_engine=MagicMock())

        manager.cache_delete("analysis:ai_analysis_cache:1")

        redis_client.delete.assert_called_once_with("analysis:ai_analysis_cache:1")


class TestAnalysisDbSwitching:
    """analysis_db 动态 Session 切换测试。"""

    def test_get_analysis_db_returns_mysql_session_when_mysql_available(self):
        """MySQL 可用时返回 MySQL Session。"""
        from app.analysis_db import get_analysis_db

        mysql_engine = MagicMock()
        mock_session = MagicMock()
        mock_sessionmaker = MagicMock(return_value=mock_session)
        mock_session.__enter__ = MagicMock(return_value=mock_session)
        mock_session.__exit__ = MagicMock(return_value=False)

        with patch("app.analysis_db.MySQLSessionLocal", mock_sessionmaker):
            with patch("app.analysis_db.get_storage_manager") as mock_sm:
                sm = MagicMock()
                sm.check_mysql.return_value = True
                sm.mysql_engine = mysql_engine
                mock_sm.return_value = sm

                db = next(get_analysis_db())

                assert db is mock_session

    def test_get_analysis_db_returns_sqlite_session_when_mysql_unavailable(self):
        """MySQL 不可用时返回 SQLite Session。"""
        from app.analysis_db import get_analysis_db

        sqlite_engine = MagicMock()
        mock_session = MagicMock()
        mock_sessionmaker = MagicMock(return_value=mock_session)
        mock_session.__enter__ = MagicMock(return_value=mock_session)
        mock_session.__exit__ = MagicMock(return_value=False)

        with patch("app.analysis_db.AnalysisSessionLocal", mock_sessionmaker):
            with patch("app.analysis_db.get_storage_manager") as mock_sm:
                sm = MagicMock()
                sm.check_mysql.return_value = False
                mock_sm.return_value = sm

                db = next(get_analysis_db())

                assert db is mock_session


class TestAnalysisMigration:
    """Analysis 数据迁移测试。"""

    def test_migrate_skips_when_mysql_has_data(self):
        """MySQL 已有数据时跳过迁移。"""
        from app.analysis_migration import migrate_analysis_sqlite_to_mysql

        with patch("app.analysis_migration.mysql_engine", MagicMock()):
            with patch("app.analysis_migration._count_mysql_analysis_records") as mock_count:
                mock_count.return_value = 10

                result = migrate_analysis_sqlite_to_mysql()

                assert result is True
                mock_count.assert_called_once()

    def test_migrate_moves_data_from_sqlite_to_mysql(self):
        """SQLite 数据正确迁移到 MySQL。"""
        from app.analysis_migration import migrate_analysis_sqlite_to_mysql

        with patch("app.analysis_migration.mysql_engine", MagicMock()):
            with patch("app.analysis_migration._count_mysql_analysis_records") as mock_count:
                mock_count.return_value = 0
                with patch("app.analysis_migration._read_sqlite_analysis_data") as mock_read:
                    mock_read.return_value = {
                        "ai_analysis_cache": [{"id": 1, "symbol": "AG2606"}]
                    }
                    with patch("app.analysis_migration._write_mysql_analysis_data") as mock_write:
                        mock_write.return_value = True

                        result = migrate_analysis_sqlite_to_mysql()

                        assert result is True
                        mock_write.assert_called_once_with({"ai_analysis_cache": [{"id": 1, "symbol": "AG2606"}]})

    def test_get_analysis_db_falls_back_to_sqlite_when_mysql_session_fails(self):
        """MySQL Session 创建失败时降级到 SQLite。"""
        from app.analysis_db import get_analysis_db

        mysql_session_maker = MagicMock(side_effect=RuntimeError("MySQL 连接失败"))
        sqlite_session = MagicMock()
        sqlite_session_maker = MagicMock(return_value=sqlite_session)

        with patch("app.analysis_db.MySQLSessionLocal", mysql_session_maker):
            with patch("app.analysis_db.AnalysisSessionLocal", sqlite_session_maker):
                with patch("app.analysis_db.get_storage_manager") as mock_sm:
                    sm = MagicMock()
                    sm.check_mysql.return_value = True
                    mock_sm.return_value = sm

                    db = next(get_analysis_db())

                    assert db is sqlite_session
                    mysql_session_maker.assert_called_once()
