"""Tests for app.core.token_store — JWT token blacklist + refresh token management"""

import time
import pytest
from unittest.mock import AsyncMock, patch, MagicMock


@pytest.fixture
def token_store():
    from app.core.token_store import TokenStore
    return TokenStore()


@pytest.mark.asyncio
async def test_blacklist_no_redis_fallback(token_store):
    """No Redis available, falls back to memory"""
    with patch.object(token_store, "_get_redis", return_value=None):
        await token_store.blacklist("test-jti", ttl=900)
        assert "test-jti" in token_store._fallback_blacklist


@pytest.mark.asyncio
async def test_is_blacklisted_no_redis_true(token_store):
    """Memory fallback returns True for blacklisted token"""
    token_store._fallback_blacklist["test-jti"] = time.time() + 900
    with patch.object(token_store, "_get_redis", return_value=None):
        assert await token_store.is_blacklisted("test-jti") is True


@pytest.mark.asyncio
async def test_is_blacklisted_no_redis_false(token_store):
    """Memory fallback returns False for non-blacklisted token"""
    with patch.object(token_store, "_get_redis", return_value=None):
        assert await token_store.is_blacklisted("no-such-jti") is False


@pytest.mark.asyncio
async def test_is_blacklisted_expired(token_store):
    """Expired blacklist entry is cleaned up and returns False"""
    token_store._fallback_blacklist["expired-jti"] = time.time() - 1
    with patch.object(token_store, "_get_redis", return_value=None):
        assert await token_store.is_blacklisted("expired-jti") is False
        assert "expired-jti" not in token_store._fallback_blacklist


@pytest.mark.asyncio
async def test_store_refresh_no_redis(token_store):
    """Store refresh token in memory fallback"""
    with patch.object(token_store, "_get_redis", return_value=None):
        await token_store.store_refresh("rt-jti", "user-1", "tenant-1", ttl=3600)
        assert "rt-jti" in token_store._fallback_refresh
        assert token_store._fallback_refresh["rt-jti"]["user_id"] == "user-1"


@pytest.mark.asyncio
async def test_validate_refresh_no_redis_found(token_store):
    """Valid refresh token returns user data"""
    token_store._fallback_refresh["rt-jti"] = {
        "user_id": "user-1", "tenant_id": "tenant-1",
        "expires": time.time() + 3600
    }
    with patch.object(token_store, "_get_redis", return_value=None):
        result = await token_store.validate_refresh("rt-jti")
    assert result is not None
    assert result["user_id"] == "user-1"
    assert result["tenant_id"] == "tenant-1"


@pytest.mark.asyncio
async def test_validate_refresh_no_redis_expired(token_store):
    """Expired refresh token returns None"""
    token_store._fallback_refresh["rt-jti"] = {
        "user_id": "user-1", "tenant_id": "tenant-1",
        "expires": time.time() - 1
    }
    with patch.object(token_store, "_get_redis", return_value=None):
        result = await token_store.validate_refresh("rt-jti")
    assert result is None


@pytest.mark.asyncio
async def test_validate_refresh_no_redis_not_found(token_store):
    """Non-existent refresh token returns None"""
    with patch.object(token_store, "_get_redis", return_value=None):
        result = await token_store.validate_refresh("no-such-jti")
    assert result is None


@pytest.mark.asyncio
async def test_revoke_refresh_no_redis(token_store):
    """Revoke removes from memory fallback"""
    token_store._fallback_refresh["rt-jti"] = {"user_id": "u1", "tenant_id": "t1", "expires": time.time() + 3600}
    with patch.object(token_store, "_get_redis", return_value=None):
        await token_store.revoke_refresh("rt-jti")
    assert "rt-jti" not in token_store._fallback_refresh


@pytest.mark.asyncio
async def test_store_refresh_with_redis(token_store):
    """With Redis, stores refresh token with correct key prefix"""
    mock_redis = AsyncMock()
    with patch.object(token_store, "_get_redis", return_value=mock_redis):
        await token_store.store_refresh("rt-jti", "user-1", "tenant-1", ttl=3600)
    mock_redis.setex.assert_called_once_with("rt:rt-jti", 3600, "user-1|tenant-1")


@pytest.mark.asyncio
async def test_blacklist_with_redis(token_store):
    """With Redis, blacklists with correct key prefix"""
    mock_redis = AsyncMock()
    with patch.object(token_store, "_get_redis", return_value=mock_redis):
        await token_store.blacklist("test-jti", ttl=900)
    mock_redis.setex.assert_called_once_with("bl:test-jti", 900, "1")


@pytest.mark.asyncio
async def test_is_blacklisted_with_redis_token(token_store):
    """With Redis, checks blacklist via exists"""
    mock_redis = AsyncMock()
    mock_redis.exists.return_value = 1
    with patch.object(token_store, "_get_redis", return_value=mock_redis):
        result = await token_store.is_blacklisted("test-jti")
    assert result is True
    mock_redis.exists.assert_called_once_with("bl:test-jti")


@pytest.mark.asyncio
async def test_is_blacklisted_with_redis_not_found(token_store):
    """With Redis, returns False when token not blacklisted"""
    mock_redis = AsyncMock()
    mock_redis.exists.return_value = 0
    with patch.object(token_store, "_get_redis", return_value=mock_redis):
        result = await token_store.is_blacklisted("test-jti")
    assert result is False


@pytest.mark.asyncio
async def test_validate_refresh_with_redis(token_store):
    """With Redis, gets and parses refresh token"""
    mock_redis = AsyncMock()
    mock_redis.get.return_value = "user-1|tenant-1"
    with patch.object(token_store, "_get_redis", return_value=mock_redis):
        result = await token_store.validate_refresh("rt-jti")
    assert result["user_id"] == "user-1"
    assert result["tenant_id"] == "tenant-1"
    mock_redis.get.assert_called_once_with("rt:rt-jti")


@pytest.mark.asyncio
async def test_revoke_refresh_with_redis(token_store):
    """With Redis, deletes the refresh token key"""
    mock_redis = AsyncMock()
    with patch.object(token_store, "_get_redis", return_value=mock_redis):
        await token_store.revoke_refresh("rt-jti")
    mock_redis.delete.assert_called_once_with("rt:rt-jti")


@pytest.mark.asyncio
async def test_get_redis_connection_failure():
    """_get_redis returns None on connection failure"""
    from app.core.token_store import TokenStore
    ts = TokenStore()
    with patch("redis.asyncio.Redis.from_url", side_effect=Exception("Connection refused")):
        result = await ts._get_redis()
    assert result is None


@pytest.mark.asyncio
async def test_get_redis_connection_success():
    """_get_redis returns Redis instance on success"""
    from app.core.token_store import TokenStore
    ts = TokenStore()
    mock_redis = AsyncMock()
    mock_redis.ping.return_value = True
    with patch("redis.asyncio.Redis.from_url", return_value=mock_redis):
        result = await ts._get_redis()
    assert result is not None
    assert result is mock_redis
