const request = require("supertest");
const { app } = require("../../server");
const couchdbService = require("../../services/couchdbService");
const jwt = require("jsonwebtoken");

// Mock couchdbService
jest.mock("../../services/couchdbService");

describe("Analytics API", () => {
  let authToken;
  let mockUser;

  beforeAll(() => {
    // Create a mock user and token
    mockUser = {
      _id: "user_123",
      type: "user",
      name: "Test User",
      email: "test@test.com",
      points: 100,
      isPremium: false,
      adoptedStreets: ["street_1"],
      completedTasks: ["task_1", "task_2"],
      posts: ["post_1"],
      events: ["event_1"],
      earnedBadges: [{ badgeId: "badge_1" }],
      stats: {
        streetsAdopted: 1,
        tasksCompleted: 2,
        postsCreated: 1,
        eventsParticipated: 1,
        badgesEarned: 1,
      },
    };

    authToken = jwt.sign({ id: mockUser._id }, process.env.JWT_SECRET || "test_secret");

    // Mock couchdbService methods
    couchdbService.find = jest.fn();
    couchdbService.findUserById = jest.fn();
    couchdbService.getDocument = jest.fn();
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  describe("GET /api/analytics/overview", () => {
    it("should return overview statistics without auth token", async () => {
      const mockUsers = [
        { type: "user", points: 100 },
        { type: "user", points: 150 },
      ];
      const mockStreets = [
        { type: "street", status: "adopted" },
        { type: "street", status: "available" },
        { type: "street", status: "adopted" },
      ];
      const mockTasks = [
        { type: "task", status: "completed" },
        { type: "task", status: "pending" },
      ];
      const mockEvents = [
        { type: "event", status: "upcoming" },
        { type: "event", status: "completed" },
      ];
      const mockPosts = [{ type: "post" }, { type: "post" }];

      couchdbService.find.mockImplementation(async (query) => {
        if (query.selector.type === "user") return mockUsers;
        if (query.selector.type === "street") return mockStreets;
        if (query.selector.type === "task") return mockTasks;
        if (query.selector.type === "event") return mockEvents;
        if (query.selector.type === "post") return mockPosts;
        return [];
      });

      const res = await request(app)
        .get("/api/analytics/overview")
        .set("x-auth-token", authToken);

      expect(res.statusCode).toBe(200);
      expect(res.body).toHaveProperty("overview");
      expect(res.body.overview).toHaveProperty("totalUsers", 2);
      expect(res.body.overview).toHaveProperty("totalStreets", 3);
      expect(res.body.overview).toHaveProperty("adoptedStreets", 2);
      expect(res.body.overview).toHaveProperty("totalTasks", 2);
      expect(res.body.overview).toHaveProperty("completedTasks", 1);
      expect(res.body.overview).toHaveProperty("totalEvents", 2);
      expect(res.body.overview).toHaveProperty("totalPosts", 2);
      expect(res.body.overview).toHaveProperty("totalPoints", 250);
      expect(res.body.overview).toHaveProperty("averagePointsPerUser", 125);
    });

    it("should filter by timeframe", async () => {
      const now = new Date();
      const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);

      const mockUsers = [{ type: "user", points: 100 }];
      const mockStreets = [{ type: "street", status: "adopted" }];
      const mockTasks = [
        { type: "task", status: "completed", createdAt: now.toISOString() },
      ];
      const mockEvents = [
        { type: "event", status: "upcoming", createdAt: now.toISOString() },
      ];
      const mockPosts = [{ type: "post", createdAt: now.toISOString() }];

      couchdbService.find.mockImplementation(async (query) => {
        if (query.selector.type === "user") return mockUsers;
        if (query.selector.type === "street") return mockStreets;
        if (query.selector.type === "task") return mockTasks;
        if (query.selector.type === "event") return mockEvents;
        if (query.selector.type === "post") return mockPosts;
        return [];
      });

      const res = await request(app)
        .get("/api/analytics/overview?timeframe=7d")
        .set("x-auth-token", authToken);

      expect(res.statusCode).toBe(200);
      expect(res.body.timeframe).toBe("7d");
    });

    it("should require authentication", async () => {
      const res = await request(app).get("/api/analytics/overview");

      expect(res.statusCode).toBe(401);
    });
  });

  describe("GET /api/analytics/user/:userId", () => {
    it("should return user-specific analytics", async () => {
      couchdbService.findUserById.mockResolvedValue(mockUser);
      couchdbService.getDocument.mockResolvedValue({
        _id: "street_1",
        name: "Main Street",
      });

      const mockTasks = [
        { type: "task", completedBy: { userId: mockUser._id }, createdAt: new Date().toISOString() },
      ];
      const mockPosts = [
        { type: "post", user: { userId: mockUser._id }, likesCount: 5, commentsCount: 3, createdAt: new Date().toISOString() },
      ];
      const mockEvents = [
        { type: "event", participants: [{ userId: mockUser._id }], createdAt: new Date().toISOString() },
      ];
      const mockTransactions = [
        { type: "point_transaction", user: { userId: mockUser._id }, amount: 50, createdAt: new Date().toISOString() },
        { type: "point_transaction", user: { userId: mockUser._id }, amount: -20, createdAt: new Date().toISOString() },
      ];

      couchdbService.find.mockImplementation(async (query) => {
        if (query.selector.type === "task") return mockTasks;
        if (query.selector.type === "post") return mockPosts;
        if (query.selector.type === "event") return mockEvents;
        if (query.selector.type === "point_transaction") return mockTransactions;
        return [];
      });

      const res = await request(app)
        .get(`/api/analytics/user/${mockUser._id}`)
        .set("x-auth-token", authToken);

      expect(res.statusCode).toBe(200);
      expect(res.body).toHaveProperty("user");
      expect(res.body.user.name).toBe("Test User");
      expect(res.body).toHaveProperty("stats");
      expect(res.body.stats).toHaveProperty("streetsAdopted");
      expect(res.body.stats).toHaveProperty("tasksCompleted");
      expect(res.body.stats).toHaveProperty("pointsEarned", 50);
      expect(res.body.stats).toHaveProperty("pointsSpent", 20);
      expect(res.body.stats).toHaveProperty("totalLikesReceived", 5);
      expect(res.body.stats).toHaveProperty("totalCommentsReceived", 3);
      expect(res.body).toHaveProperty("recentActivity");
    });

    it("should return 404 for non-existent user", async () => {
      couchdbService.findUserById.mockResolvedValue(null);

      const res = await request(app)
        .get("/api/analytics/user/invalid_user_id")
        .set("x-auth-token", authToken);

      expect(res.statusCode).toBe(404);
      expect(res.body.msg).toBe("User not found");
    });

    it("should require authentication", async () => {
      const res = await request(app).get(`/api/analytics/user/${mockUser._id}`);

      expect(res.statusCode).toBe(401);
    });
  });

  describe("GET /api/analytics/activity", () => {
    it("should return activity data grouped by day", async () => {
      const today = new Date().toISOString();
      const mockTasks = [{ type: "task", createdAt: today }];
      const mockPosts = [{ type: "post", createdAt: today }];
      const mockEvents = [{ type: "event", createdAt: today }];
      const mockStreets = [{ type: "street", status: "adopted", createdAt: today }];

      couchdbService.find.mockImplementation(async (query) => {
        if (query.selector.type === "task") return mockTasks;
        if (query.selector.type === "post") return mockPosts;
        if (query.selector.type === "event") return mockEvents;
        if (query.selector.type === "street") return mockStreets;
        return [];
      });

      const res = await request(app)
        .get("/api/analytics/activity?groupBy=day")
        .set("x-auth-token", authToken);

      expect(res.statusCode).toBe(200);
      expect(res.body).toHaveProperty("activity");
      expect(res.body).toHaveProperty("groupBy", "day");
      expect(res.body).toHaveProperty("summary");
      expect(Array.isArray(res.body.activity)).toBe(true);
    });

    it("should group activity by week", async () => {
      const today = new Date().toISOString();
      const mockTasks = [{ type: "task", createdAt: today }];
      const mockPosts = [];
      const mockEvents = [];
      const mockStreets = [];

      couchdbService.find.mockImplementation(async (query) => {
        if (query.selector.type === "task") return mockTasks;
        if (query.selector.type === "post") return mockPosts;
        if (query.selector.type === "event") return mockEvents;
        if (query.selector.type === "street") return mockStreets;
        return [];
      });

      const res = await request(app)
        .get("/api/analytics/activity?groupBy=week")
        .set("x-auth-token", authToken);

      expect(res.statusCode).toBe(200);
      expect(res.body.groupBy).toBe("week");
    });

    it("should group activity by month", async () => {
      const today = new Date().toISOString();
      const mockTasks = [{ type: "task", createdAt: today }];
      const mockPosts = [];
      const mockEvents = [];
      const mockStreets = [];

      couchdbService.find.mockImplementation(async (query) => {
        if (query.selector.type === "task") return mockTasks;
        if (query.selector.type === "post") return mockPosts;
        if (query.selector.type === "event") return mockEvents;
        if (query.selector.type === "street") return mockStreets;
        return [];
      });

      const res = await request(app)
        .get("/api/analytics/activity?groupBy=month")
        .set("x-auth-token", authToken);

      expect(res.statusCode).toBe(200);
      expect(res.body.groupBy).toBe("month");
    });

    it("should require authentication", async () => {
      const res = await request(app).get("/api/analytics/activity");

      expect(res.statusCode).toBe(401);
    });
  });

  describe("GET /api/analytics/top-contributors", () => {
    it("should return top contributors by points", async () => {
      const mockUsers = [
        {
          _id: "user_1",
          name: "User 1",
          email: "user1@test.com",
          points: 500,
          stats: { tasksCompleted: 10, postsCreated: 5, streetsAdopted: 2 },
          earnedBadges: [],
        },
        {
          _id: "user_2",
          name: "User 2",
          email: "user2@test.com",
          points: 300,
          stats: { tasksCompleted: 7, postsCreated: 3, streetsAdopted: 1 },
          earnedBadges: [],
        },
      ];

      couchdbService.find.mockResolvedValue(mockUsers);

      const res = await request(app)
        .get("/api/analytics/top-contributors?limit=5")
        .set("x-auth-token", authToken);

      expect(res.statusCode).toBe(200);
      expect(res.body).toHaveProperty("contributors");
      expect(res.body.contributors.length).toBeLessThanOrEqual(5);
      expect(res.body.metric).toBe("points");
      // Verify sorted by score descending
      if (res.body.contributors.length > 1) {
        expect(res.body.contributors[0].score).toBeGreaterThanOrEqual(
          res.body.contributors[1].score
        );
      }
    });

    it("should return top contributors by tasks", async () => {
      const mockUsers = [
        {
          _id: "user_1",
          name: "User 1",
          email: "user1@test.com",
          points: 500,
          stats: { tasksCompleted: 10, postsCreated: 5, streetsAdopted: 2 },
          earnedBadges: [],
        },
      ];

      couchdbService.find.mockResolvedValue(mockUsers);

      const res = await request(app)
        .get("/api/analytics/top-contributors?metric=tasks")
        .set("x-auth-token", authToken);

      expect(res.statusCode).toBe(200);
      expect(res.body.metric).toBe("tasks");
    });

    it("should return top contributors by posts", async () => {
      const mockUsers = [
        {
          _id: "user_1",
          name: "User 1",
          email: "user1@test.com",
          points: 500,
          stats: { tasksCompleted: 10, postsCreated: 5, streetsAdopted: 2 },
          earnedBadges: [],
        },
      ];

      couchdbService.find.mockResolvedValue(mockUsers);

      const res = await request(app)
        .get("/api/analytics/top-contributors?metric=posts")
        .set("x-auth-token", authToken);

      expect(res.statusCode).toBe(200);
      expect(res.body.metric).toBe("posts");
    });

    it("should return top contributors by streets", async () => {
      const mockUsers = [
        {
          _id: "user_1",
          name: "User 1",
          email: "user1@test.com",
          points: 500,
          stats: { tasksCompleted: 10, postsCreated: 5, streetsAdopted: 2 },
          earnedBadges: [],
        },
      ];

      couchdbService.find.mockResolvedValue(mockUsers);

      const res = await request(app)
        .get("/api/analytics/top-contributors?metric=streets")
        .set("x-auth-token", authToken);

      expect(res.statusCode).toBe(200);
      expect(res.body.metric).toBe("streets");
    });

    it("should require authentication", async () => {
      const res = await request(app).get("/api/analytics/top-contributors");

      expect(res.statusCode).toBe(401);
    });
  });

  describe("GET /api/analytics/street-stats", () => {
    it("should return street adoption and task statistics", async () => {
      const mockStreets = [
        { type: "street", status: "adopted" },
        { type: "street", status: "available" },
        { type: "street", status: "adopted" },
      ];
      const mockTasks = [
        {
          type: "task",
          status: "completed",
          street: { streetId: "street_1", name: "Main St" },
        },
        {
          type: "task",
          status: "completed",
          street: { streetId: "street_1", name: "Main St" },
        },
        { type: "task", status: "pending", street: { streetId: "street_2", name: "Oak St" } },
      ];

      couchdbService.find.mockImplementation(async (query) => {
        if (query.selector.type === "street") return mockStreets;
        if (query.selector.type === "task") return mockTasks;
        return [];
      });

      const res = await request(app)
        .get("/api/analytics/street-stats")
        .set("x-auth-token", authToken);

      expect(res.statusCode).toBe(200);
      expect(res.body).toHaveProperty("adoption");
      expect(res.body.adoption).toHaveProperty("totalStreets", 3);
      expect(res.body.adoption).toHaveProperty("adoptedStreets", 2);
      expect(res.body.adoption).toHaveProperty("availableStreets", 1);
      expect(res.body.adoption).toHaveProperty("adoptionRate");
      expect(res.body).toHaveProperty("tasks");
      expect(res.body.tasks).toHaveProperty("totalTasks", 3);
      expect(res.body.tasks).toHaveProperty("completedTasks", 2);
      expect(res.body.tasks).toHaveProperty("pendingTasks", 1);
      expect(res.body.tasks).toHaveProperty("completionRate");
      expect(res.body).toHaveProperty("topStreets");
      expect(Array.isArray(res.body.topStreets)).toBe(true);
    });

    it("should calculate correct adoption rate", async () => {
      const mockStreets = [
        { type: "street", status: "adopted" },
        { type: "street", status: "adopted" },
        { type: "street", status: "available" },
        { type: "street", status: "available" },
      ];
      const mockTasks = [];

      couchdbService.find.mockImplementation(async (query) => {
        if (query.selector.type === "street") return mockStreets;
        if (query.selector.type === "task") return mockTasks;
        return [];
      });

      const res = await request(app)
        .get("/api/analytics/street-stats")
        .set("x-auth-token", authToken);

      expect(res.statusCode).toBe(200);
      expect(res.body.adoption.adoptionRate).toBe(50.0);
    });

    it("should calculate correct task completion rate", async () => {
      const mockStreets = [];
      const mockTasks = [
        { type: "task", status: "completed" },
        { type: "task", status: "completed" },
        { type: "task", status: "completed" },
        { type: "task", status: "pending" },
      ];

      couchdbService.find.mockImplementation(async (query) => {
        if (query.selector.type === "street") return mockStreets;
        if (query.selector.type === "task") return mockTasks;
        return [];
      });

      const res = await request(app)
        .get("/api/analytics/street-stats")
        .set("x-auth-token", authToken);

      expect(res.statusCode).toBe(200);
      expect(res.body.tasks.completionRate).toBe(75.0);
    });

    it("should require authentication", async () => {
      const res = await request(app).get("/api/analytics/street-stats");

      expect(res.statusCode).toBe(401);
    });
  });
});
