const request = require('supertest');
const express = require('express');
const bcrypt = require('bcrypt');
const { sequelize, Admin } = require('../models');
const { authenticateAdmin } = require('../middleware/auth');
const { requireRole, requireSuperAdmin, requireAdmin, requireOperator } = require('../middleware/rbac');
const { generateToken } = require('../utils/jwt');

/**
 * Unit Tests for RBAC Middleware
 * Tests role-based access control functionality
 */

describe('RBAC Middleware Tests', () => {
  let app;
  let superAdminToken;
  let adminToken;
  let operatorToken;
  let superAdmin;
  let admin;
  let operator;

  beforeAll(async () => {
    await sequelize.sync({ force: true });

    // Create test admins with different roles
    const hashedPassword = await bcrypt.hash('testpass', 10);

    superAdmin = await Admin.create({
      username: 'superadmin',
      password: hashedPassword,
      email: 'super@example.com',
      role: 'super_admin',
      status: 'active',
    });

    admin = await Admin.create({
      username: 'admin',
      password: hashedPassword,
      email: 'admin@example.com',
      role: 'admin',
      status: 'active',
    });

    operator = await Admin.create({
      username: 'operator',
      password: hashedPassword,
      email: 'operator@example.com',
      role: 'operator',
      status: 'active',
    });

    // Generate tokens
    superAdminToken = generateToken({
      userId: superAdmin.id,
      type: 'admin',
      role: 'super_admin',
    });

    adminToken = generateToken({
      userId: admin.id,
      type: 'admin',
      role: 'admin',
    });

    operatorToken = generateToken({
      userId: operator.id,
      type: 'admin',
      role: 'operator',
    });

    // Create test app with RBAC routes
    app = express();
    app.use(express.json());

    // Test routes with different role requirements
    app.get('/super-admin-only', authenticateAdmin, requireSuperAdmin, (req, res) => {
      res.json({ success: true, message: 'Super admin access granted' });
    });

    app.get('/admin-or-higher', authenticateAdmin, requireAdmin, (req, res) => {
      res.json({ success: true, message: 'Admin access granted' });
    });

    app.get('/any-admin', authenticateAdmin, requireOperator, (req, res) => {
      res.json({ success: true, message: 'Operator access granted' });
    });

    app.get('/custom-role', authenticateAdmin, requireRole(['admin']), (req, res) => {
      res.json({ success: true, message: 'Custom role access granted' });
    });
  });

  afterAll(async () => {
    await sequelize.close();
  });

  describe('requireSuperAdmin', () => {
    test('should allow super_admin access', async () => {
      const response = await request(app)
        .get('/super-admin-only')
        .set('Authorization', `Bearer ${superAdminToken}`);

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
    });

    test('should deny admin access', async () => {
      const response = await request(app)
        .get('/super-admin-only')
        .set('Authorization', `Bearer ${adminToken}`);

      expect(response.status).toBe(403);
      expect(response.body.success).toBe(false);
      expect(response.body.error.code).toBe('FORBIDDEN');
    });

    test('should deny operator access', async () => {
      const response = await request(app)
        .get('/super-admin-only')
        .set('Authorization', `Bearer ${operatorToken}`);

      expect(response.status).toBe(403);
      expect(response.body.success).toBe(false);
      expect(response.body.error.code).toBe('FORBIDDEN');
    });
  });

  describe('requireAdmin', () => {
    test('should allow super_admin access', async () => {
      const response = await request(app)
        .get('/admin-or-higher')
        .set('Authorization', `Bearer ${superAdminToken}`);

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
    });

    test('should allow admin access', async () => {
      const response = await request(app)
        .get('/admin-or-higher')
        .set('Authorization', `Bearer ${adminToken}`);

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
    });

    test('should deny operator access', async () => {
      const response = await request(app)
        .get('/admin-or-higher')
        .set('Authorization', `Bearer ${operatorToken}`);

      expect(response.status).toBe(403);
      expect(response.body.success).toBe(false);
      expect(response.body.error.code).toBe('FORBIDDEN');
    });
  });

  describe('requireOperator', () => {
    test('should allow super_admin access', async () => {
      const response = await request(app)
        .get('/any-admin')
        .set('Authorization', `Bearer ${superAdminToken}`);

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
    });

    test('should allow admin access', async () => {
      const response = await request(app)
        .get('/any-admin')
        .set('Authorization', `Bearer ${adminToken}`);

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
    });

    test('should allow operator access', async () => {
      const response = await request(app)
        .get('/any-admin')
        .set('Authorization', `Bearer ${operatorToken}`);

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
    });
  });

  describe('requireRole with custom roles', () => {
    test('should allow exact role match', async () => {
      const response = await request(app)
        .get('/custom-role')
        .set('Authorization', `Bearer ${adminToken}`);

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
    });

    test('should allow higher role', async () => {
      const response = await request(app)
        .get('/custom-role')
        .set('Authorization', `Bearer ${superAdminToken}`);

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
    });

    test('should deny lower role', async () => {
      const response = await request(app)
        .get('/custom-role')
        .set('Authorization', `Bearer ${operatorToken}`);

      expect(response.status).toBe(403);
      expect(response.body.success).toBe(false);
    });
  });

  describe('RBAC without authentication', () => {
    test('should reject request without token', async () => {
      const response = await request(app)
        .get('/any-admin');

      expect(response.status).toBe(401);
      expect(response.body.success).toBe(false);
      expect(response.body.error.code).toBe('MISSING_TOKEN');
    });
  });
});
