import { createToken, verifyToken, extractTokenFromHeader } from '@goodgo/auth-sdk';
import { Request, Response } from 'express';

import { authenticate, authorize, hasRole, hasAnyRole, isAuthenticated } from '../auth.middleware';

// EN: Mock auth-sdk functions
// VI: Mock các function của auth-sdk
jest.mock('@goodgo/auth-sdk', () => ({
  createToken: jest.fn(),
  verifyToken: jest.fn(),
  extractTokenFromHeader: jest.fn(),
}));

// EN: Setup createToken mock to return fake tokens
// VI: Setup mock createToken để trả về fake tokens
(createToken as jest.Mock)
  .mockReturnValueOnce('fake-user-token')
  .mockReturnValueOnce('fake-admin-token');

// EN: Mock express types
// VI: Mock express types
const mockNext = jest.fn();
const mockJson = jest.fn();
const mockStatus = jest.fn().mockReturnValue({ json: mockJson });

// EN: Helper to create mock request/response
// VI: Helper để tạo mock request/response
const createMockReq = (overrides: any = {}): Partial<Request> => ({
  headers: {},
  ...overrides,
});

const createMockRes = (): Partial<Response> => ({
  status: mockStatus,
  json: mockJson,
});

describe('Authentication Middleware', () => {
  const jwtSecret = 'test-secret-key';
  const validToken = 'fake-user-token';
  const adminToken = 'fake-admin-token';

  beforeEach(() => {
    jest.clearAllMocks();

    // EN: Setup default mock implementations
    // VI: Setup implementations mock mặc định
    (extractTokenFromHeader as jest.Mock).mockImplementation((header) => {
      if (!header || typeof header !== 'string') return null;
      const parts = header.split(' ');
      return parts.length === 2 && parts[0] === 'Bearer' ? parts[1] : null;
    });

    (verifyToken as jest.Mock).mockImplementation((token, _options) => {
      if (token === 'fake-user-token') {
        return {
          userId: 'user-123',
          email: 'user@example.com',
          role: 'user',
          iat: Math.floor(Date.now() / 1000),
          exp: Math.floor(Date.now() / 1000) + 3600,
        };
      }
      if (token === 'fake-admin-token') {
        return {
          userId: 'admin-123',
          email: 'admin@example.com',
          role: 'admin',
          iat: Math.floor(Date.now() / 1000),
          exp: Math.floor(Date.now() / 1000) + 3600,
        };
      }
      throw new Error('Invalid token');
    });
  });

  describe('authenticate', () => {
    it('should authenticate valid token and attach user to request', () => {
      // EN: Arrange
      // VI: Chuẩn bị
      const mockReq = createMockReq({
        headers: { authorization: `Bearer ${validToken}` },
      });
      const mockRes = createMockRes();

      // EN: Act
      // VI: Thực hiện
      const middleware = authenticate({ secret: jwtSecret });
      middleware(mockReq as Request, mockRes as Response, mockNext);

      // EN: Assert
      // VI: Kiểm tra
      expect(mockNext).toHaveBeenCalled();
      expect(mockReq.user).toEqual({
        userId: 'user-123',
        email: 'user@example.com',
        role: 'user',
        iat: expect.any(Number),
        exp: expect.any(Number),
      });
      expect(mockStatus).not.toHaveBeenCalled();
    });

    it('should return 401 for missing authorization header', () => {
      // EN: Arrange
      // VI: Chuẩn bị
      const mockReq = createMockReq();
      const mockRes = createMockRes();

      // EN: Act
      // VI: Thực hiện
      const middleware = authenticate({ secret: jwtSecret });
      middleware(mockReq as Request, mockRes as Response, mockNext);

      // EN: Assert
      // VI: Kiểm tra
      expect(mockNext).not.toHaveBeenCalled();
      expect(mockStatus).toHaveBeenCalledWith(401);
      expect(mockJson).toHaveBeenCalledWith({
        success: false,
        error: {
          code: 'AUTH_001',
          message: 'Authentication required / Yêu cầu xác thực',
        },
        timestamp: expect.any(String),
      });
    });

    it('should return 401 for invalid token', () => {
      // EN: Arrange
      // VI: Chuẩn bị
      const mockReq = createMockReq({
        headers: { authorization: 'Bearer invalid-token' },
      });
      const mockRes = createMockRes();

      // EN: Act
      // VI: Thực hiện
      const middleware = authenticate({ secret: jwtSecret });
      middleware(mockReq as Request, mockRes as Response, mockNext);

      // EN: Assert
      // VI: Kiểm tra
      expect(mockNext).not.toHaveBeenCalled();
      expect(mockStatus).toHaveBeenCalledWith(401);
      expect(mockJson).toHaveBeenCalledWith({
        success: false,
        error: {
          code: 'AUTH_002',
          message: 'Invalid or expired token / Token không hợp lệ hoặc hết hạn',
        },
        timestamp: expect.any(String),
      });
    });

    it('should return 401 for malformed authorization header', () => {
      // EN: Arrange
      // VI: Chuẩn bị
      const mockReq = createMockReq({
        headers: { authorization: 'InvalidFormat token123' },
      });
      const mockRes = createMockRes();

      // EN: Act
      // VI: Thực hiện
      const middleware = authenticate({ secret: jwtSecret });
      middleware(mockReq as Request, mockRes as Response, mockNext);

      // EN: Assert
      // VI: Kiểm tra
      expect(mockNext).not.toHaveBeenCalled();
      expect(mockStatus).toHaveBeenCalledWith(401);
    });
  });

  describe('authorize', () => {
    it('should allow access for user with correct role', () => {
      // EN: Arrange
      // VI: Chuẩn bị
      const mockReq = createMockReq({
        user: { userId: 'admin-123', email: 'admin@example.com', role: 'admin' },
      });
      const mockRes = createMockRes();

      // EN: Act
      // VI: Thực hiện
      const middleware = authorize('admin');
      middleware(mockReq as Request, mockRes as Response, mockNext);

      // EN: Assert
      // VI: Kiểm tra
      expect(mockNext).toHaveBeenCalled();
      expect(mockStatus).not.toHaveBeenCalled();
    });

    it('should deny access for user with incorrect role', () => {
      // EN: Arrange
      // VI: Chuẩn bị
      const mockReq = createMockReq({
        user: { userId: 'user-123', email: 'user@example.com', role: 'user' },
      });
      const mockRes = createMockRes();

      // EN: Act
      // VI: Thực hiện
      const middleware = authorize('admin');
      middleware(mockReq as Request, mockRes as Response, mockNext);

      // EN: Assert
      // VI: Kiểm tra
      expect(mockNext).not.toHaveBeenCalled();
      expect(mockStatus).toHaveBeenCalledWith(403);
      expect(mockJson).toHaveBeenCalledWith({
        success: false,
        error: {
          code: 'AUTH_004',
          message: 'Insufficient permissions / Không đủ quyền',
        },
        timestamp: expect.any(String),
      });
    });

    it('should allow access for user with any of the allowed roles', () => {
      // EN: Arrange
      // VI: Chuẩn bị
      const mockReq = createMockReq({
        user: { userId: 'user-123', email: 'user@example.com', role: 'user' },
      });
      const mockRes = createMockRes();

      // EN: Act
      // VI: Thực hiện
      const middleware = authorize('admin', 'user', 'moderator');
      middleware(mockReq as Request, mockRes as Response, mockNext);

      // EN: Assert
      // VI: Kiểm tra
      expect(mockNext).toHaveBeenCalled();
      expect(mockStatus).not.toHaveBeenCalled();
    });

    it('should return 401 for unauthenticated user', () => {
      // EN: Arrange
      // VI: Chuẩn bị
      const mockReq = createMockReq(); // No user attached
      const mockRes = createMockRes();

      // EN: Act
      // VI: Thực hiện
      const middleware = authorize('admin');
      middleware(mockReq as Request, mockRes as Response, mockNext);

      // EN: Assert
      // VI: Kiểm tra
      expect(mockNext).not.toHaveBeenCalled();
      expect(mockStatus).toHaveBeenCalledWith(401);
      expect(mockJson).toHaveBeenCalledWith({
        success: false,
        error: {
          code: 'AUTH_003',
          message: 'Authentication required / Yêu cầu xác thực',
        },
        timestamp: expect.any(String),
      });
    });
  });

  describe('Utility Functions', () => {
    const user = { userId: '123', email: 'test@example.com', role: 'user' };
    const admin = { userId: '456', email: 'admin@example.com', role: 'admin' };

    describe('hasRole', () => {
      it('should return true for matching role', () => {
        expect(hasRole(user, 'user')).toBe(true);
        expect(hasRole(admin, 'admin')).toBe(true);
      });

      it('should return false for non-matching role', () => {
        expect(hasRole(user, 'admin')).toBe(false);
        expect(hasRole(admin, 'user')).toBe(false);
      });

      it('should return false for null/undefined user', () => {
        expect(hasRole(null as any, 'user')).toBe(false);
        expect(hasRole(undefined as any, 'admin')).toBe(false);
      });
    });

    describe('hasAnyRole', () => {
      it('should return true if user has any of the specified roles', () => {
        expect(hasAnyRole(user, ['user', 'admin'])).toBe(true);
        expect(hasAnyRole(admin, ['user', 'admin'])).toBe(true);
      });

      it('should return false if user does not have any of the specified roles', () => {
        expect(hasAnyRole(user, ['admin', 'moderator'])).toBe(false);
      });

      it('should return false for null/undefined user', () => {
        expect(hasAnyRole(null as any, ['user'])).toBe(false);
        expect(hasAnyRole(undefined as any, ['admin'])).toBe(false);
      });
    });

    describe('isAuthenticated', () => {
      it('should return true for authenticated user', () => {
        expect(isAuthenticated(user)).toBe(true);
        expect(isAuthenticated(admin)).toBe(true);
      });

      it('should return false for null/undefined user', () => {
        expect(isAuthenticated(null as any)).toBe(false);
        expect(isAuthenticated(undefined as any)).toBe(false);
      });
    });
  });

  describe('Integration Test', () => {
    it('should authenticate and authorize admin user successfully', () => {
      // EN: Arrange
      // VI: Chuẩn bị
      const mockReq = createMockReq({
        headers: { authorization: `Bearer ${adminToken}` },
      });
      const mockRes = createMockRes();
      const nextChain: jest.Mock[] = [jest.fn(), jest.fn()];

      // EN: Act - Test both authenticate and authorize middlewares
      // VI: Thực hiện - Test cả hai middleware authenticate và authorize
      const authMiddleware = authenticate({ secret: jwtSecret });
      const authorizeMiddleware = authorize('admin');

      authMiddleware(mockReq as Request, mockRes as Response, nextChain[0]);
      if (nextChain[0].mock.calls.length > 0) {
        authorizeMiddleware(mockReq as Request, mockRes as Response, nextChain[1]);
      }

      // EN: Assert
      // VI: Kiểm tra
      expect(nextChain[0]).toHaveBeenCalled(); // authenticate passed
      expect(nextChain[1]).toHaveBeenCalled(); // authorize passed
      expect(mockReq.user?.role).toBe('admin');
      expect(mockStatus).not.toHaveBeenCalled();
    });
  });
});