"""
Unit tests for daily rewards system (ws/retention/daily_rewards.py).

Tests the 7-day login streak system that awards diamonds, energy, and items.
Streak resets if player misses a day.

Run with: pytest tests/unit/test_daily_rewards.py -v
"""

import pytest
from unittest.mock import Mock, patch, MagicMock
from datetime import date, datetime, timedelta
import sys
from pathlib import Path

# Add ws directory to path
ws_dir = Path(__file__).parent.parent.parent / 'ws'
sys.path.insert(0, str(ws_dir))

from retention.daily_rewards import (
    DAILY_REWARDS,
    check_daily_login,
    claim_daily_reward,
    get_daily_reward,
    get_login_streak_info,
)


# ============================================================================
# FIXTURES
# ============================================================================

@pytest.fixture
def adult_player():
    """Create an adult test player for reward tests"""
    player = Mock()
    player.userID = "test_user_rewards"
    player.id = 12345
    player.c = Mock()
    player.c.diamonds = 50
    player.c.energy = 100
    return player


@pytest.fixture
def mock_db_connection():
    """Mock database connection and cursor"""
    mock_conn = MagicMock()
    mock_cursor = MagicMock()
    mock_conn.cursor.return_value = mock_cursor

    with patch('retention.daily_rewards.get_database_connection', return_value=mock_conn):
        yield mock_conn, mock_cursor


# ============================================================================
# DAILY REWARD TESTS (8 tests)
# ============================================================================

def test_claim_daily_reward_once_per_day(adult_player, mock_db_connection):
    """Test that daily reward can only be claimed once per day"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection
    today = date.today()

    # First check: reward available
    mock_cursor.fetchone.side_effect = [
        # check_daily_login call
        {
            'player_id': adult_player.id,
            'current_streak': 1,
            'last_login_date': today,
            'last_reward_claim': today - timedelta(days=1),
            'next_reward_day': 1
        },
        # claim_daily_reward call - already claimed today
        {
            'last_reward_claim': today
        }
    ]

    # Act
    result = claim_daily_reward(adult_player.id)

    # Assert
    assert result['success'] is False
    assert 'already claimed' in result.get('error', '').lower() or result['success'] is False


def test_daily_reward_streak_tracked(adult_player, mock_db_connection):
    """Test that login streak is properly tracked"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection
    today = date.today()
    yesterday = today - timedelta(days=1)

    mock_cursor.fetchone.return_value = {
        'player_id': adult_player.id,
        'current_streak': 3,
        'last_login_date': yesterday,
        'total_logins': 10,
        'next_reward_day': 4
    }

    # Act
    result = check_daily_login(adult_player.id)

    # Assert
    assert 'streak' in result
    assert result['streak'] >= 1
    # Streak should increment if logged in consecutive days
    mock_conn.commit.assert_called()


def test_daily_reward_streak_broken_if_miss_day(adult_player, mock_db_connection):
    """Test that streak resets if player misses a day"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection
    today = date.today()
    two_days_ago = today - timedelta(days=2)

    mock_cursor.fetchone.return_value = {
        'player_id': adult_player.id,
        'current_streak': 5,
        'last_login_date': two_days_ago,
        'total_logins': 20,
        'next_reward_day': 6
    }

    # Act
    result = check_daily_login(adult_player.id)

    # Assert
    assert 'streak_broken' in result
    if result.get('streak_broken'):
        # Streak should reset to 1
        assert result.get('streak', 0) <= 1


def test_daily_reward_increases_with_streak(adult_player, mock_db_connection):
    """Test that longer streak results in better rewards"""
    # Arrange
    day_1_reward = get_daily_reward(1)
    day_7_reward = get_daily_reward(7)

    # Assert
    assert day_1_reward is not None
    assert day_7_reward is not None
    # Day 7 should have better rewards than day 1
    assert day_7_reward['amount'] > day_1_reward['amount']


def test_daily_reward_calendar(adult_player):
    """Test that 7-day reward calendar is properly defined"""
    # Arrange & Assert
    assert len(DAILY_REWARDS) == 7

    # Verify each day has required fields
    for reward in DAILY_REWARDS:
        assert 'day' in reward
        assert 'type' in reward
        assert 'amount' in reward
        assert 'name' in reward
        assert 1 <= reward['day'] <= 7
        assert reward['amount'] > 0


def test_claim_reward_requires_login(adult_player, mock_db_connection):
    """Test that player must be logged in to claim reward"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection

    # Player has no streak record (never logged in)
    mock_cursor.fetchone.return_value = None

    # Act
    result = claim_daily_reward(adult_player.id)

    # Assert
    assert result['success'] is False or result.get('error') is not None


def test_reward_already_claimed(adult_player, mock_db_connection):
    """Test that cannot claim reward twice in same day"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection
    today = date.today()

    mock_cursor.fetchone.return_value = {
        'player_id': adult_player.id,
        'current_streak': 3,
        'last_login_date': today,
        'last_reward_claim': today,  # Already claimed today
        'next_reward_day': 4
    }

    # Act
    result = claim_daily_reward(adult_player.id)

    # Assert
    assert result['success'] is False
    assert 'already' in result.get('error', '').lower() or result.get('reward_available') is False


def test_streak_max_bonus(adult_player):
    """Test that streak has maximum bonus level (day 7, then cycles)"""
    # Arrange & Act
    day_7_reward = get_daily_reward(7)
    day_8_reward = get_daily_reward(8)  # Should wrap to day 1
    day_14_reward = get_daily_reward(14)  # Should wrap to day 7

    # Assert
    assert day_7_reward is not None
    assert day_8_reward is not None

    # Day 8 should wrap to day 1
    day_1_reward = get_daily_reward(1)
    assert day_8_reward['amount'] == day_1_reward['amount']

    # Day 14 should wrap to day 7
    if day_14_reward:
        assert day_14_reward['amount'] == day_7_reward['amount']


# ============================================================================
# STREAK CALCULATION TESTS
# ============================================================================

def test_streak_continues_on_consecutive_login(adult_player, mock_db_connection):
    """Test that streak continues when player logs in consecutive days"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection
    today = date.today()
    yesterday = today - timedelta(days=1)

    mock_cursor.fetchone.return_value = {
        'player_id': adult_player.id,
        'current_streak': 2,
        'last_login_date': yesterday,
        'total_logins': 5,
        'next_reward_day': 3
    }

    # Act
    result = check_daily_login(adult_player.id)

    # Assert
    # Streak should increment
    assert result.get('streak', 0) >= 2


def test_first_time_login_initializes_streak(adult_player, mock_db_connection):
    """Test that first login creates streak record"""
    # Arrange
    mock_conn, mock_cursor = mock_db_connection

    # No existing streak record
    mock_cursor.fetchone.return_value = None

    # Act
    result = check_daily_login(adult_player.id)

    # Assert
    assert result['streak'] == 1
    assert result['reward_available'] is True
    # Verify INSERT was called
    insert_calls = [call for call in mock_cursor.execute.call_args_list if 'INSERT' in str(call)]
    assert len(insert_calls) > 0


# ============================================================================
# REWARD TYPE TESTS
# ============================================================================

def test_reward_types_include_diamonds_energy_items():
    """Test that rewards include various types (diamonds, energy, items)"""
    # Arrange & Assert
    reward_types = set()
    for reward in DAILY_REWARDS:
        reward_types.add(reward['type'])

    # Should have at least diamonds and energy types
    assert 'diamonds' in reward_types
    # Most rewards are diamonds, but some may be energy
    diamond_rewards = [r for r in DAILY_REWARDS if r['type'] == 'diamonds']
    assert len(diamond_rewards) >= 4  # Most days give diamonds


def test_get_daily_reward_returns_correct_day():
    """Test that get_daily_reward returns correct reward for day number"""
    # Act
    reward_day_1 = get_daily_reward(1)
    reward_day_7 = get_daily_reward(7)

    # Assert
    assert reward_day_1['day'] == 1
    assert reward_day_7['day'] == 7
    assert reward_day_1['amount'] < reward_day_7['amount']  # Day 7 bonus
