/**
 * Daily Login Reward System
 *
 * Tracks 7-day login streaks and awards diamonds, energy, items, or prestige.
 * Streak resets if player misses a day.
 *
 * Ported from Python: ws/retention/daily_rewards.py
 */

import { query, queryOne, execute, getConnection } from '../../database/index.js';
import { awardDiamonds } from '../../monetization/diamondEconomy.js';
import type { RowDataPacket, PoolConnection } from 'mysql2/promise';

// ============================================================================
// Types
// ============================================================================

export interface DailyReward {
  day: number;
  type: 'diamonds' | 'energy' | 'prestige' | 'item';
  amount: number;
  name: string;
  itemId?: string | null;
}

export interface DayReward {
  id: number;
  diamonds: number;
  energy: number | null;
  money: number | null;
  bonusItem: string | null;
  claimed: boolean;
}

export interface LoginCheckResult {
  rewardAvailable: boolean;
  streak: number;
  reward: DailyReward | null;
  streakBroken: boolean;
}

export interface ClaimResult {
  success: boolean;
  reward: DailyReward | null;
  message: string;
}

export interface LoginStreakInfo {
  currentStreak: number;
  totalLogins: number;
  nextRewardDay: number;
  lastLoginDate: string | null;
}

export interface DailyRewardState {
  currentStreak: number;
  lastLoginDate: string;
  nextResetDate: string;
  canClaim: boolean;
  todaysClaimed: boolean;
  rewards: DayReward[];
}

interface DiamondAwardPlayer {
  userId?: string;
  c?: {
    diamonds?: number;
  };
  character?: {
    diamonds?: number;
  };
}

// Database row types
interface DailyRewardRow extends RowDataPacket {
  day_number: number;
  reward_type: 'diamonds' | 'energy' | 'prestige' | 'item';
  reward_amount: number;
  reward_item_id: number | null;
  display_name: string;
}

interface PlayerStreakRow extends RowDataPacket {
  player_id: string;
  current_streak: number;
  last_login_date: Date | null;
  total_logins: number;
  next_reward_day: number;
  claimed_today: number; // MySQL returns 0/1 for boolean
}

interface ColumnNameRow extends RowDataPacket {
  COLUMN_NAME: string;
}

// ============================================================================
// Default Rewards (fallback if DB not seeded)
// ============================================================================

const DAILY_REWARDS: DailyReward[] = [
  { day: 1, type: 'diamonds', amount: 5, name: '5 Diamonds' },
  { day: 2, type: 'diamonds', amount: 10, name: '10 Diamonds' },
  { day: 3, type: 'energy', amount: 50, name: '50 Energy' },
  { day: 4, type: 'diamonds', amount: 15, name: '15 Diamonds' },
  { day: 5, type: 'diamonds', amount: 20, name: '20 Diamonds + Item' },
  { day: 6, type: 'diamonds', amount: 25, name: '25 Diamonds' },
  { day: 7, type: 'diamonds', amount: 50, name: '50 Diamonds Bonus' },
];

// ============================================================================
// Helper Functions
// ============================================================================

/**
 * Get today's date as YYYY-MM-DD string
 */
function getTodayString(): string {
  return new Date().toISOString().split('T')[0];
}

/**
 * Get today's date at midnight (for comparisons)
 */
function getTodayMidnight(): Date {
  const today = new Date();
  today.setHours(0, 0, 0, 0);
  return today;
}

/**
 * Calculate days between two dates
 */
function daysBetween(date1: Date, date2: Date): number {
  const d1 = new Date(date1);
  const d2 = new Date(date2);
  d1.setHours(0, 0, 0, 0);
  d2.setHours(0, 0, 0, 0);
  return Math.floor((d2.getTime() - d1.getTime()) / (1000 * 60 * 60 * 24));
}

/**
 * Format date for MySQL (YYYY-MM-DD)
 */
function formatDateForDB(date: Date): string {
  return date.toISOString().split('T')[0];
}

// ============================================================================
// Database Operations
// ============================================================================

/**
 * Initialize daily rewards in database.
 * Called during server startup.
 */
export async function initializeDailyRewards(): Promise<void> {
  try {
    await ensureDailyRewardsTables();

    for (const reward of DAILY_REWARDS) {
      await execute(
        `INSERT INTO daily_login_rewards
         (day_number, reward_type, reward_amount, display_name)
         VALUES (?, ?, ?, ?)
         ON DUPLICATE KEY UPDATE
         reward_amount = VALUES(reward_amount),
         display_name = VALUES(display_name)`,
        [reward.day, reward.type, reward.amount, reward.name]
      );
    }
    console.log(`Initialized ${DAILY_REWARDS.length} daily login rewards`);
  } catch (error) {
    console.error('Error initializing daily rewards:', error);
  }
}

/**
 * Get reward for specific day from database
 */
export async function getDailyReward(dayNumber: number): Promise<DailyReward | null> {
  try {
    const row = await queryOne<DailyRewardRow>(
      'SELECT * FROM daily_login_rewards WHERE day_number = ?',
      [dayNumber]
    );

    if (!row) {
      console.warn(`Daily reward not found for day ${dayNumber}, using fallback`);
      return DAILY_REWARDS.find(r => r.day === dayNumber) ?? null;
    }

    return {
      day: row.day_number,
      type: row.reward_type,
      amount: row.reward_amount,
      itemId: row.reward_item_id?.toString() ?? null,
      name: row.display_name,
    };
  } catch (error) {
    console.error(`Error getting daily reward for day ${dayNumber}:`, error);
    // Fallback to in-memory rewards
    return DAILY_REWARDS.find(r => r.day === dayNumber) ?? null;
  }
}

/**
 * Get all daily reward definitions
 */
export async function getAllRewards(): Promise<DailyReward[]> {
  try {
    const rows = await query<DailyRewardRow[]>(
      'SELECT * FROM daily_login_rewards ORDER BY day_number'
    );

    if (rows.length === 0) {
      return [...DAILY_REWARDS];
    }

    return rows.map(row => ({
      day: row.day_number,
      type: row.reward_type,
      amount: row.reward_amount,
      itemId: row.reward_item_id?.toString() ?? null,
      name: row.display_name,
    }));
  } catch (error) {
    console.error('Error getting all rewards:', error);
    return [...DAILY_REWARDS];
  }
}

// ============================================================================
// Core Login Streak Functions
// ============================================================================

/**
 * Check and update daily login streak.
 * Called when player connects to server.
 */
export async function checkDailyLogin(playerId: string): Promise<LoginCheckResult> {
  const today = getTodayMidnight();
  const todayStr = formatDateForDB(today);

  let connection: PoolConnection | null = null;

  try {
    connection = await getConnection();

    // Get player's streak data
    const [rows] = await connection.execute<PlayerStreakRow[]>(
      'SELECT * FROM player_login_streak WHERE player_id = ?',
      [playerId]
    );
    const streakData = rows[0] ?? null;

    // First time login - create streak record
    // next_reward_day=2 because Day 1 is today; next_reward_day always points to tomorrow's day
    if (!streakData) {
      await connection.execute(
        `INSERT INTO player_login_streak
         (player_id, current_streak, last_login_date, total_logins, next_reward_day, claimed_today)
         VALUES (?, 1, ?, 1, 2, 0)`,
        [playerId, todayStr]
      );

      const reward = await getDailyReward(1);
      console.log(`Player ${playerId} first login - starting streak`);

      return {
        rewardAvailable: true,
        streak: 1,
        reward,
        streakBroken: false,
      };
    }

    const lastLogin = streakData.last_login_date
      ? new Date(streakData.last_login_date)
      : null;

    // Check if already logged in today
    if (lastLogin) {
      const lastLoginMidnight = new Date(lastLogin);
      lastLoginMidnight.setHours(0, 0, 0, 0);

      if (lastLoginMidnight.getTime() === today.getTime()) {
        // next_reward_day always points to tomorrow, so today's day = next - 1 (wrapping 1→7)
        const todaysDay = streakData.next_reward_day === 1 ? 7 : streakData.next_reward_day - 1;
        return {
          rewardAvailable: !streakData.claimed_today,
          streak: streakData.current_streak,
          reward: streakData.claimed_today ? null : await getDailyReward(todaysDay),
          streakBroken: false,
        };
      }
    }

    // Check if streak broken (more than 1 day gap)
    const daysSinceLogin = lastLogin ? daysBetween(lastLogin, today) : 999;
    let streakBroken = false;
    let newStreak: number;
    let nextDay: number;
    let todaysRewardDay: number;

    if (daysSinceLogin === 1) {
      // Streak continues — next_reward_day was set yesterday to point to today
      newStreak = streakData.current_streak + 1;
      todaysRewardDay = streakData.next_reward_day;
      nextDay = (streakData.next_reward_day % 7) + 1;
    } else {
      // Streak broken, restart from Day 1
      newStreak = 1;
      todaysRewardDay = 1;
      nextDay = 2; // tomorrow is Day 2
      streakBroken = true;
      console.log(`Player ${playerId} streak broken after ${daysSinceLogin} days`);
    }

    const reward = await getDailyReward(todaysRewardDay);

    // Update streak
    await connection.execute(
      `UPDATE player_login_streak
       SET current_streak = ?, last_login_date = ?,
           total_logins = total_logins + 1, next_reward_day = ?, claimed_today = 0
       WHERE player_id = ?`,
      [newStreak, todayStr, nextDay, playerId]
    );

    console.log(`Player ${playerId} daily login - streak: ${newStreak}, day: ${streakData.next_reward_day}`);

    return {
      rewardAvailable: true,
      streak: newStreak,
      reward,
      streakBroken,
    };
  } catch (error) {
    console.error(`Error checking daily login for player ${playerId}:`, error);
    return {
      rewardAvailable: false,
      streak: 0,
      reward: null,
      streakBroken: false,
    };
  } finally {
    if (connection) {
      connection.release();
    }
  }
}

/**
 * Claim today's reward.
 * Awards the reward to player and marks as claimed.
 */
export async function claimDailyReward(
  playerId: string,
  player?: DiamondAwardPlayer
): Promise<ClaimResult> {
  let connection: PoolConnection | null = null;
  const todayStr = getTodayString();

  try {
    connection = await getConnection();

    // Get streak data
    const [rows] = await connection.execute<PlayerStreakRow[]>(
      'SELECT * FROM player_login_streak WHERE player_id = ?',
      [playerId]
    );
    const streakData = rows[0] ?? null;

    if (!streakData) {
      return {
        success: false,
        reward: null,
        message: 'No login streak found',
      };
    }

    // Check if already claimed today
    if (streakData.claimed_today) {
      return {
        success: false,
        reward: null,
        message: 'Already claimed today',
      };
    }

    // Check if logged in today
    const lastLogin = streakData.last_login_date
      ? formatDateForDB(new Date(streakData.last_login_date))
      : null;

    if (lastLogin !== todayStr) {
      return {
        success: false,
        reward: null,
        message: 'Must log in today to claim reward',
      };
    }

    // Reward day follows streak progression directly:
    // streak 1 -> day 1, streak 7 -> day 7, streak 8 -> day 1
    const rewardDay = ((Math.max(1, streakData.current_streak) - 1) % 7) + 1;

    // Get the reward
    const reward = await getDailyReward(rewardDay);

    if (!reward) {
      return {
        success: false,
        reward: null,
        message: 'Reward not found',
      };
    }

    // Award based on type
    if (reward.type === 'diamonds') {
      awardDiamonds(playerId, `daily_reward_day${rewardDay}`, reward.amount, player);
    } else if (reward.type === 'energy') {
      // Update energy in player data (handled by caller via session.player)
      // The caller should update player.c.energy after successful claim
      console.log(`Player ${playerId} should receive ${reward.amount} energy`);
    } else if (reward.type === 'prestige') {
      // Prestige updates would be handled similarly
      console.log(`Player ${playerId} should receive ${reward.amount} prestige`);
    } else if (reward.type === 'item') {
      // Item rewards would be handled via inventory system
      console.log(`Player ${playerId} should receive item reward`);
    }

    // Mark as claimed
    await connection.execute(
      'UPDATE player_login_streak SET claimed_today = 1 WHERE player_id = ?',
      [playerId]
    );

    console.log(`Player ${playerId} claimed daily reward: Day ${rewardDay}`);

    return {
      success: true,
      reward,
      message: 'Reward claimed successfully',
    };
  } catch (error) {
    console.error(`Error claiming daily reward for player ${playerId}:`, error);
    return {
      success: false,
      reward: null,
      message: 'Server error',
    };
  } finally {
    if (connection) {
      connection.release();
    }
  }
}

/**
 * Get complete daily reward state for iOS DailyRewardState model.
 */
export async function getDailyRewardState(playerId: string): Promise<DailyRewardState> {
  const today = getTodayMidnight();
  const todayStr = formatDateForDB(today);

  try {
    // Get player's streak info
    const streakInfo = await queryOne<PlayerStreakRow>(
      `SELECT current_streak, last_login_date, next_reward_day, claimed_today
       FROM player_login_streak
       WHERE player_id = ?`,
      [playerId]
    );

    let currentStreak: number;
    let lastLoginDate: Date | null;
    let nextRewardDay: number;
    let claimedToday: boolean;

    if (!streakInfo) {
      // First time login - initialize
      currentStreak = 1;
      lastLoginDate = null;
      nextRewardDay = 1;
      claimedToday = false;
    } else {
      currentStreak = streakInfo.current_streak;
      lastLoginDate = streakInfo.last_login_date;
      nextRewardDay = streakInfo.next_reward_day;
      claimedToday = !!streakInfo.claimed_today;
    }

    // Check if already logged in/claimed today
    let todaysClaimed = false;
    if (lastLoginDate) {
      const lastLoginStr = formatDateForDB(new Date(lastLoginDate));
      todaysClaimed = lastLoginStr === todayStr && claimedToday;
    }

    const canClaim = lastLoginDate
      ? formatDateForDB(new Date(lastLoginDate)) === todayStr && !claimedToday
      : false;

    // Get all 7 rewards
    const rewardDefinitions = await getAllRewards();

    // Format rewards array matching iOS DayReward model
    // next_reward_day points to tomorrow, so today = next - 1 (wrapping 1→7)
    const todaysDayNum = nextRewardDay === 1 ? 7 : nextRewardDay - 1;
    const rewards: DayReward[] = rewardDefinitions.map(rewardDef => {
      const dayNum = rewardDef.day;

      // Days before today in the cycle are claimed; today is claimed only if todaysClaimed
      const claimed = dayNum < todaysDayNum || (dayNum === todaysDayNum && todaysClaimed);

      return {
        id: dayNum,
        diamonds: rewardDef.type === 'diamonds' ? rewardDef.amount : 0,
        energy: rewardDef.type === 'energy' ? rewardDef.amount : null,
        money: null,
        bonusItem: dayNum === 5 ? 'Luxury Item' : null,
        claimed,
      };
    });

    // Calculate next reset (tomorrow midnight)
    const tomorrow = new Date(today);
    tomorrow.setDate(tomorrow.getDate() + 1);

    const lastLoginStr = lastLoginDate
      ? formatDateForDB(new Date(lastLoginDate))
      : todayStr;

    return {
      currentStreak,
      lastLoginDate: lastLoginStr,
      nextResetDate: tomorrow.toISOString(),
      canClaim,
      todaysClaimed,
      rewards,
    };
  } catch (error) {
    console.error(`Error getting daily reward state for player ${playerId}:`, error);

    // Return default state
    const tomorrow = new Date();
    tomorrow.setDate(tomorrow.getDate() + 1);

    return {
      currentStreak: 0,
      lastLoginDate: todayStr,
      nextResetDate: tomorrow.toISOString(),
      canClaim: true,
      todaysClaimed: false,
      rewards: [],
    };
  }
}

/**
 * Get player's login streak information
 */
export async function getLoginStreakInfo(playerId: string): Promise<LoginStreakInfo> {
  try {
    const streakData = await queryOne<PlayerStreakRow>(
      'SELECT * FROM player_login_streak WHERE player_id = ?',
      [playerId]
    );

    if (!streakData) {
      return {
        currentStreak: 0,
        totalLogins: 0,
        nextRewardDay: 1,
        lastLoginDate: null,
      };
    }

    return {
      currentStreak: streakData.current_streak,
      totalLogins: streakData.total_logins,
      nextRewardDay: streakData.next_reward_day,
      lastLoginDate: streakData.last_login_date
        ? formatDateForDB(new Date(streakData.last_login_date))
        : null,
    };
  } catch (error) {
    console.error(`Error getting streak info for player ${playerId}:`, error);
    return {
      currentStreak: 0,
      totalLogins: 0,
      nextRewardDay: 1,
      lastLoginDate: null,
    };
  }
}

// ============================================================================
// WebSocket Handlers
// ============================================================================

/**
 * Handle daily login check (WebSocket handler).
 * Called when player connects to server.
 */
export async function handleDailyLoginCheck(
  playerId: string,
  sendToClient: (playerId: string, message: Record<string, unknown>) => void
): Promise<void> {
  try {
    // Check and update streak
    await checkDailyLogin(playerId);

    // Get complete state for client
    const state = await getDailyRewardState(playerId);

    // Send complete state matching iOS DailyRewardState model
    sendToClient(playerId, {
      type: 'dailyRewardStatus',
      ...state,
    });

    console.log(`Sent daily reward state to player ${playerId}`);
  } catch (error) {
    console.error(`Error handling daily login check for player ${playerId}:`, error);
  }
}

// ============================================================================
// Admin/Testing Functions
// ============================================================================

/**
 * Clear player streak (for testing or new game)
 */
export async function clearPlayerStreak(playerId: string): Promise<void> {
  try {
    await execute(
      'DELETE FROM player_login_streak WHERE player_id = ?',
      [playerId]
    );
    console.log(`Cleared streak for player ${playerId}`);
  } catch (error) {
    console.error(`Error clearing streak for player ${playerId}:`, error);
  }
}

/**
 * Clear all streaks (for testing)
 */
export async function clearAllStreaks(): Promise<void> {
  try {
    await execute('TRUNCATE TABLE player_login_streak');
    console.log('Cleared all player streaks');
  } catch (error) {
    console.error('Error clearing all streaks:', error);
  }
}

/**
 * Reset player's claimed_today flag (for testing)
 */
export async function resetTodayClaim(playerId: string): Promise<void> {
  try {
    await execute(
      'UPDATE player_login_streak SET claimed_today = 0 WHERE player_id = ?',
      [playerId]
    );
    console.log(`Reset today's claim for player ${playerId}`);
  } catch (error) {
    console.error(`Error resetting today's claim for player ${playerId}:`, error);
  }
}

/**
 * Ensure required database tables exist
 */
export async function ensureDailyRewardsTables(): Promise<void> {
  let connection: PoolConnection | null = null;

  try {
    connection = await getConnection();

    // Create daily_login_rewards table
    await connection.execute(`
      CREATE TABLE IF NOT EXISTS daily_login_rewards (
        day_number INT PRIMARY KEY,
        reward_type ENUM('diamonds', 'energy', 'item', 'prestige') NOT NULL,
        reward_amount INT NOT NULL,
        reward_item_id INT NULL,
        display_name VARCHAR(100),
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
    `);

    // Create player_login_streak table
    await connection.execute(`
      CREATE TABLE IF NOT EXISTS player_login_streak (
        player_id VARCHAR(128) PRIMARY KEY,
        current_streak INT DEFAULT 0,
        last_login_date DATE,
        total_logins INT DEFAULT 0,
        next_reward_day INT DEFAULT 1,
        claimed_today TINYINT(1) DEFAULT 0,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
    `);

    const [streakColumnsRaw] = await connection.execute<ColumnNameRow[]>(
      `SELECT COLUMN_NAME
       FROM information_schema.columns
       WHERE table_schema = DATABASE()
         AND table_name = 'player_login_streak'`
    );
    const streakColumns = new Set(streakColumnsRaw.map(row => row.COLUMN_NAME.toLowerCase()));

    if (!streakColumns.has('current_streak')) {
      await connection.execute('ALTER TABLE player_login_streak ADD COLUMN current_streak INT DEFAULT 0');
    }
    if (!streakColumns.has('last_login_date')) {
      await connection.execute('ALTER TABLE player_login_streak ADD COLUMN last_login_date DATE');
    }
    if (!streakColumns.has('total_logins')) {
      await connection.execute('ALTER TABLE player_login_streak ADD COLUMN total_logins INT DEFAULT 0');
    }
    if (!streakColumns.has('next_reward_day')) {
      await connection.execute('ALTER TABLE player_login_streak ADD COLUMN next_reward_day INT DEFAULT 1');
    }
    if (!streakColumns.has('claimed_today')) {
      await connection.execute('ALTER TABLE player_login_streak ADD COLUMN claimed_today TINYINT(1) DEFAULT 0');
    }

    console.log('Daily rewards tables verified');
  } catch (error) {
    console.error('Error ensuring daily rewards tables:', error);
    throw error;
  } finally {
    if (connection) {
      connection.release();
    }
  }
}
