// backend/src/utils/xpService.js
const db = require("../db");

const STREAK_BONUS = 50; // XP bonus for maintaining daily streak

/**
 * Update a user's streak based on their last activity.
 * Returns { streakUpdated: boolean, newStreakDays: number, bonusAwarded: boolean }
 */
const updateStreak = async (userId) => {
  const result = {
    streakUpdated: false,
    newStreakDays: 0,
    bonusAwarded: false,
  };

  try {
    // Get current streak data
    const xpResult = await db.query("SELECT * FROM user_xp WHERE user_id = ?", [
      userId,
    ]);

    if (xpResult.rows.length === 0) {
      // Initialize XP record if not exists
      await db.query(
        "INSERT INTO user_xp (user_id, total_xp, current_level, xp_to_next_level, streak_days, last_activity_date) VALUES (?, 0, 1, 100, 1, CURDATE())",
        [userId]
      );
      // Award bonus for first activity
      await awardXP(userId, STREAK_BONUS);
      result.newStreakDays = 1;
      result.streakUpdated = true;
      result.bonusAwarded = true;
      return result;
    }

    const xpData = xpResult.rows[0];
    const today = new Date();
    today.setHours(0, 0, 0, 0);

    let lastActivityDate = null;
    if (xpData.last_activity_date) {
      lastActivityDate = new Date(xpData.last_activity_date);
      lastActivityDate.setHours(0, 0, 0, 0);
    }

    // Calculate difference in days
    let diffDays = Infinity;
    if (lastActivityDate) {
      diffDays = Math.floor((today - lastActivityDate) / (1000 * 60 * 60 * 24));
    }

    if (diffDays === 0) {
      // Already active today - no streak change, no bonus
      result.newStreakDays = xpData.streak_days || 1;
      return result;
    }

    let newStreak = 1;
    if (diffDays === 1) {
      // Consecutive day - increment streak
      newStreak = (xpData.streak_days || 0) + 1;
    }
    // If diffDays > 1, streak resets to 1 (already set)

    // Update streak and last activity date
    await db.query(
      "UPDATE user_xp SET streak_days = ?, last_activity_date = CURDATE() WHERE user_id = ?",
      [newStreak, userId]
    );

    // Award streak bonus
    await awardXP(userId, STREAK_BONUS);

    result.newStreakDays = newStreak;
    result.streakUpdated = true;
    result.bonusAwarded = true;
    return result;
  } catch (error) {
    console.error("[xpService] Error updating streak:", error);
    return result;
  }
};

/**
 * Award XP to a user (does NOT update streak).
 * Use for lesson/investigation base rewards.
 */
const awardXP = async (userId, xpAmount) => {
  try {
    const current = await db.query("SELECT * FROM user_xp WHERE user_id = ?", [
      userId,
    ]);

    if (current.rows.length === 0) {
      await db.query(
        "INSERT INTO user_xp (user_id, total_xp, monthly_xp, current_level, xp_to_next_level, streak_days, last_activity_date) VALUES (?, ?, ?, 1, 100, 0, NULL)",
        [userId, xpAmount, xpAmount]
      );
      return { newXP: xpAmount, level: 1 };
    }

    const newXP = current.rows[0].total_xp + xpAmount;
    let level = current.rows[0].current_level;
    let xpToNext = current.rows[0].xp_to_next_level;

    // Level up logic (100 XP per level, scaling)
    while (newXP >= xpToNext) {
      level++;
      xpToNext = level * 100;
    }

    await db.query(
      "UPDATE user_xp SET total_xp = ?, monthly_xp = monthly_xp + ?, current_level = ?, xp_to_next_level = ? WHERE user_id = ?",
      [newXP, xpAmount, level, xpToNext, userId]
    );

    return { newXP, level };
  } catch (error) {
    console.error("[xpService] Error awarding XP:", error);
    return null;
  }
};

/**
 * Combined call: Award XP and update streak.
 * Use this for lesson/investigation completions.
 */
const awardXPWithStreak = async (userId, xpAmount) => {
  // First, update streak (awards STREAK_BONUS if applicable)
  const streakResult = await updateStreak(userId);

  // Then, award base XP for the activity
  const xpResult = await awardXP(userId, xpAmount);

  // Check for new badges
  const newBadges = await checkAndAwardBadges(
    userId,
    streakResult.newStreakDays
  );

  return {
    baseXP: xpAmount,
    streakBonus: streakResult.bonusAwarded ? STREAK_BONUS : 0,
    totalXPEarned: xpAmount + (streakResult.bonusAwarded ? STREAK_BONUS : 0),
    newStreakDays: streakResult.newStreakDays,
    newBadges,
    ...xpResult,
  };
};

/**
 * Check and award badges based on user's activity.
 * Called after lesson/investigation completion.
 */
const checkAndAwardBadges = async (
  userId,
  currentStreak = 0,
  investigationId = null
) => {
  const earnedBadges = [];

  try {
    // Get all badges user hasn't earned yet
    const unearned = await db.query(
      `
            SELECT b.* FROM badges b 
            WHERE b.id NOT IN (SELECT badge_id FROM user_badges WHERE user_id = ?)
        `,
      [userId]
    );

    // Get user stats for milestone checks
    const lessonCount = await db.query(
      `
            SELECT COUNT(*) as count FROM user_task_progress WHERE user_id = ? AND completed = TRUE
        `,
      [userId]
    );
    const lessonsCompleted = lessonCount.rows[0]?.count || 0;

    const investigationCount = await db.query(
      `
            SELECT COUNT(*) as count FROM investigations WHERE user_id = ? AND status = 'graded'
        `,
      [userId]
    );
    const investigationsCompleted = investigationCount.rows[0]?.count || 0;

    // Fetch investigation details if ID provided (needed for Time/Speed/First Blood)
    let invDetails = null;
    if (investigationId) {
      const result = await db.query(
        "SELECT * FROM investigations WHERE id = ?",
        [investigationId]
      );
      if (result.rows.length > 0) {
        invDetails = result.rows[0];
      }
    }

    // Check each unearned badge
    for (const badge of unearned.rows) {
      let criteria = {};
      try {
        criteria =
          typeof badge.criteria === "string"
            ? JSON.parse(badge.criteria)
            : badge.criteria || {};
      } catch (e) {
        continue;
      }

      let shouldAward = false;

      // Milestone badges
      if (
        criteria.investigations_completed &&
        investigationsCompleted >= criteria.investigations_completed
      ) {
        shouldAward = true;
      }
      if (
        criteria.lessons_completed &&
        lessonsCompleted >= criteria.lessons_completed
      ) {
        shouldAward = true;
      }

      // Streak badges
      if (criteria.streak_days && currentStreak >= criteria.streak_days) {
        shouldAward = true;
      }

      // Time Range Badges (Requires active investigation context)
      if (criteria.time_range && invDetails) {
        const hour = new Date(invDetails.updated_at || new Date()).getHours(); // Use completion time

        if (criteria.time_range === "night_owl" && hour >= 0 && hour < 5) {
          shouldAward = true;
        } else if (
          criteria.time_range === "early_bird" &&
          hour >= 5 &&
          hour < 9
        ) {
          shouldAward = true;
        } else if (
          criteria.time_range === "day_walker" &&
          hour >= 9 &&
          hour < 17
        ) {
          shouldAward = true;
        } else if (
          criteria.time_range === "lunch_break" &&
          hour >= 12 &&
          hour < 14
        ) {
          shouldAward = true;
        }
      }

      // Performance Badges (Requires active investigation context)
      if (criteria.performance && invDetails) {
        // Speed Demon: Check duration (updated_at - created_at)
        if (criteria.performance === "speed_demon") {
          const start = new Date(invDetails.created_at);
          const end = new Date(invDetails.updated_at || new Date());
          const durationMinutes = (end - start) / (1000 * 60); // minutes

          if (durationMinutes <= (criteria.threshold_minutes || 10)) {
            shouldAward = true;
          }
        }

        // First Blood: Check if this alert has other graded investigations
        if (criteria.performance === "first_blood") {
          const others = await db.query(
            `
                        SELECT COUNT(*) as count FROM investigations 
                        WHERE alert_id = ? AND status = 'graded' AND id != ?
                    `,
            [invDetails.alert_id, invDetails.id]
          );

          if (others.rows[0].count === 0) {
            shouldAward = true;
          }
        }
      }

      // Award the badge if criteria met
      if (shouldAward) {
        await db.query(
          "INSERT INTO user_badges (user_id, badge_id) VALUES (?, ?)",
          [userId, badge.id]
        );
        await awardXP(userId, badge.xp_reward || 0);
        earnedBadges.push({ name: badge.name, xp: badge.xp_reward });
        console.log(
          `[xpService] Awarded badge "${badge.name}" to user ${userId}`
        );
      }
    }
  } catch (error) {
    console.error("[xpService] Error checking badges:", error);
  }

  return earnedBadges;
};

/**
 * Get user's current XP stats (for mobile dashboard)
 */
const getUserStats = async (userId) => {
  try {
    const result = await db.query("SELECT * FROM user_xp WHERE user_id = ?", [userId]);
    
    if (result.rows.length === 0) {
      return {
        level: 1,
        totalXP: 0,
        xpToNextLevel: 100,
        streakDays: 0,
        lastActivityDate: null
      };
    }

    const data = result.rows[0];
    return {
      level: data.current_level || 1,
      totalXP: data.total_xp || 0,
      xpToNextLevel: data.xp_to_next_level || 100,
      xpProgress: data.total_xp % 100, // XP within current level
      streakDays: data.streak_days || 0,
      lastActivityDate: data.last_activity_date
    };
  } catch (error) {
    console.error('[xpService] Error getting user stats:', error);
    return { level: 1, totalXP: 0, xpToNextLevel: 100, streakDays: 0 };
  }
};

module.exports = {
  awardXP,
  updateStreak,
  awardXPWithStreak,
  checkAndAwardBadges,
  getUserStats,
  STREAK_BONUS,
};
