/**
 * Daily Rewards Service Tests
 * Tests the daily rewards business logic with mock implementations
 */
import { describe, it, expect, beforeEach } from 'vitest';

// Mock daily rewards data
const DAILY_REWARDS = [
  { day: 1, diamonds: 5, money: 100, energy: 0 },
  { day: 2, diamonds: 0, money: 200, energy: 10 },
  { day: 3, diamonds: 10, money: 150, energy: 0 },
  { day: 4, diamonds: 0, money: 300, energy: 20 },
  { day: 5, diamonds: 15, money: 250, energy: 0 },
  { day: 6, diamonds: 0, money: 400, energy: 30 },
  { day: 7, diamonds: 50, money: 500, energy: 50 },
];

// Mock player reward state storage
const playerRewardStates = new Map<string, { streak: number; lastClaimed: string | null }>();

// Mock service functions
function getDailyRewards() {
  return DAILY_REWARDS;
}

function getPlayerDailyRewardState(playerId: string) {
  if (!playerRewardStates.has(playerId)) {
    playerRewardStates.set(playerId, { streak: 0, lastClaimed: null });
  }
  return playerRewardStates.get(playerId)!;
}

function claimDailyReward(playerId: string) {
  const state = getPlayerDailyRewardState(playerId);
  const today = new Date().toISOString().split('T')[0];

  // Check if already claimed today
  if (state.lastClaimed === today) {
    return { success: false, message: 'Already claimed today' };
  }

  // Calculate if streak continues or resets
  const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().split('T')[0];
  if (state.lastClaimed && state.lastClaimed !== yesterday) {
    state.streak = 0; // Reset streak if missed a day
  }

  state.streak = (state.streak % 7) + 1;
  state.lastClaimed = today;

  const reward = DAILY_REWARDS[state.streak - 1];
  return { success: true, reward, streak: state.streak };
}

function calculateStreak(lastClaimDate: string, currentDate: string): number {
  const last = new Date(lastClaimDate);
  const current = new Date(currentDate);
  const diffDays = Math.floor((current.getTime() - last.getTime()) / (24 * 60 * 60 * 1000));

  if (diffDays > 1) {
    return 0; // Streak broken
  }
  return 1; // Streak continues
}

function getNextReward(playerId: string) {
  const state = getPlayerDailyRewardState(playerId);
  const nextDay = (state.streak % 7) + 1;
  return DAILY_REWARDS[nextDay - 1];
}

describe('Daily Rewards Service', () => {
  beforeEach(() => {
    playerRewardStates.clear();
  });

  describe('getDailyRewards', () => {
    it('should return list of daily rewards', () => {
      const rewards = getDailyRewards();

      expect(Array.isArray(rewards)).toBe(true);
      expect(rewards.length).toBeGreaterThan(0);
    });

    it('should have 7 days of rewards', () => {
      const rewards = getDailyRewards();

      expect(rewards.length).toBe(7);
    });

    it('should have increasing rewards over the week', () => {
      const rewards = getDailyRewards();

      const day1Total = (rewards[0].diamonds ?? 0) + (rewards[0].money ?? 0) + (rewards[0].energy ?? 0);
      const day7Total = (rewards[6].diamonds ?? 0) + (rewards[6].money ?? 0) + (rewards[6].energy ?? 0);

      expect(day7Total).toBeGreaterThan(day1Total);
    });

    it('should have rewards with day property', () => {
      const rewards = getDailyRewards();

      rewards.forEach((reward, index) => {
        expect(reward).toHaveProperty('day');
        expect(reward.day).toBe(index + 1);
      });
    });
  });

  describe('getPlayerDailyRewardState', () => {
    it('should return player reward state', () => {
      const state = getPlayerDailyRewardState('test-player-1');

      expect(state).toBeDefined();
      expect(state).toHaveProperty('streak');
      expect(state).toHaveProperty('lastClaimed');
    });

    it('should initialize new player with streak 0', () => {
      const state = getPlayerDailyRewardState('new-player-' + Date.now());

      expect(state.streak).toBe(0);
      expect(state.lastClaimed).toBeNull();
    });

    it('should return same state for same player', () => {
      const playerId = 'consistent-player';
      const state1 = getPlayerDailyRewardState(playerId);
      state1.streak = 3;
      const state2 = getPlayerDailyRewardState(playerId);

      expect(state2.streak).toBe(3);
    });
  });

  describe('claimDailyReward', () => {
    it('should claim daily reward for new player', () => {
      const playerId = 'claim-test-' + Date.now();
      const result = claimDailyReward(playerId);

      expect(result).toBeDefined();
      expect(result.success).toBe(true);
    });

    it('should return reward details', () => {
      const playerId = 'claim-details-' + Date.now();
      const result = claimDailyReward(playerId);

      expect(result.success).toBe(true);
      expect(result.reward).toBeDefined();
      expect(result.reward?.day).toBe(1);
    });

    it('should not allow double claiming same day', () => {
      const playerId = 'double-claim-' + Date.now();

      // First claim
      const firstClaim = claimDailyReward(playerId);
      expect(firstClaim.success).toBe(true);

      // Second claim should fail
      const secondClaim = claimDailyReward(playerId);
      expect(secondClaim.success).toBe(false);
    });

    it('should increment streak on claim', () => {
      const playerId = 'streak-claim-' + Date.now();

      const beforeState = getPlayerDailyRewardState(playerId);
      expect(beforeState.streak).toBe(0);

      claimDailyReward(playerId);
      const afterState = getPlayerDailyRewardState(playerId);

      expect(afterState.streak).toBe(1);
    });

    it('should update lastClaimed date', () => {
      const playerId = 'date-claim-' + Date.now();

      claimDailyReward(playerId);
      const state = getPlayerDailyRewardState(playerId);

      const today = new Date().toISOString().split('T')[0];
      expect(state.lastClaimed).toBe(today);
    });
  });

  describe('calculateStreak', () => {
    it('should return 1 for consecutive days', () => {
      const today = new Date();
      const yesterday = new Date(today.getTime() - 1 * 24 * 60 * 60 * 1000);

      const streak = calculateStreak(yesterday.toISOString(), today.toISOString());

      expect(streak).toBe(1);
    });

    it('should return 0 after missing more than a day', () => {
      const today = new Date();
      const threeDaysAgo = new Date(today.getTime() - 3 * 24 * 60 * 60 * 1000);

      const streak = calculateStreak(threeDaysAgo.toISOString(), today.toISOString());

      expect(streak).toBe(0);
    });

    it('should return 1 for same day', () => {
      const today = new Date();

      const streak = calculateStreak(today.toISOString(), today.toISOString());

      expect(streak).toBe(1);
    });
  });

  describe('getNextReward', () => {
    it('should return day 1 reward for new player', () => {
      const playerId = 'new-player-reward-' + Date.now();
      const nextReward = getNextReward(playerId);

      expect(nextReward).toBeDefined();
      expect(nextReward.day).toBe(1);
    });

    it('should return correct next reward after claiming', () => {
      const playerId = 'next-reward-' + Date.now();

      claimDailyReward(playerId); // Claim day 1

      // Manually set lastClaimed to yesterday to allow next claim
      const state = getPlayerDailyRewardState(playerId);
      state.lastClaimed = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString().split('T')[0];

      const nextReward = getNextReward(playerId);
      expect(nextReward.day).toBe(2);
    });
  });

  describe('Reward types', () => {
    it('should include diamond rewards', () => {
      const rewards = getDailyRewards();
      const withDiamonds = rewards.filter(r => (r.diamonds ?? 0) > 0);

      expect(withDiamonds.length).toBeGreaterThan(0);
    });

    it('should include money rewards', () => {
      const rewards = getDailyRewards();
      const withMoney = rewards.filter(r => (r.money ?? 0) > 0);

      expect(withMoney.length).toBeGreaterThan(0);
    });

    it('should include energy rewards', () => {
      const rewards = getDailyRewards();
      const withEnergy = rewards.filter(r => (r.energy ?? 0) > 0);

      expect(withEnergy.length).toBeGreaterThan(0);
    });

    it('should have day 7 with highest diamond reward', () => {
      const rewards = getDailyRewards();
      const day7 = rewards.find(r => r.day === 7);

      expect(day7?.diamonds).toBe(50);
    });
  });

  describe('Streak mechanics', () => {
    it('should have 7 day cycle', () => {
      const rewards = getDailyRewards();

      expect(rewards.length).toBe(7);
      expect(rewards[0].day).toBe(1);
      expect(rewards[6].day).toBe(7);
    });

    it('should cycle back after day 7', () => {
      const playerId = 'cycle-test';

      // Simulate 7 days of claiming
      for (let i = 0; i < 7; i++) {
        const state = getPlayerDailyRewardState(playerId);
        state.lastClaimed = null; // Reset to allow claiming
        claimDailyReward(playerId);
      }

      const state = getPlayerDailyRewardState(playerId);
      // Streak should be at 7 (or cycle back to 1)
      expect(state.streak).toBeLessThanOrEqual(7);
    });
  });

  describe('Edge cases', () => {
    it('should handle first-time player', () => {
      const playerId = 'first-timer-' + Date.now();

      const state = getPlayerDailyRewardState(playerId);
      expect(state.streak).toBe(0);
      expect(state.lastClaimed).toBeNull();

      const result = claimDailyReward(playerId);
      expect(result.success).toBe(true);
      expect(result.streak).toBe(1);
    });

    it('should handle multiple players independently', () => {
      const player1 = 'player-1-' + Date.now();
      const player2 = 'player-2-' + Date.now();

      claimDailyReward(player1);

      const state1 = getPlayerDailyRewardState(player1);
      const state2 = getPlayerDailyRewardState(player2);

      expect(state1.streak).toBe(1);
      expect(state2.streak).toBe(0);
    });
  });
});
