const request = require("supertest");
const express = require("express");
const usersRouter = require("../../../routes/users");

// Mock all dependencies
jest.mock("../../../models", () => ({
  User: {
    findByPk: jest.fn(),
    update: jest.fn(),
  },
  UserAddress: {
    findAll: jest.fn(),
    findByPk: jest.fn(),
    create: jest.fn(),
  },
}));

jest.mock("../../../middleware/auth", () => ({
  authenticateToken: jest.fn((req, res, next) => {
    req.user = {
      id: 1,
      update: jest.fn(),
    };
    next();
  }),
}));

jest.mock("../../../services/UserService", () => ({
  createUserAddress: jest.fn(),
  updateUserAddress: jest.fn(),
  deleteUserAddress: jest.fn(),
  updateProfile: jest.fn(),
}));

jest.mock("../../../utils/logger", () => ({
  info: jest.fn(),
  error: jest.fn(),
  warn: jest.fn(),
  withRequestId: jest.fn(() => ({
    info: jest.fn(),
    error: jest.fn(),
    warn: jest.fn(),
  })),
  sanitize: jest.fn((data) => data),
}));

const { User, UserAddress } = require("../../../models");
const userService = require("../../../services/UserService");

// Create express app with the router
const app = express();
app.use(express.json());
app.use("/users", usersRouter);

// Add error handler middleware
app.use((err, req, res, next) => {
  res.status(500).json({ error: err.message });
});

// Mock models
const mockUserFindByPk = User.findByPk;
const mockUserUpdate = User.update;
const mockUserAddressFindAll = UserAddress.findAll;
const mockUserAddressFindByPk = UserAddress.findByPk;
const mockUserAddressCreate = UserAddress.create;

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

  describe("GET /profile", () => {
    it("should get user profile for authenticated user", async () => {
      const mockUser = {
        id: 1,
        firstName: "John",
        lastName: "Doe",
        email: "john@example.com",
        phone: "555-1234",
        imageFilename: "profile.jpg",
      };

      mockUserFindByPk.mockResolvedValue(mockUser);

      const response = await request(app).get("/users/profile");

      expect(response.status).toBe(200);
      expect(response.body).toEqual(mockUser);
      expect(mockUserFindByPk).toHaveBeenCalledWith(1, {
        attributes: { exclude: ["password"] },
      });
    });

    it("should handle database errors", async () => {
      mockUserFindByPk.mockRejectedValue(new Error("Database error"));

      const response = await request(app).get("/users/profile");

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: "Database error" });
    });
  });

  describe("GET /addresses", () => {
    it("should get user addresses", async () => {
      const mockAddresses = [
        {
          id: 1,
          userId: 1,
          address1: "123 Main St",
          city: "New York",
          isPrimary: true,
        },
        {
          id: 2,
          userId: 1,
          address1: "456 Oak Ave",
          city: "Boston",
          isPrimary: false,
        },
      ];

      mockUserAddressFindAll.mockResolvedValue(mockAddresses);

      const response = await request(app).get("/users/addresses");

      expect(response.status).toBe(200);
      expect(response.body).toEqual(mockAddresses);
      expect(mockUserAddressFindAll).toHaveBeenCalledWith({
        where: { userId: 1 },
        order: [
          ["isPrimary", "DESC"],
          ["createdAt", "ASC"],
        ],
      });
    });

    it("should handle database errors", async () => {
      mockUserAddressFindAll.mockRejectedValue(new Error("Database error"));

      const response = await request(app).get("/users/addresses");

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: "Database error" });
    });
  });

  describe("POST /addresses", () => {
    it("should create a new address", async () => {
      const addressData = {
        address1: "789 Pine St",
        address2: "Apt 4B",
        city: "Chicago",
        state: "IL",
        zipCode: "60601",
        country: "USA",
      };

      const mockCreatedAddress = {
        id: 3,
        ...addressData,
        userId: 1,
      };

      userService.createUserAddress.mockResolvedValue(mockCreatedAddress);

      const response = await request(app)
        .post("/users/addresses")
        .send(addressData);

      expect(response.status).toBe(201);
      expect(response.body).toEqual(mockCreatedAddress);
      expect(userService.createUserAddress).toHaveBeenCalledWith(1, addressData);
    });

    it("should handle database errors during creation", async () => {
      userService.createUserAddress.mockRejectedValue(new Error("Database error"));

      const response = await request(app).post("/users/addresses").send({
        address1: "789 Pine St",
        city: "Chicago",
      });

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: "Database error" });
    });
  });

  describe("PUT /addresses/:id", () => {
    const mockAddress = {
      id: 1,
      userId: 1,
      address1: "123 Updated St",
      city: "Updated City",
    };

    it("should update user address", async () => {
      const updateData = {
        address1: "123 Updated St",
        city: "Updated City",
      };

      userService.updateUserAddress.mockResolvedValue(mockAddress);

      const response = await request(app)
        .put("/users/addresses/1")
        .send(updateData);

      expect(response.status).toBe(200);
      expect(response.body).toEqual(mockAddress);
      expect(userService.updateUserAddress).toHaveBeenCalledWith(1, "1", updateData);
    });

    it("should return 404 for non-existent address", async () => {
      userService.updateUserAddress.mockRejectedValue(new Error("Address not found"));

      const response = await request(app)
        .put("/users/addresses/999")
        .send({ address1: "Updated St" });

      expect(response.status).toBe(404);
      expect(response.body).toEqual({ error: "Address not found" });
    });

    it("should handle database errors", async () => {
      userService.updateUserAddress.mockRejectedValue(new Error("Database error"));

      const response = await request(app)
        .put("/users/addresses/1")
        .send({ address1: "Updated St" });

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: "Database error" });
    });
  });

  describe("DELETE /addresses/:id", () => {
    it("should delete user address", async () => {
      userService.deleteUserAddress.mockResolvedValue();

      const response = await request(app).delete("/users/addresses/1");

      expect(response.status).toBe(204);
      expect(userService.deleteUserAddress).toHaveBeenCalledWith(1, "1");
    });

    it("should return 404 for non-existent address", async () => {
      userService.deleteUserAddress.mockRejectedValue(new Error("Address not found"));

      const response = await request(app).delete("/users/addresses/999");

      expect(response.status).toBe(404);
      expect(response.body).toEqual({ error: "Address not found" });
    });

    it("should handle database errors", async () => {
      userService.deleteUserAddress.mockRejectedValue(new Error("Database error"));

      const response = await request(app).delete("/users/addresses/1");

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: "Database error" });
    });
  });

  describe("GET /availability", () => {
    it("should get user availability settings", async () => {
      const mockUser = {
        defaultAvailableAfter: "09:00",
        defaultAvailableBefore: "17:00",
        defaultSpecifyTimesPerDay: true,
        defaultWeeklyTimes: { monday: "09:00-17:00", tuesday: "10:00-16:00" },
      };

      mockUserFindByPk.mockResolvedValue(mockUser);

      const response = await request(app).get("/users/availability");

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        generalAvailableAfter: "09:00",
        generalAvailableBefore: "17:00",
        specifyTimesPerDay: true,
        weeklyTimes: { monday: "09:00-17:00", tuesday: "10:00-16:00" },
      });
      expect(mockUserFindByPk).toHaveBeenCalledWith(1, {
        attributes: [
          "defaultAvailableAfter",
          "defaultAvailableBefore",
          "defaultSpecifyTimesPerDay",
          "defaultWeeklyTimes",
        ],
      });
    });

    it("should handle database errors", async () => {
      mockUserFindByPk.mockRejectedValue(new Error("Database error"));

      const response = await request(app).get("/users/availability");

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: "Database error" });
    });
  });

  describe("PUT /availability", () => {
    it("should update user availability settings", async () => {
      const availabilityData = {
        generalAvailableAfter: "08:00",
        generalAvailableBefore: "18:00",
        specifyTimesPerDay: false,
        weeklyTimes: { monday: "08:00-18:00" },
      };

      mockUserUpdate.mockResolvedValue([1]);

      const response = await request(app)
        .put("/users/availability")
        .send(availabilityData);

      expect(response.status).toBe(200);
      expect(response.body).toEqual({
        message: "Availability updated successfully",
      });
      expect(mockUserUpdate).toHaveBeenCalledWith(
        {
          defaultAvailableAfter: "08:00",
          defaultAvailableBefore: "18:00",
          defaultSpecifyTimesPerDay: false,
          defaultWeeklyTimes: { monday: "08:00-18:00" },
        },
        {
          where: { id: 1 },
        }
      );
    });

    it("should handle database errors", async () => {
      mockUserUpdate.mockRejectedValue(new Error("Database error"));

      const response = await request(app).put("/users/availability").send({
        generalAvailableAfter: "08:00",
        generalAvailableBefore: "18:00",
      });

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: "Database error" });
    });
  });

  describe("GET /:id", () => {
    it("should get public user profile by ID", async () => {
      const mockUser = {
        id: 2,
        firstName: "Jane",
        lastName: "Smith",
        username: "janesmith",
        imageFilename: "jane.jpg",
      };

      mockUserFindByPk.mockResolvedValue(mockUser);

      const response = await request(app).get("/users/2");

      expect(response.status).toBe(200);
      expect(response.body).toEqual(mockUser);
      expect(mockUserFindByPk).toHaveBeenCalledWith("2", {
        attributes: { exclude: ["password", "email", "phone", "address"] },
      });
    });

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

      const response = await request(app).get("/users/999");

      expect(response.status).toBe(404);
      expect(response.body).toEqual({ error: "User not found" });
    });

    it("should handle database errors", async () => {
      mockUserFindByPk.mockRejectedValue(new Error("Database error"));

      const response = await request(app).get("/users/2");

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: "Database error" });
    });
  });

  describe("PUT /profile", () => {
    const mockUpdatedUser = {
      id: 1,
      firstName: "Updated",
      lastName: "User",
      email: "updated@example.com",
      phone: "555-9999",
    };

    it("should update user profile", async () => {
      const profileData = {
        firstName: "Updated",
        lastName: "User",
        email: "updated@example.com",
        phone: "555-9999",
        address1: "123 New St",
        city: "New City",
      };

      userService.updateProfile.mockResolvedValue(mockUpdatedUser);

      const response = await request(app)
        .put("/users/profile")
        .send(profileData);

      expect(response.status).toBe(200);
      expect(response.body).toEqual(mockUpdatedUser);
      expect(userService.updateProfile).toHaveBeenCalledWith(1, profileData);
    });

    it("should handle database errors", async () => {
      userService.updateProfile.mockRejectedValue(new Error("Database error"));

      const response = await request(app).put("/users/profile").send({
        firstName: "Test",
      });

      expect(response.status).toBe(500);
      expect(response.body).toEqual({ error: "Database error" });
    });
  });
});
