from typing import Generator
from unittest.mock import MagicMock, patch

import pytest

from app.modules.filemanager.storages import alist as alist_module
from app.modules.filemanager.storages.alist import Alist
from app.modules.filemanager.storages.alistgo import AlistGo
from app.schemas.types import StorageSchema


class _FakeResponse:
    """模拟 AList 登录接口响应"""

    def __init__(self, payload: dict, status_code: int = 200) -> None:
        """保存模拟响应数据和状态码"""
        self._payload = payload
        self.status_code = status_code

    def json(self) -> dict:
        """返回模拟 JSON 响应数据"""
        return self._payload


@pytest.fixture
def clear_token_cache() -> Generator[None, None, None]:
    """在测试前后清理 AList 登录令牌缓存"""
    Alist._Alist__login_token.cache_clear()  # noqa
    yield
    Alist._Alist__login_token.cache_clear()  # noqa


def test_alistgo_schema_registered() -> None:
    """AListGo 存储类型应注册到存储枚举"""
    assert AlistGo.schema == StorageSchema.AlistGo
    assert StorageSchema.AlistGo.value == "alistgo"


def test_alistgo_singleton_isolated_from_alist() -> None:
    """AListGo 与 OpenList 应使用彼此独立的存储实例"""
    alist = Alist()
    alistgo = AlistGo()
    assert alistgo is not alist
    assert isinstance(alistgo, Alist)


def test_alistgo_token_isolated_from_alist(clear_token_cache) -> None:
    """AListGo 与 OpenList 应按各自凭据缓存登录令牌"""
    def _conf(storage):
        return {
            "url": f"http://{storage.schema.value}.test",
            "username": "user",
            "password": "pass",
        }

    responses = [
        _FakeResponse({"code": 200, "message": "success", "data": {"token": "token-alist"}}),
        _FakeResponse({"code": 200, "message": "success", "data": {"token": "token-alistgo"}}),
    ]
    request_utils = MagicMock()
    request_utils.post_res.side_effect = responses

    alist = Alist()
    alistgo = AlistGo()
    with patch.object(Alist, "get_conf", _conf):
        with patch.object(alist_module, "RequestUtils", return_value=request_utils):
            assert alist._Alist__generate_token() == "token-alist"  # noqa
            assert alistgo._Alist__generate_token() == "token-alistgo"  # noqa
            assert alist._Alist__generate_token() == "token-alist"  # noqa
            assert alistgo._Alist__generate_token() == "token-alistgo"  # noqa

    assert request_utils.post_res.call_count == 2


def test_init_storage_keeps_other_storage_token(clear_token_cache) -> None:
    """重新初始化单个存储时不应清除另一存储的登录令牌"""
    def _conf(storage):
        return {
            "url": f"http://{storage.schema.value}.test",
            "username": "user",
            "password": "pass",
        }

    responses = [
        _FakeResponse({"code": 200, "message": "success", "data": {"token": "token-alist"}}),
        _FakeResponse({"code": 200, "message": "success", "data": {"token": "token-alistgo"}}),
        _FakeResponse({"code": 200, "message": "success", "data": {"token": "token-alistgo-new"}}),
    ]
    request_utils = MagicMock()
    request_utils.post_res.side_effect = responses

    alist = Alist()
    alistgo = AlistGo()
    with patch.object(Alist, "get_conf", _conf):
        with patch.object(alist_module, "RequestUtils", return_value=request_utils):
            assert alist._Alist__generate_token() == "token-alist"  # noqa
            assert alistgo._Alist__generate_token() == "token-alistgo"  # noqa

            alistgo.init_storage()

            assert alist._Alist__generate_token() == "token-alist"  # noqa
            assert alistgo._Alist__generate_token() == "token-alistgo-new"  # noqa

    assert request_utils.post_res.call_count == 3
