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

describe("SSE Routes", () => {
  let token;
  let userId;

  beforeAll(async () => {
    await couchdbService.initialize();
  });

  beforeEach(async () => {
    // Create a test user with unique email to avoid conflicts
    const timestamp = Date.now();
    const user = await User.create({
      name: "SSE Test User",
      username: `sseuser${timestamp}`,
      email: `sse${timestamp}@test.com`,
      password: "Password123!",
    });
    userId = user._id;

    // Generate token
    const payload = { user: { id: user._id } };
    token = jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: "1h" });

    // Clear SSE service state
    sseService.clients.clear();
    sseService.topics.clear();
  });

  afterAll(async () => {
    await couchdbService.shutdown();
    server.close();
  });

  describe("POST /api/sse/subscribe", () => {
    test("should require authentication", async () => {
      const res = await request(app)
        .post("/api/sse/subscribe")
        .send({ topics: ["test"] });

      expect(res.statusCode).toBe(401);
      expect(res.body.success).toBe(false);
    });

    test("should subscribe to topics with valid token", async () => {
      // First, add client to SSE service
      const mockRes = { write: jest.fn() };
      sseService.addClient(userId, mockRes);

      const res = await request(app)
        .post("/api/sse/subscribe")
        .set("x-auth-token", token)
        .send({ topics: ["events", "posts"] });

      expect(res.statusCode).toBe(200);
      expect(res.body.success).toBe(true);
      expect(res.body.topics).toEqual(["events", "posts"]);

      // Verify subscription in service
      const stats = sseService.getStats();
      expect(stats.topics).toHaveProperty("events");
      expect(stats.topics).toHaveProperty("posts");
    });

    test("should fail if user not connected to SSE stream", async () => {
      const res = await request(app)
        .post("/api/sse/subscribe")
        .set("x-auth-token", token)
        .send({ topics: ["events"] });

      expect(res.statusCode).toBe(400);
      expect(res.body.success).toBe(false);
    });

    test("should validate topics array", async () => {
      const res = await request(app)
        .post("/api/sse/subscribe")
        .set("x-auth-token", token)
        .send({ topics: "not-an-array" });

      expect(res.statusCode).toBe(400);
      expect(res.body.success).toBe(false);
    });
  });

  describe("POST /api/sse/unsubscribe", () => {
    test("should require authentication", async () => {
      const res = await request(app)
        .post("/api/sse/unsubscribe")
        .send({ topics: ["test"] });

      expect(res.statusCode).toBe(401);
      expect(res.body.success).toBe(false);
    });

    test("should unsubscribe from topics", async () => {
      // Setup: Add client and subscribe
      const mockRes = { write: jest.fn() };
      sseService.addClient(userId, mockRes);
      sseService.subscribe(userId, ["events", "posts"]);

      // Unsubscribe from one topic
      const res = await request(app)
        .post("/api/sse/unsubscribe")
        .set("x-auth-token", token)
        .send({ topics: ["events"] });

      expect(res.statusCode).toBe(200);
      expect(res.body.success).toBe(true);

      // Verify unsubscription
      const stats = sseService.getStats();
      expect(stats.topics).not.toHaveProperty("events");
      expect(stats.topics).toHaveProperty("posts");
    });

    test("should validate topics array", async () => {
      const res = await request(app)
        .post("/api/sse/unsubscribe")
        .set("x-auth-token", token)
        .send({ topics: null });

      expect(res.statusCode).toBe(400);
      expect(res.body.success).toBe(false);
    });
  });

  describe("SSE Service", () => {
    test("should add and remove clients", () => {
      const mockRes = { write: jest.fn() };
      
      sseService.addClient(userId, mockRes);
      let stats = sseService.getStats();
      expect(stats.totalClients).toBe(1);

      sseService.removeClient(userId);
      stats = sseService.getStats();
      expect(stats.totalClients).toBe(0);
    });

    test("should broadcast to all clients", () => {
      const mockRes1 = { write: jest.fn() };
      const mockRes2 = { write: jest.fn() };
      
      sseService.addClient("user1", mockRes1);
      sseService.addClient("user2", mockRes2);

      sseService.broadcast("testEvent", { message: "Hello" });

      expect(mockRes1.write).toHaveBeenCalledWith(
        'event: testEvent\ndata: {"message":"Hello"}\n\n'
      );
      expect(mockRes2.write).toHaveBeenCalledWith(
        'event: testEvent\ndata: {"message":"Hello"}\n\n'
      );
    });

    test("should broadcast to topic subscribers only", () => {
      const mockRes1 = { write: jest.fn() };
      const mockRes2 = { write: jest.fn() };
      
      sseService.addClient("user1", mockRes1);
      sseService.addClient("user2", mockRes2);
      sseService.subscribe("user1", ["events"]);

      sseService.broadcastToTopic("events", "eventUpdate", { id: 1 });

      expect(mockRes1.write).toHaveBeenCalled();
      expect(mockRes2.write).not.toHaveBeenCalled();
    });

    test("should send to specific user", () => {
      const mockRes = { write: jest.fn() };
      
      sseService.addClient(userId, mockRes);
      const success = sseService.sendToUser(userId, "notification", { text: "Test" });

      expect(success).toBe(true);
      expect(mockRes.write).toHaveBeenCalledWith(
        'event: notification\ndata: {"text":"Test"}\n\n'
      );
    });

    test("should return false when sending to non-existent user", () => {
      const success = sseService.sendToUser("nonexistent", "test", {});
      expect(success).toBe(false);
    });

    test("should get accurate stats", () => {
      const mockRes1 = { write: jest.fn() };
      const mockRes2 = { write: jest.fn() };
      
      sseService.addClient("user1", mockRes1);
      sseService.addClient("user2", mockRes2);
      sseService.subscribe("user1", ["events", "posts"]);
      sseService.subscribe("user2", ["events"]);

      const stats = sseService.getStats();
      
      expect(stats.totalClients).toBe(2);
      expect(stats.totalTopics).toBe(2);
      expect(stats.topics.events).toBe(2);
      expect(stats.topics.posts).toBe(1);
    });

    test("should clean up topics when last subscriber leaves", () => {
      const mockRes = { write: jest.fn() };
      
      sseService.addClient(userId, mockRes);
      sseService.subscribe(userId, ["events"]);
      
      let stats = sseService.getStats();
      expect(stats.totalTopics).toBe(1);

      sseService.removeClient(userId);
      stats = sseService.getStats();
      expect(stats.totalTopics).toBe(0);
    });
  });
});
