// Note: CouchDB mocking is handled in jest.preSetup.js
const request = require("supertest");
const express = require("express");
const jwt = require("jsonwebtoken");
const User = require("../models/User");
const Task = require("../models/Task");
const Street = require("../models/Street");

// Create test app with gamification routes
const createTestApp = () => {
  const app = express();
  app.use(express.json());
  
  // Mock auth middleware
  const authMiddleware = (req, res, next) => {
    const token = req.header("x-auth-token");
    if (!token) {
      return res.status(401).json({ msg: "No token, authorization denied" });
    }
    
    try {
      const decoded = jwt.verify(token, process.env.JWT_SECRET || "test_secret");
      req.user = decoded.user;
      next();
    } catch (err) {
      res.status(401).json({ msg: "Token is not valid" });
    }
  };
  
  // Mock gamification routes
  app.post("/api/tasks/complete", authMiddleware, (req, res) => {
    const { taskId } = req.body;
    
    if (!taskId) {
      return res.status(400).json({ msg: "Task ID is required" });
    }
    
    // Mock task completion with points
    const points = Math.floor(Math.random() * 50) + 10; // 10-60 points
    
    res.json({
      success: true,
      points,
      message: `Task completed! You earned ${points} points.`,
      newTotal: 100 + points
    });
  });
  
  app.get("/api/users/points", authMiddleware, (req, res) => {
    // Mock user points data
    res.json({
      points: 100,
      level: 5,
      stats: {
        streetsAdopted: 1,
        tasksCompleted: 1,
        postsCreated: 1,
        eventsParticipated: 1,
        badgesEarned: 1
      },
      recentActivity: [
        {
          type: "task_completed",
          points: 25,
          description: "Completed street cleaning",
          timestamp: new Date().toISOString()
        }
      ]
    });
  });
  
  app.get("/api/users/leaderboard", authMiddleware, (req, res) => {
    // Mock leaderboard
    const leaderboard = [
      { _id: "user1", name: "User 1", points: 500, rank: 1 },
      { _id: "user2", name: "User 2", points: 400, rank: 2 },
      { _id: "user3", name: "User 3", points: 300, rank: 3 }
    ];
    
    res.json(leaderboard);
  });
  
  app.post("/api/users/award-badge", authMiddleware, (req, res) => {
    const { badgeType } = req.body;
    
    if (!badgeType) {
      return res.status(400).json({ msg: "Badge type is required" });
    }
    
    res.json({
      success: true,
      badge: {
        type: badgeType,
        name: "Test Badge",
        description: "Awarded for testing",
        icon: "test-icon",
        awardedAt: new Date().toISOString()
      }
    });
  });
  
  app.post("/api/streets/adopt", authMiddleware, (req, res) => {
    const { streetId } = req.body;
    
    if (!streetId) {
      return res.status(400).json({ msg: "Street ID is required" });
    }
    
    res.json({
      success: true,
      points: 50,
      message: "Street adopted! You earned 50 points.",
      street: {
        _id: streetId,
        adoptedBy: req.user.id,
        adoptionDate: new Date().toISOString()
      }
    });
  });
  
  app.post("/api/events/join", authMiddleware, (req, res) => {
    const { eventId } = req.body;
    
    if (!eventId) {
      return res.status(400).json({ msg: "Event ID is required" });
    }
    
    res.json({
      success: true,
      points: 15,
      message: "Joined event! You earned 15 points.",
      event: {
        _id: eventId,
        participants: [req.user.id],
        joinDate: new Date().toISOString()
      }
    });
  });
  
  app.get("/api/users/achievements", authMiddleware, (req, res) => {
    // Mock achievements
    res.json({
      achievements: [
        {
          _id: "achievement1",
          name: "First Street",
          description: "Adopt your first street",
          icon: "street-icon",
          unlocked: true,
          unlockedAt: new Date().toISOString()
        },
        {
          _id: "achievement2", 
          name: "Task Master",
          description: "Complete 10 tasks",
          icon: "task-icon",
          unlocked: false,
          progress: 7,
          total: 10
        }
      ],
      totalAchievements: 10,
      unlockedAchievements: 3
    });
  });
  
  // Global error handler
  app.use((err, req, res, next) => {
    console.error(err.message);
    res.status(500).json({ msg: "Server error" });
  });
  
  return app;
};

describe("Gamification System", () => {
  let app;
  let testUser;
  let authToken;

  beforeAll(() => {
    app = createTestApp();
    
    // Create mock test user
    testUser = {
      _id: "test_user_123",
      name: "Test User",
      email: "test@example.com"
    };

    // Generate auth token
    authToken = jwt.sign(
      { user: { id: testUser._id } },
      process.env.JWT_SECRET || "test_secret"
    );
  });

  beforeEach(() => {
    // Reset mocks before each test
  });

  describe("Task Completion Rewards", () => {
    test("should award points for completing a task", async () => {
      const response = await request(app)
        .post("/api/tasks/complete")
        .set("x-auth-token", authToken)
        .send({ taskId: "task_123" })
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.points).toBeGreaterThan(0);
      expect(response.body.message).toContain("earned");
      expect(response.body.newTotal).toBeGreaterThan(100);
    });

    test("should require task ID for completion", async () => {
      const response = await request(app)
        .post("/api/tasks/complete")
        .set("x-auth-token", authToken)
        .send({})
        .expect(400);

      expect(response.body.msg).toContain("Task ID is required");
    });

    test("should require authentication for task completion", async () => {
      const response = await request(app)
        .post("/api/tasks/complete")
        .send({ taskId: "task_123" })
        .expect(401);

      expect(response.body.msg).toContain("authorization denied");
    });
  });

  describe("User Points and Stats", () => {
    test("should retrieve user points and statistics", async () => {
      const response = await request(app)
        .get("/api/users/points")
        .set("x-auth-token", authToken)
        .expect(200);

      expect(response.body.points).toBe(100);
      expect(response.body.level).toBe(5);
      expect(response.body.stats).toBeDefined();
      expect(response.body.stats.streetsAdopted).toBe(1);
      expect(response.body.stats.tasksCompleted).toBe(1);
      expect(response.body.recentActivity).toBeDefined();
      expect(Array.isArray(response.body.recentActivity)).toBe(true);
    });

    test("should require authentication for points retrieval", async () => {
      const response = await request(app)
        .get("/api/users/points")
        .expect(401);

      expect(response.body.msg).toContain("authorization denied");
    });
  });

  describe("Leaderboard System", () => {
    test("should retrieve leaderboard with ranked users", async () => {
      const response = await request(app)
        .get("/api/users/leaderboard")
        .set("x-auth-token", authToken)
        .expect(200);

      expect(Array.isArray(response.body)).toBe(true);
      expect(response.body.length).toBeGreaterThan(0);
      
      // Check that users are properly ranked
      response.body.forEach((user, index) => {
        expect(user.rank).toBe(index + 1);
        expect(user.points).toBeGreaterThan(0);
        expect(user.name).toBeDefined();
      });
      
      // Check that leaderboard is sorted by points (descending)
      for (let i = 1; i < response.body.length; i++) {
        expect(response.body[i-1].points).toBeGreaterThanOrEqual(response.body[i].points);
      }
    });

    test("should require authentication for leaderboard access", async () => {
      const response = await request(app)
        .get("/api/users/leaderboard")
        .expect(401);

      expect(response.body.msg).toContain("authorization denied");
    });
  });

  describe("Badge System", () => {
    test("should award badge to user", async () => {
      const response = await request(app)
        .post("/api/users/award-badge")
        .set("x-auth-token", authToken)
        .send({ badgeType: "first_street" })
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.badge).toBeDefined();
      expect(response.body.badge.type).toBe("first_street");
      expect(response.body.badge.name).toBe("Test Badge");
      expect(response.body.badge.awardedAt).toBeDefined();
    });

    test("should require badge type for awarding", async () => {
      const response = await request(app)
        .post("/api/users/award-badge")
        .set("x-auth-token", authToken)
        .send({})
        .expect(400);

      expect(response.body.msg).toContain("Badge type is required");
    });

    test("should require authentication for badge awarding", async () => {
      const response = await request(app)
        .post("/api/users/award-badge")
        .send({ badgeType: "first_street" })
        .expect(401);

      expect(response.body.msg).toContain("authorization denied");
    });
  });

  describe("Street Adoption Rewards", () => {
    test("should award points for street adoption", async () => {
      const response = await request(app)
        .post("/api/streets/adopt")
        .set("x-auth-token", authToken)
        .send({ streetId: "street_123" })
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.points).toBe(50);
      expect(response.body.message).toContain("earned 50 points");
      expect(response.body.street).toBeDefined();
      expect(response.body.street.adoptedBy).toBe(testUser._id);
    });

    test("should require street ID for adoption", async () => {
      const response = await request(app)
        .post("/api/streets/adopt")
        .set("x-auth-token", authToken)
        .send({})
        .expect(400);

      expect(response.body.msg).toContain("Street ID is required");
    });
  });

  describe("Event Participation Rewards", () => {
    test("should award points for joining events", async () => {
      const response = await request(app)
        .post("/api/events/join")
        .set("x-auth-token", authToken)
        .send({ eventId: "event_123" })
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.points).toBe(15);
      expect(response.body.message).toContain("earned 15 points");
      expect(response.body.event).toBeDefined();
      expect(response.body.event.participants).toContain(testUser._id);
    });

    test("should require event ID for joining", async () => {
      const response = await request(app)
        .post("/api/events/join")
        .set("x-auth-token", authToken)
        .send({})
        .expect(400);

      expect(response.body.msg).toContain("Event ID is required");
    });
  });

  describe("Achievements System", () => {
    test("should retrieve user achievements", async () => {
      const response = await request(app)
        .get("/api/users/achievements")
        .set("x-auth-token", authToken)
        .expect(200);

      expect(response.body.achievements).toBeDefined();
      expect(Array.isArray(response.body.achievements)).toBe(true);
      expect(response.body.totalAchievements).toBe(10);
      expect(response.body.unlockedAchievements).toBe(3);
      
      // Check achievement structure
      response.body.achievements.forEach(achievement => {
        expect(achievement._id).toBeDefined();
        expect(achievement.name).toBeDefined();
        expect(achievement.description).toBeDefined();
        expect(achievement.icon).toBeDefined();
        expect(typeof achievement.unlocked).toBe('boolean');
        
        if (achievement.unlocked) {
          expect(achievement.unlockedAt).toBeDefined();
        } else {
          expect(achievement.progress).toBeDefined();
          expect(achievement.total).toBeDefined();
        }
      });
    });

    test("should require authentication for achievements retrieval", async () => {
      const response = await request(app)
        .get("/api/users/achievements")
        .expect(401);

      expect(response.body.msg).toContain("authorization denied");
    });
  });

  describe("Gamification Consistency", () => {
    test("should maintain consistent point calculations", async () => {
      // Complete multiple actions and verify point consistency
      const taskResponse = await request(app)
        .post("/api/tasks/complete")
        .set("x-auth-token", authToken)
        .send({ taskId: "task_123" });

      const streetResponse = await request(app)
        .post("/api/streets/adopt")
        .set("x-auth-token", authToken)
        .send({ streetId: "street_123" });

      const eventResponse = await request(app)
        .post("/api/events/join")
        .set("x-auth-token", authToken)
        .send({ eventId: "event_123" });

      // Verify all responses have proper point awards
      expect(taskResponse.body.points).toBeGreaterThan(0);
      expect(streetResponse.body.points).toBe(50);
      expect(eventResponse.body.points).toBe(15);
      
      // Verify all have success messages
      expect(taskResponse.body.message).toContain("earned");
      expect(streetResponse.body.message).toContain("earned");
      expect(eventResponse.body.message).toContain("earned");
    });

    test("should handle concurrent gamification actions", async () => {
      // Test concurrent requests
      const actions = [
        request(app)
          .post("/api/tasks/complete")
          .set("x-auth-token", authToken)
          .send({ taskId: "task_1" }),
        request(app)
          .post("/api/tasks/complete")
          .set("x-auth-token", authToken)
          .send({ taskId: "task_2" }),
        request(app)
          .post("/api/events/join")
          .set("x-auth-token", authToken)
          .send({ eventId: "event_1" })
      ];

      const responses = await Promise.all(actions);
      
      // All should succeed
      responses.forEach(response => {
        expect(response.status).toBe(200);
        expect(response.body.success).toBe(true);
        expect(response.body.points).toBeGreaterThan(0);
      });
    });
  });
});