import type { Express } from "express";
import { createServer, type Server } from "http";
import { storage } from "./storage.js";
import { insertLabelSchema, insertTaskSchema, insertNoteSchema, insertXpEventSchema, insertGoalSchema, insertRewardSchema, rewards, userRewards, User } from "../shared/schema.js";
import { z } from "zod";
import { EmailService } from "./email.js";
import { AiService } from "./ai.js";


const emailService = new EmailService(storage);
const aiService = new AiService(storage);

import { setupAuth, hashPassword, comparePassword } from "./auth.js";

function isAdmin(req: any, res: any, next: any) {
  if (req.isAuthenticated() && req.user.role === 'admin') {
    return next();
  }
  res.status(403).json({ error: "Unauthorized: Admin access required" });
}

export async function registerRoutes(app: Express): Promise<Server> {
  setupAuth(app);

  // --- Setup Routes ---
  app.get("/api/setup/status", async (req, res) => {
    const hasAdmin = await storage.hasAdminUser();
    res.json({ isSetup: hasAdmin });
  });

  // Public settings endpoint for auth page
  app.get("/api/settings/public", async (req, res) => {
    const regEnabled = await storage.getSystemSettings("registration_enabled");
    // Default to true if not set, or specifically check for "false"
    res.json({ registration_enabled: regEnabled !== "false" });
  });

  app.post("/api/setup", async (req, res) => {
    const hasAdmin = await storage.hasAdminUser();
    if (hasAdmin) {
      return res.status(403).json({ error: "Setup already completed" });
    }

    // Create Super Admin
    try {
      const hashedPassword = await hashPassword(req.body.password);
      const adminUser = await storage.createUser({
        username: req.body.username,
        email: req.body.email,
        password: hashedPassword,
        role: 'admin',
        isActive: true
      });

      // Auto-enable registration by default on setup
      await storage.setSystemSettings("registration_enabled", "true");

      req.login(adminUser, (err) => {
        if (err) return res.status(500).json({ error: "Login failed after setup" });
        return res.json(adminUser);
      });
    } catch (e) {
      res.status(500).json({ error: "Failed to create admin user" });
    }
  });

  // --- Password Reset Routes ---
  app.post("/api/auth/forgot-password", async (req, res) => {
    try {
      const { email } = req.body;
      if (!email) return res.status(400).json({ error: "Email required" });

      const user = await storage.getUserByEmail(email);
      if (!user) {
        // Check security best practices: delay response or return success to avoid enumeration?
        // For now, let's behave nicely.
        return res.json({ message: "If an account exists, a reset email has been sent." });
      }

      const tokenString = crypto.randomUUID();
      // Expires in 1 hour
      const expiresAt = new Date(Date.now() + 60 * 60 * 1000);

      await storage.createPasswordResetToken({
        userId: user.id,
        token: tokenString,
        expiresAt,
        isUsed: false
      });

      await emailService.sendPasswordResetEmail(user, tokenString);
      res.json({ message: "If an account exists, a reset email has been sent." });
    } catch (e) {
      console.error("Forgot Password Error:", e);
      res.status(500).json({ error: "Server error" });
    }
  });

  app.post("/api/auth/reset-password", async (req, res) => {
    try {
      const { token, newPassword } = req.body;
      if (!token || !newPassword) return res.status(400).json({ error: "Token and password required" });

      const resetToken = await storage.getPasswordResetToken(token);
      if (!resetToken) {
        return res.status(400).json({ error: "Invalid or expired token" });
      }

      if (resetToken.isUsed) {
        return res.status(400).json({ error: "Token already used" });
      }

      if (new Date() > new Date(resetToken.expiresAt)) {
        return res.status(400).json({ error: "Token expired" });
      }

      // Update User Password
      const hashedPassword = await hashPassword(newPassword);
      await storage.updateUser(resetToken.userId, { password: hashedPassword });

      // Mark token used
      await storage.markPasswordResetTokenUsed(resetToken.id);

      res.json({ message: "Password reset successfully. You can now login." });
    } catch (e) {
      console.error("Reset Password Error:", e);
      res.status(500).json({ error: "Server error" });
    }
  });

  // --- Admin Routes ---
  app.get("/api/admin/users", isAdmin, async (req, res) => {
    try {
      const users = await storage.getAllUsers();
      res.json(users);
    } catch (e) {
      res.status(500).json({ error: "Failed to fetch users" });
    }
  });

  app.post("/api/admin/users", isAdmin, async (req, res) => {
    try {
      const hashedPassword = await hashPassword(req.body.password);
      const newUser = await storage.createUser({
        username: req.body.username,
        email: req.body.email,
        password: hashedPassword,
        role: req.body.role || 'user',
        isActive: true
      });
      res.json(newUser);
    } catch (e) {
      res.status(500).json({ error: "Failed to create user" });
    }
  });

  app.post("/api/admin/users/:id/toggle-active", isAdmin, async (req, res) => {
    try {
      const user = await storage.getUser(req.params.id);
      if (!user) return res.status(404).json({ error: "User not found" });

      if (user.role === 'admin' && user.id === (req.user as User).id) {
        return res.status(400).json({ error: "Cannot deactivate yourself" });
      }

      const updated = await storage.updateUser(user.id, { isActive: !user.isActive });
      res.json(updated);
    } catch (e) {
      res.status(500).json({ error: "Failed to toggle user status" });
    }
  });

  app.delete("/api/admin/users/:id", isAdmin, async (req, res) => {
    try {
      const user = await storage.getUser(req.params.id);
      if (!user) return res.status(404).json({ error: "User not found" });

      if (user.role === 'admin' && user.id === (req.user as User).id) {
        return res.status(400).json({ error: "Cannot delete yourself" });
      }

      await storage.deleteUser(user.id);
      res.sendStatus(204);
    } catch (e) {
      res.status(500).json({ error: "Failed to delete user" });
    }
  });

  app.get("/api/admin/settings", isAdmin, async (req, res) => {
    const keys = ["registration_enabled", "smtp_host", "smtp_port", "smtp_user", "smtp_pass", "smtp_from", "smtp_secure"];
    const settings: any = {};
    for (const key of keys) {
      const val = await storage.getSystemSettings(key);
      if (key === "registration_enabled" || key === "smtp_secure") {
        settings[key] = val === "true";
      } else {
        settings[key] = val || ""; // Return empty string if undefined for inputs
      }
    }
    res.json(settings);
  });

  app.post("/api/admin/settings", isAdmin, async (req, res) => {
    const keys = ["registration_enabled", "smtp_host", "smtp_port", "smtp_user", "smtp_pass", "smtp_from", "smtp_secure"];
    for (const key of keys) {
      if (req.body[key] !== undefined) {
        await storage.setSystemSettings(key, String(req.body[key]));
      }
    }
    res.json({ success: true });
  });

  // --- AI Routes ---
  app.post("/api/ai/chat", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    const user = req.user as User;
    if (!user.aiEnabled) return res.status(403).json({ error: "AI Assistant is disabled for this user" });

    try {
      const { messages } = req.body;
      if (!Array.isArray(messages)) return res.status(400).json({ error: "Messages must be an array" });

      // Build User Context
      const tasks = await storage.getTasksForUser(user.id);
      const activeTasks = tasks.filter(t => t.status !== 'done');
      const completedTasks = tasks.filter(t => t.status === 'done');

      const context = `
User Context:
- User ID: ${user.id}
- Username: ${user.username}
- XP: ${user.xp} (Level ${user.level})

Task Summary:
- Total Active Tasks: ${activeTasks.length}
- Total Completed Tasks: ${completedTasks.length}

High Priority Active Tasks:
${activeTasks.filter(t => t.priority === 'high').map(t => `- ${t.title} (Due: ${t.dueDate})`).join('\n') || 'None'}

Recent Active Tasks:
${activeTasks.slice(0, 5).map(t => `- [${t.priority}] ${t.title}`).join('\n')}
`;

      const response = await aiService.chat(messages, user, context);
      res.json({ role: "assistant", content: response });
    } catch (e: any) {
      console.error("AI Route Error:", e);
      res.status(500).json({ error: e.message || "Failed to generate AI response" });
    }
  });

  // Health check endpoint
  app.get("/api/health", (req, res) => {
    res.status(200).json({ status: "ok", timestamp: new Date().toISOString() });
  });

  // Labels API routes
  app.get("/api/labels", async (req, res) => {
    try {
      if (!req.isAuthenticated()) return res.sendStatus(401);
      const userId = (req.user as User).id;
      const allLabels = await storage.getAllLabels();
      const sharedLabels = await storage.getSharedLabels(userId);
      const sharedLabelIds = new Set(sharedLabels.map(sl => sl.labelId));

      // Filter: Created by me OR Shared with me
      // If creatorId is null (legacy/default), everyone sees it? Or system public?
      // Assumption: labels with creatorId=null are "System Defaults" visible to all.
      // Or we should update getAllLabels to filter in DB.

      const visibleLabels = allLabels.filter(l =>
        l.creatorId === userId ||
        sharedLabelIds.has(l.id) ||
        l.creatorId === null
      );

      res.json(visibleLabels);
    } catch (error) {
      res.status(500).json({ error: "Failed to fetch labels" });
    }
  });

  app.get("/api/labels/:id", async (req, res) => {
    try {
      const label = await storage.getLabel(req.params.id);
      if (!label) {
        return res.status(404).json({ error: "Label not found" });
      }
      res.json(label);
    } catch (error) {
      res.status(500).json({ error: "Failed to fetch label" });
    }
  });

  app.post("/api/labels", async (req, res) => {
    try {
      const result = insertLabelSchema.safeParse(req.body);
      if (!result.success) {
        return res.status(400).json({ error: "Invalid label data", details: result.error });
      }

      const label = await storage.createLabel({
        ...result.data,
        creatorId: (req.user as User).id // Assign creator
      });
      res.status(201).json(label);
    } catch (error) {
      console.error("Create Label Error:", error);
      res.status(500).json({ error: "Failed to create label" });
    }
  });

  app.patch("/api/labels/:id", async (req, res) => {
    try {
      const updates = insertLabelSchema.partial().safeParse(req.body);
      if (!updates.success) {
        return res.status(400).json({ error: "Invalid label data", details: updates.error });
      }

      const label = await storage.updateLabel(req.params.id, updates.data);
      if (!label) {
        return res.status(404).json({ error: "Label not found" });
      }
      res.json(label);
    } catch (error) {
      res.status(500).json({ error: "Failed to update label" });
    }
  });

  app.delete("/api/labels/:id", async (req, res) => {
    try {
      const success = await storage.deleteLabel(req.params.id);
      if (!success) {
        return res.status(404).json({ error: "Label not found" });
      }
      res.status(204).send();
    } catch (error) {
      res.status(500).json({ error: "Failed to delete label" });
    }
  });

  // Shared Label Routes
  app.get("/api/labels/:id/share", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    try {
      const label = await storage.getLabel(req.params.id);
      if (!label) return res.status(404).json({ error: "Label not found" });

      // Only creator or admin or write permission can view shares?
      // Actually creator, or anyone with 'write'/'admin' permission on the label?
      // For simplicity: Only creator can manage shares.
      if (label.creatorId && label.creatorId !== (req.user as User).id) {
        return res.status(403).json({ error: "Only the label owner can manage shares" });
      }

      // const shares = await storage.getLabelShares(req.params.id); // Not needed as we fetch below

      // We need user details for the frontend
      const users = await storage.getLabelSharedUsers(req.params.id);
      const sharesWithDetails = await Promise.all(users.map(async u => {
        const shareInfos = await storage.getLabelShares(req.params.id);
        const specificShare = shareInfos.find(s => s.sharedWithUserId === u.id);
        return {
          userId: u.id,
          username: u.username,
          permission: specificShare?.permission || 'read'
        };
      }));

      res.json(sharesWithDetails);
    } catch (e) {
      res.status(500).json({ error: "Failed to fetch label shares" });
    }
  });

  app.post("/api/labels/:id/share", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    try {
      const { username, permission } = req.body;
      if (!username) return res.status(400).json({ error: "Username required" });

      const label = await storage.getLabel(req.params.id);
      if (!label) return res.status(404).json({ error: "Label not found" });

      if (label.creatorId && label.creatorId !== (req.user as User).id) {
        return res.status(403).json({ error: "Only owner can share label" });
      }

      const targetUser = await storage.getUserByUsername(username);
      if (!targetUser) return res.status(404).json({ error: "User not found" });
      if (targetUser.id === (req.user as User).id) return res.status(400).json({ error: "Cannot share with yourself" });

      const share = await storage.shareLabel(label.id, targetUser.id, (req.user as User).id, permission || 'read');
      res.json(share);
    } catch (e) {
      res.status(500).json({ error: "Failed to share label" });
    }
  });

  app.delete("/api/labels/:id/share/:userId", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    try {
      const label = await storage.getLabel(req.params.id);
      if (!label) return res.status(404).json({ error: "Label not found" });

      if (label.creatorId && label.creatorId !== (req.user as User).id) {
        // Also allow user to unshare themselves?
        if (req.params.userId !== (req.user as User).id) {
          return res.status(403).json({ error: "Only owner can remove other collaborators" });
        }
      }

      await storage.unshareLabel(label.id, req.params.userId);
      res.sendStatus(204);
    } catch (e) {
      res.status(500).json({ error: "Failed to remove share" });
    }
  });

  // Tasks API routes (updated GET)
  app.get("/api/tasks", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    try {
      const tasks = await storage.getTasksForUser((req.user as User).id);
      res.json(tasks);
    } catch (error) {
      res.status(500).json({ error: "Failed to fetch tasks" });
    }
  });

  app.get("/api/tasks/:id", async (req, res) => {
    // TODO: Check if user has access to this specific task (Owns it OR is Shared)
    // For now, simple get
    try {
      const task = await storage.getTask(req.params.id);
      if (!task) {
        return res.status(404).json({ error: "Task not found" });
      }
      res.json(task);
    } catch (error) {
      res.status(500).json({ error: "Failed to fetch task" });
    }
  });

  app.post("/api/tasks", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    try {
      const result = insertTaskSchema.safeParse(req.body);
      if (!result.success) {
        return res.status(400).json({ error: "Invalid task data", details: result.error });
      }

      const task = await storage.createTask({
        ...result.data,
        userId: (req.user as User).id
      });
      res.status(201).json(task);
    } catch (error) {
      res.status(500).json({ error: "Failed to create task" });
    }
  });

  app.patch("/api/tasks/:id", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    try {
      const previousTask = await storage.getTask(req.params.id);
      const updates = insertTaskSchema.partial().safeParse(req.body);

      if (!updates.success) {
        return res.status(400).json({ error: "Invalid task data", details: updates.error });
      }

      // Gamification: Award XP on completion
      if (previousTask && previousTask.status !== 'done' && updates.data.status === 'done') {
        try {
          const xpEarned = calculateXP(previousTask);
          const user = await storage.getUser((req.user as User).id);

          if (!user) throw new Error("User not found for gamification");

          let newStreak = user.currentStreak || 0;
          let streakBonus = 0;
          let diffDays = 0;

          if (user) {
            const now = new Date();
            const lastDate = user.lastTaskDate ? new Date(user.lastTaskDate) : null;

            if (!lastDate) {
              newStreak = 1;
            } else {
              const diffTime = Math.abs(now.setHours(0, 0, 0, 0) - lastDate.setHours(0, 0, 0, 0));
              diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));

              if (diffDays === 1) {
                newStreak += 1;
                streakBonus = Math.min(newStreak * 5, 50);
              } else if (diffDays > 1) {
                newStreak = 1;
                newStreak = 1;
              }
            }

            // --- Daily Clear Bonus Check ---
            // Check if this was the last 'todo' task for today
            const startOfDay = new Date();
            startOfDay.setHours(0, 0, 0, 0);
            const endOfDay = new Date();
            endOfDay.setHours(23, 59, 59, 999);

            // Re-fetch all tasks (inefficient but safe for now, better: optimize storage method)
            const allTasks = await storage.getTasksForUser(user.id);
            const remainingToday = allTasks.filter(t =>
              t.id !== previousTask.id && // exclude current
              t.status !== 'done' && // is remaining
              t.dueDate && // has due date
              new Date(t.dueDate) >= startOfDay &&
              new Date(t.dueDate) <= endOfDay
            );

            if (remainingToday.length === 0) {
              // Bonus!
              const clearBonus = 50;
              await storage.logXpEvent({
                userId: user.id,
                amount: clearBonus,
                source: 'daily_clear_bonus', // Ensure translation key exists
              });
              console.log(`[Gamification] Awarded ${clearBonus} XP for Daily Clear`);
            }

            if (diffDays !== 0 || !lastDate) {
              await storage.updateUser(user.id, {
                currentStreak: newStreak,
                lastTaskDate: new Date()
              });
            }
          }

          // Log XP Event (Task)
          await storage.logXpEvent({
            userId: (req.user as User).id,
            amount: xpEarned,
            source: 'task_completion',
            taskId: previousTask.id
          });

          // Log XP Event (Streak Bonus)
          if (streakBonus > 0) {
            await storage.logXpEvent({
              userId: (req.user as User).id,
              amount: streakBonus,
              source: 'daily_streak',
            });
            console.log(`[Gamification] Awarded ${streakBonus} XP for streak of ${newStreak}`);
          }

          console.log(`[Gamification] Awarded ${xpEarned} XP for task ${previousTask.title}`);

          // --- Goal Progress Check ---
          try {
            // Fetch active goals
            const goals = await storage.getGoals(); // TODO: Filter by userId in storage
            const userGoals = goals.filter(g => g.userId === user.id && !g.completed);

            for (const goal of userGoals) {
              let progress = 0;
              // Calculate progress based on type
              if (goal.type === 'weekly_tasks') {
                // Count tasks completed this week
                // Simplified: just update goal.current + 1 for now if we don't have full count logic
                // Ideally we recount from history, but incremental update is easier
                progress = goal.current + 1;
              } else if (goal.type === 'streak') {
                progress = newStreak;
              } else if (goal.type === 'total_xp') {
                progress = user.xp + xpEarned; // XP updated via logXpEvent side-effect? No, explicitly.
                // The user obj here is stale, user.xp is old. 
                // But we just added xpEarned in logXpEvent (via side effect in storage).
                // Let's assume +xpEarned.
                // A better way is to re-fetch user, or rely on client/server sync.
                progress = user.xp + xpEarned + streakBonus;
              }

              // Update Goal
              if (progress !== goal.current) {
                await storage.updateGoal(goal.id, { current: progress, completed: progress >= goal.target });

                if (progress >= goal.target && !goal.completed) {
                  // Goal Completion Bonus
                  const goalBonus = 100;
                  await storage.logXpEvent({
                    userId: user.id,
                    amount: goalBonus,
                    source: 'goal_completed'
                  });
                  console.log(`[Gamification] Goal "${goal.title}" Completed! +${goalBonus} XP`);
                }
              }
            }
          } catch (goalErr) {
            console.error("[Gamification] Error checking goals:", goalErr);
          }

        } catch (err) {
          console.error("[Gamification] Error processing rewards:", err);
          // Do not fail the request, just log
        }
      }

      const task = await storage.updateTask(req.params.id, updates.data);
      if (!task) {
        return res.status(404).json({ error: "Task not found" });
      }
      res.json(task);
    } catch (error: any) {
      console.error("PATCH Task Error:", error);
      res.status(500).json({ error: "Failed to update task", details: String(error) });
    }
  });

  app.delete("/api/tasks/:id", async (req, res) => {
    try {
      const success = await storage.deleteTask(req.params.id);
      if (!success) {
        return res.status(404).json({ error: "Task not found" });
      }
      res.status(204).send();
    } catch (error) {
      res.status(500).json({ error: "Failed to delete task" });
    }
  });

  // Notes API routes
  app.get("/api/notes", async (req, res) => {
    try {
      // In a real app, filter by userId
      // const notes = await storage.getAllNotes(); // You'd need to implement this in storage.ts
      res.json([]); // Placeholder until storage implementation
    } catch (error) {
      res.status(500).json({ error: "Failed to fetch notes" });
    }
  });

  app.post("/api/notes", async (req, res) => {
    try {
      const result = insertNoteSchema.safeParse(req.body);
      if (!result.success) {
        return res.status(400).json({ error: "Invalid note data", details: result.error });
      }
      // const note = await storage.createNote(result.data);
      res.status(201).json({ ...result.data, id: "placeholder" });
    } catch (error) {
      res.status(500).json({ error: "Failed to create note" });
    }
  });

  // Goals API
  app.get("/api/goals", async (req, res) => {
    try {
      const goals = await storage.getGoals(); // Need to impl in storage
      res.json(goals);
    } catch (error) {
      res.status(500).json({ error: "Failed to fetch goals" });
    }
  });

  app.post("/api/goals", async (req, res) => {
    try {
      console.log("POST /api/goals hit", req.body);
      const result = insertGoalSchema.safeParse(req.body);
      if (!result.success) {
        console.error("Validation error:", result.error);
        return res.status(400).json(result.error);
      }
      console.log("Validation passed, creating goal...");
      const goal = await storage.createGoal(result.data);
      console.log("Goal created:", goal);
      res.json(goal);
    } catch (error) {
      console.error("Error in POST /api/goals:", error);
      res.status(500).json({ error: "Failed to create goal" });
    }
  });

  // Analytics API
  app.get("/api/analytics/weekly", async (req, res) => {
    // Return last 7 days. Key is 0-6 (Sun-Sat) or ISO date.
    // For simplicity, let's return day index relative to today or just standard day index (0=Sun)
    // To make it look "last 7 days" we can return relative indices
    const keys = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
    // Better: Send localizable keys.
    // Day format: "day_1" (Mon) ... "day_7" (Sun) or just short codes the frontend can map

    // We will send standard JS Day indices adjusted: 1 (Mon) - 7 (Sun) for "ISO Week" style or just 0-6
    // Let's send a `labelKey` that the frontend can translate.
    const data = [
      { labelKey: 'mon', xp: Math.floor(Math.random() * 500) },
      { labelKey: 'tue', xp: Math.floor(Math.random() * 500) },
      { labelKey: 'wed', xp: Math.floor(Math.random() * 500) },
      { labelKey: 'thu', xp: Math.floor(Math.random() * 500) },
      { labelKey: 'fri', xp: Math.floor(Math.random() * 500) },
      { labelKey: 'sat', xp: Math.floor(Math.random() * 500) },
      { labelKey: 'sun', xp: Math.floor(Math.random() * 500) },
    ];
    res.json(data);
  });

  app.get("/api/analytics/yearly", async (req, res) => {
    const data = [
      { labelKey: 'jan', xp: Math.floor(Math.random() * 2000) },
      { labelKey: 'feb', xp: Math.floor(Math.random() * 2000) },
      { labelKey: 'mar', xp: Math.floor(Math.random() * 2000) },
      { labelKey: 'apr', xp: Math.floor(Math.random() * 2000) },
      { labelKey: 'may', xp: Math.floor(Math.random() * 2000) },
      { labelKey: 'jun', xp: Math.floor(Math.random() * 2000) },
      { labelKey: 'jul', xp: Math.floor(Math.random() * 2000) },
      { labelKey: 'aug', xp: Math.floor(Math.random() * 2000) },
      { labelKey: 'sep', xp: Math.floor(Math.random() * 2000) },
      { labelKey: 'oct', xp: Math.floor(Math.random() * 2000) },
      { labelKey: 'nov', xp: Math.floor(Math.random() * 2000) },
      { labelKey: 'dec', xp: Math.floor(Math.random() * 2000) },
    ];
    res.json(data);
  });

  app.get("/api/analytics/monthly", async (req, res) => {
    // Return last 4-5 weeks with actual Calendar Week numbers
    // Mocking for now: Assume current week is ~50
    const currentWeek = 50;
    const data = [
      { labelKey: (currentWeek - 3).toString(), xp: Math.floor(Math.random() * 800) },
      { labelKey: (currentWeek - 2).toString(), xp: Math.floor(Math.random() * 800) },
      { labelKey: (currentWeek - 1).toString(), xp: Math.floor(Math.random() * 800) },
      { labelKey: currentWeek.toString(), xp: Math.floor(Math.random() * 800) },
    ];
    res.json(data);
  });

  // Gamification Logic Wrapper
  const calculateXP = (task: any) => {
    let baseXP = 10;
    if (task.priority === 'high') baseXP += 20;
    if (task.priority === 'medium') baseXP += 10;
    if (task.energyLevel === 'high') baseXP += 30; // Bonus for high energy stuff
    return baseXP;
  };

  // Rewards API
  app.get("/api/rewards", async (req, res) => {
    try {
      const allRewards = await storage.getAllRewards();
      const userId = req.query.userId as string;

      // Filter rewards: System rewards OR User's own rewards
      const visibleRewards = allRewards.filter(r => r.isSystem || (userId && r.userId === userId));

      // If userId is provided, check ownership of visible rewards
      if (userId) {
        const userRewards = await storage.getUserRewards(userId);
        const ownedIds = new Set(userRewards.map(ur => ur.rewardId));
        return res.json(visibleRewards.map(r => ({ ...r, owned: ownedIds.has(r.id) })));
      }
      res.json(visibleRewards);
    } catch (error) {
      res.status(500).json({ error: "Failed to fetch rewards" });
    }
  });

  app.post("/api/rewards/purchase", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    try {
      // 1. Get User & Reward
      const userId = (req.user as User).id;
      const { rewardId } = req.body;

      if (!rewardId) return res.status(400).json({ error: "Missing rewardId" });
      const user = await storage.getUser(userId); // Fetch user here
      const allRewards = await storage.getAllRewards();
      const reward = allRewards.find(r => r.id === rewardId);

      if (!user || !reward) return res.status(404).json({ error: "User or Reward not found" });

      // 2. Check Ownership (if one-time)
      // For now, allow multiple purchases unless type is 'feature_unlock'
      if (reward.type === 'feature_unlock') {
        const userRewards = await storage.getUserRewards(userId);
        if (userRewards.some(ur => ur.rewardId === rewardId)) {
          return res.status(400).json({ error: "Reward already owned" });
        }
      }

      // 3. Check Funds
      if (user.xp < reward.cost) {
        return res.status(400).json({ error: "Insufficient XP" });
      }

      // 4. Transaction
      const updatedUser = await storage.updateUserXP(userId, -reward.cost);
      await storage.createUserReward({ userId, rewardId });

      res.json({ success: true, user: updatedUser });
    } catch (e) {
      res.status(500).json({ error: "Purchase failed" });
    }
  });

  app.post("/api/rewards", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    try {
      const rewardData = {
        ...req.body,
        userId: (req.user as User).id,
        isSystem: (req.user as User).role === 'admin' && req.body.isSystem !== false,
      };

      // Force isSystem=false for non-admins
      if ((req.user as User).role !== 'admin') {
        rewardData.isSystem = false;
      }

      const reward = await storage.createReward(rewardData);
      res.json(reward);
    } catch (err) {
      res.status(500).json({ error: "Failed to create reward" });
    }
  });

  // User Gamification Endpoints
  app.get("/api/user/history", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    try {
      const history = await storage.getXpEvents((req.user as User).id);
      res.json(history);
    } catch (e) {
      res.status(500).json({ error: "Failed to fetch history" });
    }
  });

  app.get("/api/user/inventory", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    try {
      const userRewards = await storage.getUserRewards((req.user as User).id);
      // Join with rewards details
      const allRewards = await storage.getAllRewards();
      const inventory = userRewards.map(ur => {
        const reward = allRewards.find(r => r.id === ur.rewardId);
        return {
          ...ur,
          reward // Nested details
        };
      }).filter(item => item.reward); // Filter out any broken links
      res.json(inventory);
    } catch (e) {
      res.status(500).json({ error: "Failed to fetch inventory" });
    }
  });

  // --- Social & Leaderboard Routes ---

  app.get("/api/leaderboard", async (req, res) => {
    try {
      const users = await storage.getLeaderboard();
      // Return public info only
      const leaderboard = users.map(u => ({
        username: u.username,
        xp: u.xp,
        level: u.level,
        id: u.id // Needed? Maybe for linking profile
      }));
      res.json(leaderboard);
    } catch (e) {
      res.status(500).json({ error: "Failed to fetch leaderboard" });
    }
  });

  app.patch("/api/user/privacy", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    try {
      const { showOnLeaderboard, isSearchable, aiEnabled } = req.body;
      const updates: any = {};
      if (showOnLeaderboard !== undefined) updates.showOnLeaderboard = showOnLeaderboard;
      if (isSearchable !== undefined) updates.isSearchable = isSearchable;
      if (aiEnabled !== undefined) updates.aiEnabled = aiEnabled;

      const updated = await storage.updateUser((req.user as User).id, updates);
      res.json(updated);
    } catch (e) {
      res.status(500).json({ error: "Failed to update privacy settings" });
    }
  });

  app.patch("/api/user/profile", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    try {
      const { email } = req.body;
      if (!email || !email.includes('@')) return res.status(400).json({ error: "Invalid email" });

      const existing = await storage.getUserByEmail(email);
      if (existing && existing.id !== (req.user as User).id) {
        return res.status(400).json({ error: "Email already taken" });
      }

      const updated = await storage.updateUser((req.user as User).id, { email });
      res.json(updated);
    } catch (e) {
      res.status(500).json({ error: "Failed to update profile" });
    }
  });

  app.patch("/api/user/password", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    try {
      const { currentPassword, newPassword } = req.body;
      if (!currentPassword || !newPassword) return res.status(400).json({ error: "Missing fields" });

      const user = await storage.getUser((req.user as User).id);
      if (!user) return res.status(404).json({ error: "User not found" });

      const isValid = await comparePassword(currentPassword, user.password);
      if (!isValid) return res.status(400).json({ error: "Incorrect current password" });

      const hashedPassword = await hashPassword(newPassword);
      await storage.updateUser(user.id, { password: hashedPassword });

      res.json({ message: "Password updated" });
    } catch (e) {
      res.status(500).json({ error: "Failed to update password" });
    }
  });

  app.get("/api/users/search", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    const query = req.query.q as string;
    try {
      const users = await storage.searchUsers(query);
      // Filter out self
      const others = users.filter(u => u.id !== (req.user as User).id);
      res.json(others.map(u => ({ id: u.id, username: u.username })));
    } catch (e) {
      res.status(500).json({ error: "Search failed" });
    }
  });

  app.post("/api/tasks/:id/share", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    try {
      const { targetUserId } = req.body;
      const taskId = req.params.id;

      // Verify ownership
      const task = await storage.getTask(taskId);
      if (!task) return res.status(404).json({ error: "Task not found" });

      if (task.userId !== (req.user as User).id) {
        return res.status(403).json({ error: "Unauthorized" });
      }

      await storage.shareTask({
        taskId,
        sharedByUserId: (req.user as User).id,
        sharedWithUserId: targetUserId
      });
      res.json({ success: true });
    } catch (e) {
      res.status(500).json({ error: "Failed to share task" });
    }
  });

  app.get("/api/tasks/:id/shared-users", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    try {
      const taskId = req.params.id;
      // Verify ownership or access? Ideally only owner can see who else sees it.
      const task = await storage.getTask(taskId);
      if (!task) return res.status(404).json({ error: "Task not found" });

      if (task.userId !== (req.user as User).id) {
        return res.status(403).json({ error: "Unauthorized" });
      }

      const users = await storage.getTaskSharedUsers(taskId);
      // Return minimal info
      res.json(users.map(u => ({ id: u.id, username: u.username })));
    } catch (e) {
      res.status(500).json({ error: "Failed to fetch shared users" });
    }
  });

  app.delete("/api/tasks/:id/share/:userId", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    try {
      const taskId = req.params.id;
      const targetUserId = req.params.userId;

      // Verify ownership
      const task = await storage.getTask(taskId);
      if (!task) return res.status(404).json({ error: "Task not found" });

      if (task.userId !== (req.user as User).id) {
        return res.status(403).json({ error: "Unauthorized" });
      }

      const success = await storage.unshareTask(taskId, targetUserId);
      if (success) {
        res.json({ success: true });
      } else {
        res.status(404).json({ error: "Share not found" });
      }
    } catch (e) {
      res.status(500).json({ error: "Failed to unshare task" });
    }
  });

  app.post("/api/users/share-all", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    try {
      const { targetUserId } = req.body;
      await storage.shareAllTasks({
        ownerId: (req.user as User).id,
        viewerId: targetUserId
      });
      res.json({ success: true });
    } catch (e) {
      res.status(500).json({ error: "Failed to share all tasks" });
    }
  });


  const httpServer = createServer(app);


  // Storage needs to support Goal Update
  app.patch("/api/goals/:id", async (req, res) => {
    if (!req.isAuthenticated()) return res.sendStatus(401);
    try {
      const updated = await storage.updateGoal(req.params.id, req.body);
      res.json(updated);
    } catch (e) {
      res.status(500).json({ error: "Failed to update goal" });
    }
  });

  return httpServer;
}
