/**
 * Enhanced Role-Based Access Control (RBAC) Service
 * 
 * Implements RBAC and ABAC per DoD/MilSpec requirements:
 * - NIST SP 800-53: AC-2 (Account Management), AC-3 (Access Enforcement)
 * - NIST SP 800-171: 3.1.1-3.1.23 (Access Control)
 * 
 * Features:
 * - Hierarchical roles
 * - Dynamic permission assignment
 * - Attribute-Based Access Control (ABAC)
 * - Role separation of duties
 * - Least privilege enforcement
 * - Periodic access reviews
 */

import { getDb } from '../db'
import { logger } from '../lib/logger'

export interface Role {
  id: string
  name: string
  description?: string
  permissions: string[]
  isSystem: boolean
  createdAt: Date
  updatedAt: Date
}

export interface Permission {
  id: string
  name: string
  resourceType: string
  action: string
  conditions?: Record<string, any>
  description?: string
}

export interface UserRole {
  id: string
  userId: string
  roleId: string
  grantedBy?: string
  grantedAt: Date
  expiresAt?: Date
  metadata?: Record<string, any>
}

/**
 * Check if user has permission
 */
export async function hasPermission(
  userId: string,
  resourceType: string,
  action: string,
  resourceId?: string,
  context?: Record<string, any>
): Promise<boolean> {
  const db = getDb()
  
  // Get user's roles
  const rolesResult = await db.query(
    `SELECT r.id, r.name, r.permissions, rp.permission_id, p.name as permission_name,
            p.resource_type, p.action, p.conditions, rp.conditions as role_conditions
     FROM user_roles ur
     JOIN roles r ON ur.role_id = r.id
     LEFT JOIN role_permissions rp ON r.id = rp.role_id
     LEFT JOIN permissions p ON rp.permission_id = p.id
     WHERE ur.user_id = $1 
       AND (ur.expires_at IS NULL OR ur.expires_at > NOW())
       AND r.name = 'SYSTEM_ADMIN' OR (
         (p.resource_type = $2 OR p.resource_type = '*')
         AND (p.action = $3 OR p.action = '*')
       )`,
    [userId, resourceType, action]
  )

  if (rolesResult.rows.length === 0) {
    return false
  }

  // Check for SYSTEM_ADMIN role (full access)
  const hasSystemAdmin = rolesResult.rows.some(row => row.name === 'SYSTEM_ADMIN')
  if (hasSystemAdmin) {
    return true
  }

  // Check permissions with conditions (ABAC)
  for (const row of rolesResult.rows) {
    if (!row.permission_id) {
      // Check role-level permissions
      const rolePermissions = row.permissions || []
      if (rolePermissions.includes('*') || rolePermissions.includes(`${resourceType}:*`) || 
          rolePermissions.includes(`${resourceType}:${action}`)) {
        // Check conditions if present
        if (row.role_conditions && !evaluateConditions(row.role_conditions, context)) {
          continue
        }
        return true
      }
    } else {
      // Check permission-level access
      if ((row.resource_type === resourceType || row.resource_type === '*') &&
          (row.action === action || row.action === '*')) {
        // Check conditions if present
        const conditions = row.conditions || row.role_conditions
        if (conditions && !evaluateConditions(conditions, context)) {
          continue
        }
        return true
      }
    }
  }

  return false
}

/**
 * Evaluate ABAC conditions
 */
function evaluateConditions(conditions: Record<string, any>, context?: Record<string, any>): boolean {
  if (!conditions || Object.keys(conditions).length === 0) {
    return true // No conditions = allow
  }

  if (!context) {
    return false // Conditions required but no context provided
  }

  // Simple condition evaluation
  // In production, this should support more complex expressions
  for (const [key, value] of Object.entries(conditions)) {
    if (context[key] !== value) {
      return false
    }
  }

  return true
}

/**
 * Get user's roles
 */
export async function getUserRoles(userId: string): Promise<Role[]> {
  const db = getDb()
  
  const result = await db.query(
    `SELECT r.id, r.name, r.description, r.permissions, r.is_system, r.created_at, r.updated_at
     FROM user_roles ur
     JOIN roles r ON ur.role_id = r.id
     WHERE ur.user_id = $1 
       AND (ur.expires_at IS NULL OR ur.expires_at > NOW())
     ORDER BY r.name`,
    [userId]
  )

  return result.rows.map(row => ({
    id: row.id,
    name: row.name,
    description: row.description,
    permissions: row.permissions || [],
    isSystem: row.is_system,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  }))
}

/**
 * Assign role to user
 */
export async function assignRole(
  userId: string,
  roleId: string,
  grantedBy: string,
  expiresAt?: Date,
  metadata?: Record<string, any>
): Promise<void> {
  const db = getDb()
  
  await db.query(
    `INSERT INTO user_roles (user_id, role_id, granted_by, expires_at, metadata)
     VALUES ($1, $2, $3, $4, $5)
     ON CONFLICT (user_id, role_id) DO UPDATE
     SET granted_by = $3, expires_at = $4, metadata = $5`,
    [userId, roleId, grantedBy, expiresAt, JSON.stringify(metadata || {})]
  )

  logger.info('Role assigned', { userId, roleId, grantedBy })
}

/**
 * Revoke role from user
 */
export async function revokeRole(userId: string, roleId: string): Promise<void> {
  const db = getDb()
  
  await db.query(
    'DELETE FROM user_roles WHERE user_id = $1 AND role_id = $2',
    [userId, roleId]
  )

  logger.info('Role revoked', { userId, roleId })
}

/**
 * Get all roles
 */
export async function getAllRoles(): Promise<Role[]> {
  const db = getDb()
  
  const result = await db.query(
    'SELECT id, name, description, permissions, is_system, created_at, updated_at FROM roles ORDER BY name'
  )

  return result.rows.map(row => ({
    id: row.id,
    name: row.name,
    description: row.description,
    permissions: row.permissions || [],
    isSystem: row.is_system,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  }))
}

/**
 * Create a new role
 */
export async function createRole(
  name: string,
  description?: string,
  permissions: string[] = []
): Promise<Role> {
  const db = getDb()
  
  const result = await db.query(
    `INSERT INTO roles (name, description, permissions)
     VALUES ($1, $2, $3)
     RETURNING id, name, description, permissions, is_system, created_at, updated_at`,
    [name, description, JSON.stringify(permissions)]
  )

  const row = result.rows[0]
  return {
    id: row.id,
    name: row.name,
    description: row.description,
    permissions: row.permissions || [],
    isSystem: row.is_system,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  }
}

/**
 * Check separation of duties
 * Ensures users don't have conflicting roles
 */
export async function checkSeparationOfDuties(userId: string, newRoleId: string): Promise<boolean> {
  const db = getDb()
  
  // Get user's current roles
  const currentRoles = await getUserRoles(userId)
  
  // Get new role
  const newRoleResult = await db.query('SELECT name FROM roles WHERE id = $1', [newRoleId])
  if (newRoleResult.rows.length === 0) {
    return false
  }
  const newRoleName = newRoleResult.rows[0].name

  // Define conflicting role pairs (example)
  const conflicts: Record<string, string[]> = {
    'SYSTEM_ADMIN': [], // System admin can have any role
    'SECURITY_ADMIN': ['TENANT_ADMIN'], // Security admin shouldn't be tenant admin
    'TENANT_ADMIN': ['SECURITY_ADMIN'],
  }

  const conflictingRoles = conflicts[newRoleName] || []
  const hasConflict = currentRoles.some(role => conflictingRoles.includes(role.name))

  if (hasConflict) {
    logger.warn('Separation of duties violation detected', {
      userId,
      newRole: newRoleName,
      conflictingRoles: currentRoles.map(r => r.name),
    })
    return false
  }

  return true
}

