const jwt = require('jsonwebtoken');
const auth = require('../../middleware/auth');

describe('Auth Middleware', () => {
  let req, res, next;

  beforeEach(() => {
    req = {
      header: jest.fn(),
    };
    res = {
      status: jest.fn().mockReturnThis(),
      json: jest.fn(),
    };
    next = jest.fn();
  });

  describe('Valid Token', () => {
    it('should authenticate with valid token and call next()', () => {
      const userId = 'user123';
      const token = jwt.sign(
        { user: { id: userId } },
        process.env.JWT_SECRET,
        { expiresIn: 3600 }
      );

      req.header.mockReturnValue(token);

      auth(req, res, next);

      expect(req.user).toBeDefined();
      expect(req.user.id).toBe(userId);
      expect(next).toHaveBeenCalled();
      expect(res.status).not.toHaveBeenCalled();
      expect(res.json).not.toHaveBeenCalled();
    });

    it('should decode token with correct user data', () => {
      const userData = { id: 'user456' };
      const token = jwt.sign(
        { user: userData },
        process.env.JWT_SECRET,
        { expiresIn: 3600 }
      );

      req.header.mockReturnValue(token);

      auth(req, res, next);

      expect(req.user).toEqual(userData);
      expect(next).toHaveBeenCalled();
    });
  });

  describe('No Token', () => {
    it('should return 401 when no token is provided', () => {
      req.header.mockReturnValue(null);

      auth(req, res, next);

      expect(res.status).toHaveBeenCalledWith(401);
      expect(res.json).toHaveBeenCalledWith({
        success: false,
        msg: 'No token, authorization denied',
      });
      expect(next).not.toHaveBeenCalled();
    });

    it('should return 401 when token is undefined', () => {
      req.header.mockReturnValue(undefined);

      auth(req, res, next);

      expect(res.status).toHaveBeenCalledWith(401);
      expect(res.json).toHaveBeenCalledWith({
        success: false,
        msg: 'No token, authorization denied',
      });
      expect(next).not.toHaveBeenCalled();
    });

    it('should return 401 when token is empty string', () => {
      req.header.mockReturnValue('');

      auth(req, res, next);

      expect(res.status).toHaveBeenCalledWith(401);
      expect(res.json).toHaveBeenCalledWith({
        success: false,
        msg: 'No token, authorization denied',
      });
      expect(next).not.toHaveBeenCalled();
    });
  });

  describe('Invalid Token', () => {
    it('should return 401 with malformed token', () => {
      req.header.mockReturnValue('invalid-token-format');

      auth(req, res, next);

      expect(res.status).toHaveBeenCalledWith(401);
      expect(res.json).toHaveBeenCalledWith({
        success: false,
        msg: 'Token is not valid',
      });
      expect(next).not.toHaveBeenCalled();
    });

    it('should return 401 with expired token', () => {
      const expiredToken = jwt.sign(
        { user: { id: 'user123' } },
        process.env.JWT_SECRET,
        { expiresIn: -1 } // Already expired
      );

      req.header.mockReturnValue(expiredToken);

      auth(req, res, next);

      expect(res.status).toHaveBeenCalledWith(401);
      expect(res.json).toHaveBeenCalledWith({
        success: false,
        msg: 'Token is not valid',
      });
      expect(next).not.toHaveBeenCalled();
    });

    it('should return 401 with token signed with wrong secret', () => {
      const wrongToken = jwt.sign(
        { user: { id: 'user123' } },
        'wrong-secret',
        { expiresIn: 3600 }
      );

      req.header.mockReturnValue(wrongToken);

      auth(req, res, next);

      expect(res.status).toHaveBeenCalledWith(401);
      expect(res.json).toHaveBeenCalledWith({
        success: false,
        msg: 'Token is not valid',
      });
      expect(next).not.toHaveBeenCalled();
    });

    it('should return 401 with random string token', () => {
      req.header.mockReturnValue('randomstringnotajwt');

      auth(req, res, next);

      expect(res.status).toHaveBeenCalledWith(401);
      expect(res.json).toHaveBeenCalledWith({
        success: false,
        msg: 'Token is not valid',
      });
      expect(next).not.toHaveBeenCalled();
    });
  });

  describe('Header Name', () => {
    it('should check for "x-auth-token" header', () => {
      req.header.mockReturnValue(null);

      auth(req, res, next);

      expect(req.header).toHaveBeenCalledWith('x-auth-token');
    });
  });

  describe('Token Format', () => {
    it('should accept token with Bearer prefix if properly formatted', () => {
      // Note: The current middleware doesn't strip Bearer prefix
      // This test verifies current behavior
      const token = jwt.sign(
        { user: { id: 'user123' } },
        process.env.JWT_SECRET,
        { expiresIn: 3600 }
      );

      const bearerToken = `Bearer ${token}`;
      req.header.mockReturnValue(bearerToken);

      auth(req, res, next);

      // Will fail because middleware doesn't strip Bearer
      expect(res.status).toHaveBeenCalledWith(401);
      expect(res.json).toHaveBeenCalledWith({
        success: false,
        msg: 'Token is not valid',
      });
    });

    it('should accept token without Bearer prefix', () => {
      const token = jwt.sign(
        { user: { id: 'user123' } },
        process.env.JWT_SECRET,
        { expiresIn: 3600 }
      );

      req.header.mockReturnValue(token);

      auth(req, res, next);

      expect(next).toHaveBeenCalled();
      expect(res.status).not.toHaveBeenCalled();
    });
  });

  describe('Request Mutation', () => {
    it('should add user object to request', () => {
      const userId = 'user789';
      const token = jwt.sign(
        { user: { id: userId } },
        process.env.JWT_SECRET,
        { expiresIn: 3600 }
      );

      req.header.mockReturnValue(token);

      expect(req.user).toBeUndefined();

      auth(req, res, next);

      expect(req.user).toBeDefined();
      expect(req.user.id).toBe(userId);
    });

    it('should not modify request when token is invalid', () => {
      req.header.mockReturnValue('invalid');

      expect(req.user).toBeUndefined();

      auth(req, res, next);

      expect(req.user).toBeUndefined();
    });
  });

  describe('Multiple Token Formats', () => {
    it('should handle token with extra whitespace', () => {
      const token = jwt.sign(
        { user: { id: 'user123' } },
        process.env.JWT_SECRET,
        { expiresIn: 3600 }
      );

      // Token with leading/trailing spaces
      req.header.mockReturnValue(` ${token} `);

      auth(req, res, next);

      // Current middleware doesn't trim, so this will fail
      expect(res.status).toHaveBeenCalledWith(401);
    });
  });

  describe('Edge Cases', () => {
    it('should handle token with missing user data', () => {
      const token = jwt.sign(
        { someOtherData: 'value' },
        process.env.JWT_SECRET,
        { expiresIn: 3600 }
      );

      req.header.mockReturnValue(token);

      auth(req, res, next);

      // Middleware will accept token if valid, even without user field
      expect(next).toHaveBeenCalled();
      expect(req.user).toBeUndefined();
    });

    it('should handle very long tokens', () => {
      const largePayload = {
        user: {
          id: 'user123',
          data: 'x'.repeat(10000),
        },
      };

      const token = jwt.sign(largePayload, process.env.JWT_SECRET, {
        expiresIn: 3600,
      });

      req.header.mockReturnValue(token);

      auth(req, res, next);

      expect(next).toHaveBeenCalled();
      expect(req.user.id).toBe('user123');
    });
  });
});
