const request = require("supertest");
const express = require("express");

// Mock CouchDB service before importing routes
jest.mock("../../services/couchdbService", () => ({
  initialize: jest.fn().mockResolvedValue(true),
  create: jest.fn(),
  getById: jest.fn(),
  find: jest.fn(),
  createDocument: jest.fn(),
  updateDocument: jest.fn(),
  deleteDocument: jest.fn(),
  getDocument: jest.fn(),
}));

// Mock logger to avoid console output during tests
jest.mock("../../utils/logger", () => ({
  info: jest.fn(),
  error: jest.fn(),
  warn: jest.fn(),
  debug: jest.fn(),
}));

// Mock auth middleware
jest.mock("../../middleware/auth", () => (req, res, next) => {
  req.user = { id: "test-user-id" };
  next();
});

const aiRoute = require("../../routes/ai");
const aiService = require("../../services/aiService");
const Street = require("../../models/Street");
const Task = require("../../models/Task");
const couchdbService = require("../../services/couchdbService");

// Setup Express app for testing
const app = express();
app.use(express.json());
app.use("/api/ai", aiRoute);

describe("AI Routes", () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  describe("GET /api/ai/status", () => {
    it("should return AI service status", async () => {
      const response = await request(app)
        .get("/api/ai/status")
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.data).toHaveProperty("configured");
      expect(response.body.data).toHaveProperty("status");
    });
  });

  describe("GET /api/ai/task-suggestions", () => {
    it("should return task suggestions without streetId", async () => {
      const response = await request(app)
        .get("/api/ai/task-suggestions")
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.data).toBeInstanceOf(Array);
      expect(response.body.data.length).toBeGreaterThan(0);
      expect(response.body.meta).toHaveProperty("source");
      expect(response.body.meta).toHaveProperty("aiConfigured");
      
      // Check suggestion structure
      const suggestion = response.body.data[0];
      expect(suggestion).toHaveProperty("description");
      expect(suggestion).toHaveProperty("details");
      expect(suggestion).toHaveProperty("estimatedTime");
      expect(suggestion).toHaveProperty("difficulty");
      expect(suggestion).toHaveProperty("priority");
      expect(suggestion).toHaveProperty("source");
    });

    it("should return task suggestions with custom count", async () => {
      const response = await request(app)
        .get("/api/ai/task-suggestions?count=3")
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.data).toBeInstanceOf(Array);
      expect(response.body.data.length).toBe(3);
    });

    it("should validate count parameter maximum", async () => {
      const response = await request(app)
        .get("/api/ai/task-suggestions?count=15")
        .expect(400);

      expect(response.body.success).toBe(false);
      expect(response.body.errors).toBeInstanceOf(Array);
    });

    it("should validate count is a positive number", async () => {
      const response = await request(app)
        .get("/api/ai/task-suggestions?count=0")
        .expect(400);

      expect(response.body.success).toBe(false);
      expect(response.body.errors).toBeInstanceOf(Array);
    });

    it("should return task suggestions for specific street", async () => {
      const streetId = "test-street-123";
      const streetData = {
        _id: streetId,
        _rev: "1-abc",
        type: "street",
        name: "Test Avenue",
        location: {
          type: "Point",
          coordinates: [-122.4194, 37.7749]
        },
        status: "available",
        stats: {
          completedTasksCount: 0,
          reportsCount: 0,
          openReportsCount: 0
        }
      };

      // Mock street lookup
      couchdbService.getDocument.mockResolvedValueOnce(streetData);

      // Mock task lookup
      couchdbService.find.mockResolvedValueOnce([
        {
          _id: "task-1",
          type: "task",
          street: { streetId, name: "Test Avenue" },
          description: "Remove graffiti from wall",
          status: "completed",
          completedAt: new Date().toISOString()
        },
        {
          _id: "task-2",
          type: "task",
          street: { streetId, name: "Test Avenue" },
          description: "Clear storm drains",
          status: "completed",
          completedAt: new Date().toISOString()
        }
      ]);

      const response = await request(app)
        .get(`/api/ai/task-suggestions?streetId=${streetId}`)
        .expect(200);

      expect(response.body.success).toBe(true);
      expect(response.body.data).toBeInstanceOf(Array);
      expect(response.body.data.length).toBeGreaterThan(0);
      
      // Check that street name is included
      const suggestion = response.body.data[0];
      expect(suggestion.street).toBe("Test Avenue");
    });

    it("should return 404 for non-existent street", async () => {
      // Mock street not found
      couchdbService.getDocument.mockResolvedValueOnce(null);

      const response = await request(app)
        .get("/api/ai/task-suggestions?streetId=nonexistent-id")
        .expect(404);

      expect(response.body.success).toBe(false);
      expect(response.body.msg).toBe("Street not found");
    });

    it("should include warning when OpenAI is not configured", async () => {
      const response = await request(app)
        .get("/api/ai/task-suggestions")
        .expect(200);

      expect(response.body.success).toBe(true);
      
      if (!aiService.isAvailable()) {
        expect(response.body.warning).toBeDefined();
        expect(response.body.warning).toContain("OpenAI API not configured");
      }
    });
  });

  describe("AI Service Integration", () => {
    it("should generate suggestions with proper structure", async () => {
      const suggestions = await aiService.generateTaskSuggestions({
        streetName: "Main Street",
        numberOfSuggestions: 5
      });

      expect(suggestions).toBeInstanceOf(Array);
      expect(suggestions.length).toBe(5);
      
      suggestions.forEach(suggestion => {
        expect(suggestion).toHaveProperty("street");
        expect(suggestion).toHaveProperty("description");
        expect(suggestion).toHaveProperty("details");
        expect(suggestion).toHaveProperty("estimatedTime");
        expect(suggestion).toHaveProperty("difficulty");
        expect(suggestion).toHaveProperty("priority");
        expect(suggestion).toHaveProperty("source");
        
        expect(["easy", "moderate", "hard"]).toContain(suggestion.difficulty);
        expect(["low", "medium", "high"]).toContain(suggestion.priority);
      });
    });

    it("should respect numberOfSuggestions parameter", async () => {
      const suggestions1 = await aiService.generateTaskSuggestions({
        streetName: "Oak Avenue",
        numberOfSuggestions: 3
      });

      const suggestions2 = await aiService.generateTaskSuggestions({
        streetName: "Elm Street",
        numberOfSuggestions: 7
      });

      expect(suggestions1.length).toBe(3);
      expect(suggestions2.length).toBe(7);
    });

    it("should include street name in suggestions", async () => {
      const streetName = "Custom Street Name";
      const suggestions = await aiService.generateTaskSuggestions({
        streetName,
        numberOfSuggestions: 3
      });

      suggestions.forEach(suggestion => {
        expect(suggestion.street).toBe(streetName);
      });
    });

    it("should return mock data when OpenAI is not configured", async () => {
      const suggestions = await aiService.generateTaskSuggestions({
        streetName: "Test Street",
        numberOfSuggestions: 5
      });

      expect(suggestions).toBeInstanceOf(Array);
      expect(suggestions.length).toBe(5);
      
      // Mock suggestions should have source: "mock"
      if (!aiService.isAvailable()) {
        expect(suggestions[0].source).toBe("mock");
      }
    });
  });
});
