const User = require("../models/User");
const PointTransaction = require("../models/PointTransaction");
const Badge = require("../models/Badge");
const UserBadge = require("../models/UserBadge");

/**
 * Point rewards for different actions
 */
const POINT_VALUES = {
  STREET_ADOPTION: 100,
  TASK_COMPLETION: 50,
  POST_CREATION: 10,
  EVENT_PARTICIPATION: 75,
};

/**
 * Awards points to a user with transaction tracking
 *
 * @param {string} userId - User ID
 * @param {number} amount - Points to award (can be negative for deductions)
 * @param {string} type - Transaction type (street_adoption, task_completion, etc.)
 * @param {string} description - Human-readable description
 * @param {Object} relatedEntity - Related entity {entityType, entityId}
 * @returns {Promise<Object>} - Updated user and transaction
 */
async function awardPoints(
  userId,
  amount,
  type,
  description,
  relatedEntity = {}
) {
  try {
    // Get current user
    const user = await User.findById(userId);
    if (!user) {
      throw new Error("User not found");
    }

    // Calculate new balance
    const currentBalance = user.points || 0;
    const newBalance = currentBalance + amount;

    // Update user points
    const updatedUser = await User.update(userId, { points: newBalance });

    // Create transaction record
    const transaction = await PointTransaction.create({
      user: userId,
      amount: amount,
      transactionType: type,
      description: description,
      relatedEntity: relatedEntity,
      balanceAfter: newBalance,
    });

    // Check for new badges
    await checkAndAwardBadges(userId, newBalance);

    return {
      user: updatedUser,
      transaction: transaction,
      newBalance: newBalance,
    };
  } catch (error) {
    console.error("Error awarding points:", error);
    throw error;
  }
}

/**
 * Get user's current point balance
 */
async function getUserPoints(userId) {
  try {
    const user = await User.findById(userId);
    return user ? user.points || 0 : 0;
  } catch (error) {
    console.error("Error getting user points:", error);
    throw error;
  }
}

/**
 * Get user's transaction history
 */
async function getUserTransactionHistory(userId, limit = 50, skip = 0) {
  try {
    return await PointTransaction.findByUser(userId, limit, skip);
  } catch (error) {
    console.error("Error getting transaction history:", error);
    throw error;
  }
}

/**
 * Check if user qualifies for new badges and award them
 */
async function checkAndAwardBadges(userId, userPoints = null) {
  try {
    // Get user's current points if not provided
    if (userPoints === null) {
      userPoints = await getUserPoints(userId);
    }

    // Get user's stats for badge checking
    const userStats = await getUserStats(userId);
    const userBadges = await UserBadge.findByUser(userId);

    // Get all available badges
    const allBadges = await Badge.findAll();

    // Check each badge criteria
    for (const badge of allBadges) {
      // Skip if user already has this badge
      if (userBadges.some(ub => ub.badge?._id === badge._id || ub.badge === badge._id)) {
        continue;
      }

      let qualifies = false;

      // Check different badge criteria
      switch (badge.criteria?.type) {
        case 'points_earned':
          qualifies = userPoints >= badge.criteria.threshold;
          break;
        case 'street_adoptions':
          qualifies = userStats.streetAdoptions >= badge.criteria.threshold;
          break;
        case 'task_completions':
          qualifies = userStats.tasksCompleted >= badge.criteria.threshold;
          break;
        case 'post_creations':
          qualifies = userStats.postsCreated >= badge.criteria.threshold;
          break;
        case 'event_participations':
          qualifies = userStats.eventsParticipated >= badge.criteria.threshold;
          break;
        case 'consecutive_days':
          qualifies = userStats.consecutiveDays >= badge.criteria.threshold;
          break;
        case 'special':
          // Special badges are awarded manually
          qualifies = false;
          break;
      }

      if (qualifies) {
        await awardBadge(userId, badge._id);
      }
    }
  } catch (error) {
    console.error("Error checking badges:", error);
    throw error;
  }
}

/**
 * Award a specific badge to a user
 */
async function awardBadge(userId, badgeId) {
  try {
    // Get badge details
    const badge = await Badge.findById(badgeId);
    if (!badge) {
      throw new Error("Badge not found");
    }

    // Create user badge record
    const userBadge = await UserBadge.create({
      user: userId,
      badge: badgeId,
      earnedAt: new Date().toISOString(),
    });

    // Award points for earning badge (if it's a rare or higher badge)
    let pointsAwarded = 0;
    if (badge.rarity === 'rare') {
      pointsAwarded = 50;
    } else if (badge.rarity === 'epic') {
      pointsAwarded = 100;
    } else if (badge.rarity === 'legendary') {
      pointsAwarded = 200;
    }

    if (pointsAwarded > 0) {
      await awardPoints(
        userId,
        pointsAwarded,
        'badge_earned',
        `Earned ${badge.name} badge`,
        { entityType: 'Badge', entityId: badgeId }
      );
    }

    return userBadge;
  } catch (error) {
    console.error("Error awarding badge:", error);
    throw error;
  }
}

/**
 * Get user statistics for badge checking
 */
async function getUserStats(userId) {
  try {
    const user = await User.findById(userId);
    if (!user) {
      throw new Error("User not found");
    }
    
    return {
      points: user.points || 0,
      streetsAdopted: user.stats?.streetsAdopted || 0,
      tasksCompleted: user.stats?.tasksCompleted || 0,
      postsCreated: user.stats?.postsCreated || 0,
      eventsParticipated: user.stats?.eventsParticipated || 0,
      badgesEarned: user.stats?.badgesEarned || 0,
      consecutiveDays: user.stats?.consecutiveDays || 0,
    };
  } catch (error) {
    console.error("Error getting user stats:", error);
    throw error;
  }
}

/**
 * Get user's badge progress for all badges (earned and unearned)
 */
async function getUserBadgeProgress(userId) {
  try {
    const allBadges = await Badge.findAll();
    const userStats = await getUserStats(userId);
    const userEarnedBadges = await UserBadge.findByUser(userId);

    const badgeProgress = allBadges.map(badge => {
      const earnedBadge = userEarnedBadges.find(ub => ub.badge?._id === badge._id || ub.badge === badge._id);
      const isEarned = !!earnedBadge;
      let progress = 0;
      let threshold = badge.criteria?.threshold || 0;

      if (isEarned) {
        progress = threshold; // If earned, progress is full
      } else if (badge.criteria?.type) {
        switch (badge.criteria.type) {
          case 'points_earned':
            progress = userStats.points || 0;
            break;
          case 'street_adoptions':
            progress = userStats.streetsAdopted;
            break;
          case 'task_completions':
            progress = userStats.tasksCompleted;
            break;
          case 'post_creations':
            progress = userStats.postsCreated;
            break;
          case 'event_participations':
            progress = userStats.eventsParticipated;
            break;
          case 'consecutive_days':
            progress = userStats.consecutiveDays;
            break;
          case 'special':
            progress = 0; // Special badges have no progress bar
            threshold = 1;
            break;
          default:
            progress = 0;
        }
      }

      // Ensure progress doesn't exceed threshold
      progress = Math.min(progress, threshold);

      return {
        ...badge,
        isEarned,
        progress,
        threshold,
        earnedAt: isEarned ? earnedBadge.earnedAt : null,
      };
    });

    return badgeProgress;
  } catch (error) {
    console.error("Error getting user badge progress:", error);
    throw error;
  }
}

/**
 * Get user's badges
 */
async function getUserBadges(userId) {
  try {
    const userBadges = await UserBadge.findByUser(userId);
    const badges = [];

    for (const userBadge of userBadges) {
      const badgeData = userBadge.badge;
      // If badge is already populated (object), use it; otherwise fetch it
      const badge = typeof badgeData === 'object' && badgeData._id ? badgeData : await Badge.findById(badgeData);
      if (badge) {
        badges.push({
          ...badge,
          earnedAt: userBadge.earnedAt,
        });
      }
    }

    return badges;
  } catch (error) {
    console.error("Error getting user badges:", error);
    throw error;
  }
}

/**
 * Redeem points for a reward
 */
async function redeemPoints(userId, rewardId, pointsCost) {
  try {
    const currentPoints = await getUserPoints(userId);
    
    if (currentPoints < pointsCost) {
      throw new Error("Insufficient points");
    }

    // Deduct points
    const result = await awardPoints(
      userId,
      -pointsCost,
      'reward_redemption',
      `Redeemed reward ${rewardId}`,
      { entityType: 'Reward', entityId: rewardId }
    );

    return result;
  } catch (error) {
    console.error("Error redeeming points:", error);
    throw error;
  }
}

/**
 * Get leaderboard
 */
async function getLeaderboard(limit = 10) {
  try {
    // This would typically use a more efficient query
    const users = await User.findAll();
    
    // Sort by points (this should be done at database level for efficiency)
    const sortedUsers = users
      .filter(user => user.points > 0)
      .sort((a, b) => b.points - a.points)
      .slice(0, limit);

    return sortedUsers.map((user, index) => ({
      rank: index + 1,
      userId: user._id,
      username: user.username,
      points: user.points,
      badges: [], // Would populate if needed
    }));
  } catch (error) {
    console.error("Error getting leaderboard:", error);
    throw error;
  }
}

/**
 * Get global leaderboard (all time)
 * @param {number} limit - Number of users to return
 * @param {number} offset - Offset for pagination
 * @returns {Promise<Array>} Leaderboard data
 */
async function getGlobalLeaderboard(limit = 100, offset = 0) {
  try {
    const couchdbService = require("./couchdbService");
    const Street = require("../models/Street");
    const Task = require("../models/Task");
    
    // Get all users sorted by points
    const result = await couchdbService.find({
      selector: {
        type: "user",
        points: { $gt: 0 }
      },
      sort: [{ points: "desc" }],
      limit: limit,
      skip: offset
    });

    const users = Array.isArray(result) ? result : [];

    // Enrich with stats and badges
    const leaderboard = await Promise.all(users.map(async (user, index) => {
      // Get user badges
      const userBadgesRaw = await UserBadge.findByUser(user._id);
      const userBadges = Array.isArray(userBadgesRaw) ? userBadgesRaw : [];
      const badges = await Promise.all(userBadges.map(async (ub) => {
        const badgeData = ub.badge;
        const badge = typeof badgeData === 'object' && badgeData._id ? badgeData : await Badge.findById(badgeData);
        return badge ? {
          _id: badge._id,
          name: badge.name,
          icon: badge.icon,
          rarity: badge.rarity
        } : null;
      }));

      return {
        rank: offset + index + 1,
        userId: user._id,
        username: user.name,
        avatar: user.profilePicture || null,
        points: user.points || 0,
        streetsAdopted: user.stats?.streetsAdopted || user.adoptedStreets?.length || 0,
        tasksCompleted: user.stats?.tasksCompleted || user.completedTasks?.length || 0,
        badges: badges.filter(b => b !== null)
      };
    }));

    return leaderboard;
  } catch (error) {
    console.error("Error getting global leaderboard:", error);
    throw error;
  }
}

/**
 * Get weekly leaderboard
 * @param {number} limit - Number of users to return
 * @param {number} offset - Offset for pagination
 * @returns {Promise<Array>} Leaderboard data
 */
async function getWeeklyLeaderboard(limit = 100, offset = 0) {
  try {
    const couchdbService = require("./couchdbService");
    
    // Calculate start of week (Monday 00:00:00)
    const now = new Date();
    const dayOfWeek = now.getDay();
    const daysToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
    const startOfWeek = new Date(now);
    startOfWeek.setDate(now.getDate() - daysToMonday);
    startOfWeek.setHours(0, 0, 0, 0);

    // Get all point transactions since start of week
    const txResult = await couchdbService.find({
      selector: {
        type: "point_transaction",
        createdAt: { $gte: startOfWeek.toISOString() }
      }
    });
    const transactions = Array.isArray(txResult) ? txResult : [];

    // Aggregate points by user
    const userPointsMap = {};
    transactions.forEach(transaction => {
      if (!userPointsMap[transaction.user]) {
        userPointsMap[transaction.user] = 0;
      }
      userPointsMap[transaction.user] += transaction.amount;
    });

    // Convert to array and sort
    const userPoints = Object.entries(userPointsMap)
      .map(([userId, points]) => ({ userId, points }))
      .filter(entry => entry.points > 0)
      .sort((a, b) => b.points - a.points)
      .slice(offset, offset + limit);

    // Enrich with user data
    const leaderboard = await Promise.all(userPoints.map(async (entry, index) => {
      const user = await User.findById(entry.userId);
      if (!user) return null;

      // Get user badges
      const userBadgesRaw = await UserBadge.findByUser(user._id);
      const userBadges = Array.isArray(userBadgesRaw) ? userBadgesRaw : [];
      const badges = await Promise.all(userBadges.map(async (ub) => {
        const badgeData = ub.badge;
        const badge = typeof badgeData === 'object' && badgeData._id ? badgeData : await Badge.findById(badgeData);
        return badge ? {
          _id: badge._id,
          name: badge.name,
          icon: badge.icon,
          rarity: badge.rarity
        } : null;
      }));

      return {
        rank: offset + index + 1,
        userId: user._id,
        username: user.name,
        avatar: user.profilePicture || null,
        points: entry.points,
        streetsAdopted: user.stats?.streetsAdopted || user.adoptedStreets?.length || 0,
        tasksCompleted: user.stats?.tasksCompleted || user.completedTasks?.length || 0,
        badges: badges.filter(b => b !== null)
      };
    }));

    return leaderboard.filter(entry => entry !== null);
  } catch (error) {
    console.error("Error getting weekly leaderboard:", error);
    throw error;
  }
}

/**
 * Get monthly leaderboard
 * @param {number} limit - Number of users to return
 * @param {number} offset - Offset for pagination
 * @returns {Promise<Array>} Leaderboard data
 */
async function getMonthlyLeaderboard(limit = 100, offset = 0) {
  try {
    const couchdbService = require("./couchdbService");
    
    // Calculate start of month
    const now = new Date();
    const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);

    // Get all point transactions since start of month
    const txResult = await couchdbService.find({
      selector: {
        type: "point_transaction",
        createdAt: { $gte: startOfMonth.toISOString() }
      }
    });
    const transactions = Array.isArray(txResult) ? txResult : [];

    // Aggregate points by user
    const userPointsMap = {};
    transactions.forEach(transaction => {
      if (!userPointsMap[transaction.user]) {
        userPointsMap[transaction.user] = 0;
      }
      userPointsMap[transaction.user] += transaction.amount;
    });

    // Convert to array and sort
    const userPoints = Object.entries(userPointsMap)
      .map(([userId, points]) => ({ userId, points }))
      .filter(entry => entry.points > 0)
      .sort((a, b) => b.points - a.points)
      .slice(offset, offset + limit);

    // Enrich with user data
    const leaderboard = await Promise.all(userPoints.map(async (entry, index) => {
      const user = await User.findById(entry.userId);
      if (!user) return null;

      // Get user badges
      const userBadgesRaw = await UserBadge.findByUser(user._id);
      const userBadges = Array.isArray(userBadgesRaw) ? userBadgesRaw : [];
      const badges = await Promise.all(userBadges.slice(0, 5).map(async (ub) => {
        const badgeData = ub.badge;
        const badge = typeof badgeData === 'object' && badgeData._id ? badgeData : await Badge.findById(badgeData);
        return badge ? {
          _id: badge._id,
          name: badge.name,
          icon: badge.icon,
          rarity: badge.rarity
        } : null;
      }));

      return {
        rank: offset + index + 1,
        userId: user._id,
        username: user.name,
        avatar: user.profilePicture || null,
        points: entry.points,
        streetsAdopted: user.stats?.streetsAdopted || user.adoptedStreets?.length || 0,
        tasksCompleted: user.stats?.tasksCompleted || user.completedTasks?.length || 0,
        badges: badges.filter(b => b !== null)
      };
    }));

    return leaderboard.filter(entry => entry !== null);
  } catch (error) {
    console.error("Error getting monthly leaderboard:", error);
    throw error;
  }
}

/**
 * Get friends leaderboard
 * @param {string} userId - User ID
 * @param {number} limit - Number of users to return
 * @param {number} offset - Offset for pagination
 * @returns {Promise<Array>} Leaderboard data
 */
async function getFriendsLeaderboard(userId, limit = 100, offset = 0) {
  try {
    const user = await User.findById(userId);
    if (!user) {
      throw new Error("User not found");
    }

    // For now, return empty array as friends system isn't implemented
    // In future, would get user's friends list and filter leaderboard
    const friendIds = Array.isArray(user.friends) ? user.friends : [];
    
    if (friendIds.length === 0) {
      // Include self if no friends
      friendIds.push(userId);
    }

    const couchdbService = require("./couchdbService");
    
    // Get friends' data
    const friends = await couchdbService.find({
      selector: {
        type: "user",
        _id: { $in: friendIds }
      }
    });

    // Sort by points
    const sortedFriends = friends
      .sort((a, b) => (b.points || 0) - (a.points || 0))
      .slice(offset, offset + limit);

    // Enrich with badges
    const leaderboard = await Promise.all(sortedFriends.map(async (friend, index) => {
      const userBadges = await UserBadge.findByUser(friend._id);
      const badges = await Promise.all(userBadges.slice(0, 5).map(async (ub) => {
        const badgeData = ub.badge;
        const badge = typeof badgeData === 'object' && badgeData._id ? badgeData : await Badge.findById(badgeData);
        return badge ? {
          _id: badge._id,
          name: badge.name,
          icon: badge.icon,
          rarity: badge.rarity
        } : null;
      }));

      return {
        rank: offset + index + 1,
        userId: friend._id,
        username: friend.name,
        avatar: friend.profilePicture || null,
        points: friend.points || 0,
        streetsAdopted: friend.stats?.streetsAdopted || friend.adoptedStreets?.length || 0,
        tasksCompleted: friend.stats?.tasksCompleted || friend.completedTasks?.length || 0,
        badges: badges.filter(b => b !== null),
        isFriend: true
      };
    }));

    return leaderboard;
  } catch (error) {
    console.error("Error getting friends leaderboard:", error);
    throw error;
  }
}

/**
 * Get user's leaderboard position
 * @param {string} userId - User ID
 * @param {string} timeframe - 'all', 'week', or 'month'
 * @returns {Promise<Object>} User's position data
 */
async function getUserLeaderboardPosition(userId, timeframe = "all") {
  try {
    const user = await User.findById(userId);
    if (!user) {
      return null;
    }

    let rank = 0;
    let totalUsers = 0;
    let userPoints = 0;

    const couchdbService = require("./couchdbService");

    if (timeframe === "all") {
      // Get all users with points
      const allUsers = await couchdbService.find({
        selector: {
          type: "user",
          points: { $gt: 0 }
        },
        sort: [{ points: "desc" }]
      });

      totalUsers = allUsers.length;
      userPoints = user.points || 0;
      
      // Find user's rank
      rank = allUsers.findIndex(u => u._id === userId) + 1;
    } else if (timeframe === "week" || timeframe === "month") {
      // Calculate start date
      const now = new Date();
      let startDate;
      
      if (timeframe === "week") {
        const dayOfWeek = now.getDay();
        const daysToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
        startDate = new Date(now);
        startDate.setDate(now.getDate() - daysToMonday);
        startDate.setHours(0, 0, 0, 0);
      } else {
        startDate = new Date(now.getFullYear(), now.getMonth(), 1);
      }

      // Get all transactions for period
      const transactions = await couchdbService.find({
        selector: {
          type: "point_transaction",
          createdAt: { $gte: startDate.toISOString() }
        }
      });

      // Aggregate points by user
      const userPointsMap = {};
      transactions.forEach(transaction => {
        if (!userPointsMap[transaction.user]) {
          userPointsMap[transaction.user] = 0;
        }
        userPointsMap[transaction.user] += transaction.amount;
      });

      // Sort users by points
      const sortedUsers = Object.entries(userPointsMap)
        .filter(([_, points]) => points > 0)
        .sort((a, b) => b[1] - a[1]);

      totalUsers = sortedUsers.length;
      userPoints = userPointsMap[userId] || 0;
      rank = sortedUsers.findIndex(([id, _]) => id === userId) + 1;
    }

      // Get user badges
      const userBadges = await UserBadge.findByUser(user._id);
      const badges = await Promise.all(userBadges.slice(0, 5).map(async (ub) => {
        const badgeData = ub.badge;
        const badge = typeof badgeData === 'object' && badgeData._id ? badgeData : await Badge.findById(badgeData);
        return badge ? {
          _id: badge._id,
          name: badge.name,
          icon: badge.icon,
          rarity: badge.rarity
        } : null;
      }));

    return {
      rank: rank || null,
      totalUsers,
      userId: user._id,
      username: user.name,
      avatar: user.profilePicture || null,
      points: userPoints,
      streetsAdopted: user.stats?.streetsAdopted || user.adoptedStreets?.length || 0,
      tasksCompleted: user.stats?.tasksCompleted || user.completedTasks?.length || 0,
      badges: badges.filter(b => b !== null),
      percentile: totalUsers > 0 ? Math.round((1 - (rank - 1) / totalUsers) * 100) : 0
    };
  } catch (error) {
    console.error("Error getting user leaderboard position:", error);
    throw error;
  }
}

/**
 * Get leaderboard statistics
 * @returns {Promise<Object>} Statistics data
 */
async function getLeaderboardStats() {
  try {
    const couchdbService = require("./couchdbService");

    // Get all users with points
    const allUsers = await couchdbService.find({
      selector: {
        type: "user",
        points: { $gt: 0 }
      }
    });

    // Calculate statistics
    const totalUsers = allUsers.length;
    const totalPoints = allUsers.reduce((sum, user) => sum + (user.points || 0), 0);
    const avgPoints = totalUsers > 0 ? Math.round(totalPoints / totalUsers) : 0;
    const maxPoints = allUsers.length > 0 ? Math.max(...allUsers.map(u => u.points || 0)) : 0;
    const minPoints = allUsers.length > 0 ? Math.min(...allUsers.map(u => u.points || 0)) : 0;

    // Get weekly stats
    const now = new Date();
    const dayOfWeek = now.getDay();
    const daysToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
    const startOfWeek = new Date(now);
    startOfWeek.setDate(now.getDate() - daysToMonday);
    startOfWeek.setHours(0, 0, 0, 0);

    const weeklyTransactions = await couchdbService.find({
      selector: {
        type: "point_transaction",
        createdAt: { $gte: startOfWeek.toISOString() }
      }
    });

    const weeklyPoints = weeklyTransactions.reduce((sum, t) => sum + (t.amount || 0), 0);
    const activeUsersThisWeek = new Set(weeklyTransactions.map(t => t.user)).size;

    return {
      totalUsers,
      totalPoints,
      avgPoints,
      maxPoints,
      minPoints,
      weeklyStats: {
        totalPoints: weeklyPoints,
        activeUsers: activeUsersThisWeek,
        transactions: weeklyTransactions.length
      }
    };
  } catch (error) {
    console.error("Error getting leaderboard statistics:", error);
    throw error;
  }
}

module.exports = {
  awardPoints,
  getUserPoints,
  getUserTransactionHistory,
  checkAndAwardBadges,
  awardBadge,
  getUserBadges,
  getUserBadgeProgress,
  getUserStats,
  redeemPoints,
  getLeaderboard,
  getGlobalLeaderboard,
  getWeeklyLeaderboard,
  getMonthlyLeaderboard,
  getFriendsLeaderboard,
  getUserLeaderboardPosition,
  getLeaderboardStats,
  POINT_VALUES,
};