const request = require('supertest');
const bcrypt = require('bcrypt');
const createApp = require('../app');
const { sequelize, Admin, LoginHistory } = require('../models');

/**
 * Integration Tests for Admin Authentication System
 * Tests admin login, RBAC, and operation logging
 */

describe('Admin Authentication Integration Tests', () => {
  let app;
  let testAdmin;
  let testOperator;
  let testInactiveAdmin;

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

    // Create test admins
    const hashedPassword = await bcrypt.hash('testpass123', 10);

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

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

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

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

  describe('POST /api/v1/admin/login', () => {
    test('should login successfully with valid credentials', async () => {
      const response = await request(app)
        .post('/api/v1/admin/login')
        .send({
          username: 'testadmin',
          password: 'testpass123',
        });

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data).toHaveProperty('admin');
      expect(response.body.data).toHaveProperty('token');
      expect(response.body.data.admin.username).toBe('testadmin');
      expect(response.body.data.admin.role).toBe('admin');
      expect(response.body.data.admin).not.toHaveProperty('password');
    });

    test('should create login history record on successful login', async () => {
      const beforeCount = await LoginHistory.count({
        where: { userId: testAdmin.id, userType: 'admin' },
      });

      await request(app)
        .post('/api/v1/admin/login')
        .send({
          username: 'testadmin',
          password: 'testpass123',
        });

      const afterCount = await LoginHistory.count({
        where: { userId: testAdmin.id, userType: 'admin' },
      });

      expect(afterCount).toBe(beforeCount + 1);

      // Verify login history details
      const loginRecord = await LoginHistory.findOne({
        where: { userId: testAdmin.id, userType: 'admin' },
        order: [['loginAt', 'DESC']],
      });

      expect(loginRecord).toBeDefined();
      expect(loginRecord.userId).toBe(testAdmin.id);
      expect(loginRecord.userType).toBe('admin');
      expect(loginRecord.loginAt).toBeDefined();
    });

    test('should update lastLoginAt on successful login', async () => {
      const beforeLogin = await Admin.findByPk(testAdmin.id);
      const previousLoginAt = beforeLogin.lastLoginAt;

      // Wait a bit to ensure timestamp difference
      await new Promise(resolve => setTimeout(resolve, 100));

      await request(app)
        .post('/api/v1/admin/login')
        .send({
          username: 'testadmin',
          password: 'testpass123',
        });

      const afterLogin = await Admin.findByPk(testAdmin.id);
      
      if (previousLoginAt) {
        expect(afterLogin.lastLoginAt.getTime()).toBeGreaterThan(previousLoginAt.getTime());
      } else {
        expect(afterLogin.lastLoginAt).toBeDefined();
      }
    });

    test('should reject login with invalid username', async () => {
      const response = await request(app)
        .post('/api/v1/admin/login')
        .send({
          username: 'nonexistent',
          password: 'testpass123',
        });

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

    test('should reject login with invalid password', async () => {
      const response = await request(app)
        .post('/api/v1/admin/login')
        .send({
          username: 'testadmin',
          password: 'wrongpassword',
        });

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

    test('should reject login for inactive admin', async () => {
      const response = await request(app)
        .post('/api/v1/admin/login')
        .send({
          username: 'inactiveadmin',
          password: 'testpass123',
        });

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

    test('should reject login with missing credentials', async () => {
      const response = await request(app)
        .post('/api/v1/admin/login')
        .send({
          username: 'testadmin',
        });

      expect(response.status).toBe(400);
      expect(response.body.success).toBe(false);
      expect(response.body.error.code).toBe('MISSING_CREDENTIALS');
    });
  });

  describe('GET /api/v1/admin/profile', () => {
    let adminToken;

    beforeAll(async () => {
      const response = await request(app)
        .post('/api/v1/admin/login')
        .send({
          username: 'testadmin',
          password: 'testpass123',
        });

      adminToken = response.body.data.token;
    });

    test('should get admin profile with valid token', async () => {
      const response = await request(app)
        .get('/api/v1/admin/profile')
        .set('Authorization', `Bearer ${adminToken}`);

      expect(response.status).toBe(200);
      expect(response.body.success).toBe(true);
      expect(response.body.data.admin.username).toBe('testadmin');
      expect(response.body.data.admin).not.toHaveProperty('password');
    });

    test('should reject request without token', async () => {
      const response = await request(app)
        .get('/api/v1/admin/profile');

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

    test('should reject request with invalid token', async () => {
      const response = await request(app)
        .get('/api/v1/admin/profile')
        .set('Authorization', 'Bearer invalid.token.here');

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

  describe('Admin Authentication Middleware', () => {
    test('should reject user token for admin endpoints', async () => {
      // Generate a user token
      const { generateToken } = require('../utils/jwt');
      const userToken = generateToken({
        userId: 'test-user-id',
        type: 'user',
      });

      const response = await request(app)
        .get('/api/v1/admin/profile')
        .set('Authorization', `Bearer ${userToken}`);

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