/**
 * @ai-summary Integration tests for admin management API endpoints
 * @ai-context Tests complete request/response cycle with test database and admin guard
 */

import request from 'supertest';
import { app } from '../../../../app';
import pool from '../../../../core/config/database';
import { readFileSync } from 'fs';
import { join } from 'path';
import fastifyPlugin from 'fastify-plugin';
import { setAdminGuardPool } from '../../../../core/plugins/admin-guard.plugin';

const DEFAULT_ADMIN_SUB = 'test-admin-123';
const DEFAULT_ADMIN_EMAIL = 'test-admin@motovaultpro.com';

let currentUser = {
  sub: DEFAULT_ADMIN_SUB,
  email: DEFAULT_ADMIN_EMAIL,
};

// Mock auth plugin to inject test admin user
jest.mock('../../../../core/plugins/auth.plugin', () => {
  return {
    default: fastifyPlugin(async function(fastify) {
      fastify.decorate('authenticate', async function(request, _reply) {
        // Inject dynamic test user context
        request.user = { sub: currentUser.sub };
        request.userContext = {
          userId: currentUser.sub,
          email: currentUser.email,
          isAdmin: false, // Will be set by admin guard
        };
      });
    }, { name: 'auth-plugin' })
  };
});

describe('Admin Management Integration Tests', () => {
  let testAdminAuth0Sub: string;
  let testNonAdminAuth0Sub: string;

  beforeAll(async () => {
    // Run the admin migration directly using the migration file
    const migrationFile = join(__dirname, '../../migrations/001_create_admin_users.sql');
    const migrationSQL = readFileSync(migrationFile, 'utf-8');
    await pool.query(migrationSQL);

    // Set admin guard pool
    setAdminGuardPool(pool);

    // Create test admin user
    testAdminAuth0Sub = DEFAULT_ADMIN_SUB;
    await pool.query(`
      INSERT INTO admin_users (auth0_sub, email, role, created_by)
      VALUES ($1, $2, $3, $4)
      ON CONFLICT (auth0_sub) DO NOTHING
    `, [testAdminAuth0Sub, DEFAULT_ADMIN_EMAIL, 'admin', 'system']);

    // Create test non-admin auth0Sub for permission tests
    testNonAdminAuth0Sub = 'test-non-admin-456';
  });

  afterAll(async () => {
    // Clean up test database
    await pool.query('DROP TABLE IF EXISTS admin_audit_logs CASCADE');
    await pool.query('DROP TABLE IF EXISTS admin_users CASCADE');
    await pool.end();
  });

  beforeEach(async () => {
    // Clean up test data before each test (except the test admin)
    await pool.query(
      'DELETE FROM admin_users WHERE auth0_sub != $1 AND auth0_sub != $2',
      [testAdminAuth0Sub, 'system|bootstrap']
    );
    await pool.query('DELETE FROM admin_audit_logs');
    currentUser = {
      sub: DEFAULT_ADMIN_SUB,
      email: DEFAULT_ADMIN_EMAIL,
    };
  });

  describe('Authorization', () => {
    it('should reject non-admin user trying to list admins', async () => {
      // Create mock for non-admin user
      currentUser = {
        sub: testNonAdminAuth0Sub,
        email: 'test-user@example.com',
      };

      const response = await request(app)
        .get('/api/admin/admins')
        .expect(403);

      expect(response.body.error).toBe('Forbidden');
      expect(response.body.message).toBe('Admin access required');
    });
  });

  describe('GET /api/admin/verify', () => {
    it('should confirm admin access for existing admin', async () => {
      currentUser = {
        sub: testAdminAuth0Sub,
        email: DEFAULT_ADMIN_EMAIL,
      };

      const response = await request(app)
        .get('/api/admin/verify')
        .expect(200);

      expect(response.body.isAdmin).toBe(true);
      expect(response.body.adminRecord).toMatchObject({
        auth0Sub: testAdminAuth0Sub,
        email: DEFAULT_ADMIN_EMAIL,
      });
    });

    it('should link admin record by email when auth0_sub differs', async () => {
      const placeholderSub = 'auth0|placeholder-sub';
      const realSub = 'auth0|real-admin-sub';
      const email = 'link-admin@example.com';

      await pool.query(`
        INSERT INTO admin_users (auth0_sub, email, role, created_by)
        VALUES ($1, $2, $3, $4)
      `, [placeholderSub, email, 'admin', testAdminAuth0Sub]);

      currentUser = {
        sub: realSub,
        email,
      };

      const response = await request(app)
        .get('/api/admin/verify')
        .expect(200);

      expect(response.body.isAdmin).toBe(true);
      expect(response.body.adminRecord).toMatchObject({
        auth0Sub: realSub,
        email,
      });

      const record = await pool.query(
        'SELECT auth0_sub FROM admin_users WHERE email = $1',
        [email]
      );
      expect(record.rows[0].auth0_sub).toBe(realSub);
    });

    it('should return non-admin response for unknown user', async () => {
      currentUser = {
        sub: 'auth0|non-admin-123',
        email: 'non-admin@example.com',
      };

      const response = await request(app)
        .get('/api/admin/verify')
        .expect(200);

      expect(response.body.isAdmin).toBe(false);
      expect(response.body.adminRecord).toBeNull();
    });
  });

  describe('GET /api/admin/admins', () => {
    it('should list all admin users', async () => {
      // Create additional test admins
      await pool.query(`
        INSERT INTO admin_users (auth0_sub, email, role, created_by)
        VALUES
        ($1, $2, $3, $4),
        ($5, $6, $7, $8)
      `, [
        'auth0|admin1', 'admin1@example.com', 'admin', testAdminAuth0Sub,
        'auth0|admin2', 'admin2@example.com', 'super_admin', testAdminAuth0Sub
      ]);

      const response = await request(app)
        .get('/api/admin/admins')
        .expect(200);

      expect(response.body).toHaveProperty('total');
      expect(response.body).toHaveProperty('admins');
      expect(response.body.admins.length).toBeGreaterThanOrEqual(3); // At least test admin + 2 created
      expect(response.body.admins[0]).toMatchObject({
        auth0Sub: expect.any(String),
        email: expect.any(String),
        role: expect.stringMatching(/^(admin|super_admin)$/),
        createdAt: expect.any(String),
        createdBy: expect.any(String)
      });
    });

    it('should include revoked admins in the list', async () => {
      // Create and revoke an admin
      await pool.query(`
        INSERT INTO admin_users (auth0_sub, email, role, created_by, revoked_at)
        VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)
      `, ['auth0|revoked', 'revoked@example.com', 'admin', testAdminAuth0Sub]);

      const response = await request(app)
        .get('/api/admin/admins')
        .expect(200);

      const revokedAdmin = response.body.admins.find(
        (admin: any) => admin.email === 'revoked@example.com'
      );
      expect(revokedAdmin).toBeDefined();
      expect(revokedAdmin.revokedAt).toBeTruthy();
    });
  });

  describe('POST /api/admin/admins', () => {
    it('should create a new admin user', async () => {
      const newAdminData = {
        email: 'newadmin@example.com',
        role: 'admin'
      };

      const response = await request(app)
        .post('/api/admin/admins')
        .send(newAdminData)
        .expect(201);

      expect(response.body).toMatchObject({
        auth0Sub: expect.any(String),
        email: 'newadmin@example.com',
        role: 'admin',
        createdAt: expect.any(String),
        createdBy: testAdminAuth0Sub,
        revokedAt: null
      });

      // Verify audit log was created
      const auditResult = await pool.query(
        'SELECT * FROM admin_audit_logs WHERE action = $1 AND resource_id = $2',
        ['CREATE', 'newadmin@example.com']
      );
      expect(auditResult.rows.length).toBe(1);
      expect(auditResult.rows[0].actor_admin_id).toBe(testAdminAuth0Sub);
    });

    it('should reject invalid email', async () => {
      const invalidData = {
        email: 'not-an-email',
        role: 'admin'
      };

      const response = await request(app)
        .post('/api/admin/admins')
        .send(invalidData)
        .expect(400);

      expect(response.body.error).toBe('Bad Request');
      expect(response.body.message).toBe('Invalid request body');
    });

    it('should reject duplicate email', async () => {
      const adminData = {
        email: 'duplicate@example.com',
        role: 'admin'
      };

      // Create first admin
      await request(app)
        .post('/api/admin/admins')
        .send(adminData)
        .expect(201);

      // Try to create duplicate
      const response = await request(app)
        .post('/api/admin/admins')
        .send(adminData)
        .expect(400);

      expect(response.body.error).toBe('Bad Request');
      expect(response.body.message).toContain('already exists');
    });

    it('should create super_admin when role specified', async () => {
      const superAdminData = {
        email: 'superadmin@example.com',
        role: 'super_admin'
      };

      const response = await request(app)
        .post('/api/admin/admins')
        .send(superAdminData)
        .expect(201);

      expect(response.body.role).toBe('super_admin');
    });

    it('should default to admin role when not specified', async () => {
      const adminData = {
        email: 'defaultrole@example.com'
      };

      const response = await request(app)
        .post('/api/admin/admins')
        .send(adminData)
        .expect(201);

      expect(response.body.role).toBe('admin');
    });
  });

  describe('PATCH /api/admin/admins/:auth0Sub/revoke', () => {
    it('should revoke admin access', async () => {
      // Create admin to revoke
      const createResult = await pool.query(`
        INSERT INTO admin_users (auth0_sub, email, role, created_by)
        VALUES ($1, $2, $3, $4)
        RETURNING auth0_sub
      `, ['auth0|to-revoke', 'torevoke@example.com', 'admin', testAdminAuth0Sub]);

      const auth0Sub = createResult.rows[0].auth0_sub;

      const response = await request(app)
        .patch(`/api/admin/admins/${auth0Sub}/revoke`)
        .expect(200);

      expect(response.body).toMatchObject({
        auth0Sub,
        email: 'torevoke@example.com',
        revokedAt: expect.any(String)
      });

      // Verify audit log
      const auditResult = await pool.query(
        'SELECT * FROM admin_audit_logs WHERE action = $1 AND target_admin_id = $2',
        ['REVOKE', auth0Sub]
      );
      expect(auditResult.rows.length).toBe(1);
    });

    it('should prevent revoking last active admin', async () => {
      // First, ensure only one active admin exists
      await pool.query(
        'UPDATE admin_users SET revoked_at = CURRENT_TIMESTAMP WHERE auth0_sub != $1',
        [testAdminAuth0Sub]
      );

      const response = await request(app)
        .patch(`/api/admin/admins/${testAdminAuth0Sub}/revoke`)
        .expect(400);

      expect(response.body.error).toBe('Bad Request');
      expect(response.body.message).toContain('Cannot revoke the last active admin');
    });

    it('should return 404 for non-existent admin', async () => {
      const response = await request(app)
        .patch('/api/admin/admins/auth0|nonexistent/revoke')
        .expect(404);

      expect(response.body.error).toBe('Not Found');
      expect(response.body.message).toBe('Admin user not found');
    });
  });

  describe('PATCH /api/admin/admins/:auth0Sub/reinstate', () => {
    it('should reinstate revoked admin', async () => {
      // Create revoked admin
      const createResult = await pool.query(`
        INSERT INTO admin_users (auth0_sub, email, role, created_by, revoked_at)
        VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)
        RETURNING auth0_sub
      `, ['auth0|to-reinstate', 'toreinstate@example.com', 'admin', testAdminAuth0Sub]);

      const auth0Sub = createResult.rows[0].auth0_sub;

      const response = await request(app)
        .patch(`/api/admin/admins/${auth0Sub}/reinstate`)
        .expect(200);

      expect(response.body).toMatchObject({
        auth0Sub,
        email: 'toreinstate@example.com',
        revokedAt: null
      });

      // Verify audit log
      const auditResult = await pool.query(
        'SELECT * FROM admin_audit_logs WHERE action = $1 AND target_admin_id = $2',
        ['REINSTATE', auth0Sub]
      );
      expect(auditResult.rows.length).toBe(1);
    });

    it('should return 404 for non-existent admin', async () => {
      const response = await request(app)
        .patch('/api/admin/admins/auth0|nonexistent/reinstate')
        .expect(404);

      expect(response.body.error).toBe('Not Found');
      expect(response.body.message).toBe('Admin user not found');
    });

    it('should handle reinstating already active admin', async () => {
      // Create active admin
      const createResult = await pool.query(`
        INSERT INTO admin_users (auth0_sub, email, role, created_by)
        VALUES ($1, $2, $3, $4)
        RETURNING auth0_sub
      `, ['auth0|already-active', 'active@example.com', 'admin', testAdminAuth0Sub]);

      const auth0Sub = createResult.rows[0].auth0_sub;

      const response = await request(app)
        .patch(`/api/admin/admins/${auth0Sub}/reinstate`)
        .expect(200);

      expect(response.body.revokedAt).toBeNull();
    });
  });

  describe('GET /api/admin/audit-logs', () => {
    it('should fetch audit logs with default pagination', async () => {
      // Create some audit log entries
      await pool.query(`
        INSERT INTO admin_audit_logs (actor_admin_id, action, resource_type, resource_id)
        VALUES
        ($1, $2, $3, $4),
        ($5, $6, $7, $8),
        ($9, $10, $11, $12)
      `, [
        testAdminAuth0Sub, 'CREATE', 'admin_user', 'test1@example.com',
        testAdminAuth0Sub, 'REVOKE', 'admin_user', 'test2@example.com',
        testAdminAuth0Sub, 'REINSTATE', 'admin_user', 'test3@example.com'
      ]);

      const response = await request(app)
        .get('/api/admin/audit-logs')
        .expect(200);

      expect(response.body).toHaveProperty('total');
      expect(response.body).toHaveProperty('logs');
      expect(response.body.logs.length).toBeGreaterThanOrEqual(3);
      expect(response.body.logs[0]).toMatchObject({
        id: expect.any(String),
        actorAdminId: testAdminAuth0Sub,
        action: expect.any(String),
        resourceType: expect.any(String),
        createdAt: expect.any(String)
      });
    });

    it('should support pagination with limit and offset', async () => {
      // Create multiple audit log entries
      for (let i = 0; i < 15; i++) {
        await pool.query(`
          INSERT INTO admin_audit_logs (actor_admin_id, action, resource_type, resource_id)
          VALUES ($1, $2, $3, $4)
        `, [testAdminAuth0Sub, 'CREATE', 'admin_user', `test${i}@example.com`]);
      }

      const response = await request(app)
        .get('/api/admin/audit-logs?limit=5&offset=0')
        .expect(200);

      expect(response.body.logs.length).toBeLessThanOrEqual(5);
      expect(response.body.total).toBeGreaterThanOrEqual(15);
    });

    it('should return logs in descending order by created_at', async () => {
      // Create audit logs with delays to ensure different timestamps
      await pool.query(`
        INSERT INTO admin_audit_logs (actor_admin_id, action, created_at)
        VALUES
        ($1, $2, CURRENT_TIMESTAMP - INTERVAL '2 minutes'),
        ($3, $4, CURRENT_TIMESTAMP - INTERVAL '1 minute'),
        ($5, $6, CURRENT_TIMESTAMP)
      `, [
        testAdminAuth0Sub, 'FIRST',
        testAdminAuth0Sub, 'SECOND',
        testAdminAuth0Sub, 'THIRD'
      ]);

      const response = await request(app)
        .get('/api/admin/audit-logs?limit=3')
        .expect(200);

      expect(response.body.logs[0].action).toBe('THIRD');
      expect(response.body.logs[1].action).toBe('SECOND');
      expect(response.body.logs[2].action).toBe('FIRST');
    });
  });

  describe('End-to-end workflow', () => {
    it('should create, revoke, and reinstate admin with full audit trail', async () => {
      // 1. Create new admin
      const createResponse = await request(app)
        .post('/api/admin/admins')
        .send({ email: 'workflow@example.com', role: 'admin' })
        .expect(201);

      const auth0Sub = createResponse.body.auth0Sub;

      // 2. Verify admin appears in list
      const listResponse = await request(app)
        .get('/api/admin/admins')
        .expect(200);

      const createdAdmin = listResponse.body.admins.find(
        (admin: any) => admin.auth0Sub === auth0Sub
      );
      expect(createdAdmin).toBeDefined();
      expect(createdAdmin.revokedAt).toBeNull();

      // 3. Revoke admin
      const revokeResponse = await request(app)
        .patch(`/api/admin/admins/${auth0Sub}/revoke`)
        .expect(200);

      expect(revokeResponse.body.revokedAt).toBeTruthy();

      // 4. Reinstate admin
      const reinstateResponse = await request(app)
        .patch(`/api/admin/admins/${auth0Sub}/reinstate`)
        .expect(200);

      expect(reinstateResponse.body.revokedAt).toBeNull();

      // 5. Verify complete audit trail
      const auditResponse = await request(app)
        .get('/api/admin/audit-logs')
        .expect(200);

      const workflowLogs = auditResponse.body.logs.filter(
        (log: any) => log.targetAdminId === auth0Sub || log.resourceId === 'workflow@example.com'
      );

      expect(workflowLogs.length).toBeGreaterThanOrEqual(3);
      const actions = workflowLogs.map((log: any) => log.action);
      expect(actions).toContain('CREATE');
      expect(actions).toContain('REVOKE');
      expect(actions).toContain('REINSTATE');
    });
  });
});
