import { Request, Response, NextFunction } from "express";
import { catchAsyncError } from "../../middlewares/catchAsyncError";
import ErrorHandler from "../../middlewares/error";
import database from "../../config/db";

// Helper function to sanitize role data
const sanitizeRole = (role: any) => {
  const { permissions, ...sanitized } = role;
  return sanitized;
};

// Helper function to sanitize permission data
const sanitizePermission = (permission: any) => {
  const { required_privilege_level, is_sensitive, ...sanitized } = permission;
  return sanitized;
};

// controller: get all roles (admin only)
export const getAllRoles = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const { include_system = 'true', include_inactive = 'false' } = req.query;
  
  let whereConditions: string[] = [];
  const values: any[] = [];
  let paramCounter = 1;
  
  if (include_system === 'false') {
    whereConditions.push(`is_system_role = $${paramCounter}`);
    values.push(false);
    paramCounter++;
  }
  
  if (include_inactive === 'false') {
    whereConditions.push(`is_active = $${paramCounter}`);
    values.push(true);
    paramCounter++;
  }
  
  const whereClause = whereConditions.length > 0 
    ? `WHERE ${whereConditions.join(' AND ')}` 
    : '';
  
  const rolesResult = await database.query(
    `SELECT * FROM roles ${whereClause} ORDER BY level DESC, name`,
    values
  );
  
  const roles = rolesResult.rows.map(sanitizeRole);
  
  res.status(200).json({
    success: true,
    data: {
      roles,
      count: roles.length,
    },
  });
});

// controller: get role by ID
export const getRoleById = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const { role_id } = req.params;
  
  const roleResult = await database.query(
    `SELECT * FROM roles WHERE id = $1`,
    [role_id]
  );
  
  if (roleResult.rows.length === 0) {
    return next(new ErrorHandler("Role not found.", 404));
  }
  
  const role = roleResult.rows[0];
  
  // Get permissions for this role
  const permissionsResult = await database.query(
    `SELECT 
      p.*,
      rp.is_allowed,
      rp.constraints
    FROM role_permissions rp
    INNER JOIN permissions p ON rp.permission_id = p.id
    WHERE rp.role_id = $1
    ORDER BY p.category, p.name`,
    [role_id]
  );
  
  res.status(200).json({
    success: true,
    data: {
      role: sanitizeRole(role),
      permissions: permissionsResult.rows.map(sanitizePermission),
      permission_count: permissionsResult.rows.length,
    },
  });
});

// controller: create new role (admin only)
export const createRole = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const {
    name,
    display_name,
    description,
    level,
    permissions,
    is_default = false,
    is_active = true
  } = req.body;
  
  // Validate required fields
  if (!name || !display_name || !level) {
    return next(new ErrorHandler("Name, display_name, and level are required.", 400));
  }
  
  // Validate name format
  const nameRegex = /^[a-z_]+$/;
  if (!nameRegex.test(name)) {
    return next(new ErrorHandler("Role name must contain only lowercase letters and underscores.", 400));
  }
  
  // Check if role already exists
  const existingRole = await database.query(
    `SELECT id FROM roles WHERE name = $1`,
    [name]
  );
  
  if (existingRole.rows.length > 0) {
    return next(new ErrorHandler("Role with this name already exists.", 400));
  }
  
  // Validate level range
  if (level < 0 || level > 100) {
    return next(new ErrorHandler("Level must be between 0 and 100.", 400));
  }
  
  // Create role
  const roleResult = await database.query(
    `INSERT INTO roles (
      name,
      display_name,
      description,
      level,
      permissions,
      is_default,
      is_active,
      created_at,
      updated_at
    ) VALUES ($1, $2, $3, $4, $5, $6, $7, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
    RETURNING *`,
    [
      name,
      display_name,
      description || null,
      level,
      permissions || {},
      is_default,
      is_active
    ]
  );
  
  const role = roleResult.rows[0];
  
  // Log the action
  const admin = (req as any).user;
  await database.query(
    `INSERT INTO permission_audit_log (
      action,
      target_role_id,
      performed_by,
      new_value
    ) VALUES ($1, $2, $3, $4)`,
    [
      'update_role',
      role.id,
      admin.id,
      JSON.stringify({
        name: role.name,
        display_name: role.display_name,
        level: role.level
      })
    ]
  );
  
  res.status(201).json({
    success: true,
    message: "Role created successfully.",
    data: {
      role: sanitizeRole(role),
    },
  });
});

// controller: update role (admin only)
export const updateRole = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const { role_id } = req.params;
  const updateData = req.body;
  
  // Get current role
  const currentRoleResult = await database.query(
    `SELECT * FROM roles WHERE id = $1`,
    [role_id]
  );
  
  if (currentRoleResult.rows.length === 0) {
    return next(new ErrorHandler("Role not found.", 404));
  }
  
  const currentRole = currentRoleResult.rows[0];
  
  // Don't allow modifying system roles
  if (currentRole.is_system_role) {
    return next(new ErrorHandler("Cannot modify system roles.", 403));
  }
  
  // Remove fields that cannot be updated
  const disallowedFields = ['id', 'name', 'is_system_role', 'created_at'];
  Object.keys(updateData).forEach(key => {
    if (disallowedFields.includes(key)) {
      delete updateData[key];
    }
  });
  
  if (Object.keys(updateData).length === 0) {
    return next(new ErrorHandler("No valid fields to update.", 400));
  }
  
  // Prepare update query
  const setClauses: string[] = [];
  const values: any[] = [];
  let paramCounter = 1;
  
  Object.entries(updateData).forEach(([key, value]) => {
    setClauses.push(`${key} = $${paramCounter}`);
    
    if (typeof value === 'object') {
      values.push(JSON.stringify(value));
    } else {
      values.push(value);
    }
    
    paramCounter++;
  });
  
  setClauses.push(`updated_at = CURRENT_TIMESTAMP`);
  
  const query = `
    UPDATE roles 
    SET ${setClauses.join(', ')}
    WHERE id = $${paramCounter}
    RETURNING *
  `;
  
  values.push(role_id);
  
  const roleResult = await database.query(query, values);
  const updatedRole = roleResult.rows[0];
  
  // Log the action
  const admin = (req as any).user;
  await database.query(
    `INSERT INTO permission_audit_log (
      action,
      target_role_id,
      performed_by,
      previous_value,
      new_value
    ) VALUES ($1, $2, $3, $4, $5)`,
    [
      'update_role',
      role_id,
      admin.id,
      JSON.stringify(currentRole),
      JSON.stringify(updatedRole)
    ]
  );
  
  res.status(200).json({
    success: true,
    message: "Role updated successfully.",
    data: {
      role: sanitizeRole(updatedRole),
    },
  });
});

// controller: delete role (admin only)
export const deleteRole = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const { role_id } = req.params;
  
  // Check if role exists
  const roleResult = await database.query(
    `SELECT * FROM roles WHERE id = $1`,
    [role_id]
  );
  
  if (roleResult.rows.length === 0) {
    return next(new ErrorHandler("Role not found.", 404));
  }
  
  const role = roleResult.rows[0];
  
  // Don't allow deleting system roles or default role
  if (role.is_system_role) {
    return next(new ErrorHandler("Cannot delete system roles.", 403));
  }
  
  if (role.is_default) {
    return next(new ErrorHandler("Cannot delete default role.", 403));
  }
  
  // Check if role is assigned to any users
  const assignmentsResult = await database.query(
    `SELECT COUNT(*) as assignment_count 
     FROM role_assignments 
     WHERE role_id = $1 AND is_active = true`,
    [role_id]
  );
  
  const assignmentCount = parseInt(assignmentsResult.rows[0]?.assignment_count || 0);
  
  if (assignmentCount > 0) {
    return next(new ErrorHandler("Cannot delete role that is assigned to users.", 400));
  }
  
  // Delete role permissions first
  await database.query(
    `DELETE FROM role_permissions WHERE role_id = $1`,
    [role_id]
  );
  
  // Delete role
  await database.query(
    `DELETE FROM roles WHERE id = $1`,
    [role_id]
  );
  
  // Log the action
  const admin = (req as any).user;
  await database.query(
    `INSERT INTO permission_audit_log (
      action,
      target_role_id,
      performed_by,
      previous_value
    ) VALUES ($1, $2, $3, $4)`,
    [
      'update_role',
      role_id,
      admin.id,
      JSON.stringify(role)
    ]
  );
  
  res.status(200).json({
    success: true,
    message: "Role deleted successfully.",
  });
});

// controller: get all permissions
export const getAllPermissions = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const { category, action, resource } = req.query;
  
  let whereConditions: string[] = [];
  const values: any[] = [];
  let paramCounter = 1;
  
  if (category) {
    whereConditions.push(`category = $${paramCounter}`);
    values.push(category);
    paramCounter++;
  }
  
  if (action) {
    whereConditions.push(`action = $${paramCounter}`);
    values.push(action);
    paramCounter++;
  }
  
  if (resource) {
    whereConditions.push(`resource = $${paramCounter}`);
    values.push(resource);
    paramCounter++;
  }
  
  const whereClause = whereConditions.length > 0 
    ? `WHERE ${whereConditions.join(' AND ')}` 
    : '';
  
  const permissionsResult = await database.query(
    `SELECT * FROM permissions ${whereClause} ORDER BY category, name`,
    values
  );
  
  const permissions = permissionsResult.rows.map(sanitizePermission);
  
  res.status(200).json({
    success: true,
    data: {
      permissions,
      count: permissions.length,
    },
  });
});

// controller: assign role to user (admin only)
export const assignRoleToUser = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const admin = (req as any).user;
  const { user_id, role_name, is_primary, expires_at, reason } = req.body;
  
  if (!user_id || !role_name) {
    return next(new ErrorHandler("User ID and role name are required.", 400));
  }
  
  // Use database function to assign role
  const result = await database.query(
    `SELECT * FROM assign_role_to_user($1, $2, $3, $4, $5, $6)`,
    [user_id, role_name, admin.id, is_primary || false, expires_at || null, reason || null]
  );
  
  const assignmentResult = result.rows[0]?.assign_role_to_user;
  
  if (!assignmentResult.success) {
    return next(new ErrorHandler(assignmentResult.message, 400));
  }
  
  res.status(200).json({
    success: true,
    message: "Role assigned successfully.",
    data: assignmentResult.data,
  });
});

// controller: remove role from user (admin only)
export const removeRoleFromUser = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const admin = (req as any).user;
  const { user_id, role_name, reason } = req.body;
  
  if (!user_id || !role_name) {
    return next(new ErrorHandler("User ID and role name are required.", 400));
  }
  
  // Use database function to remove role
  const result = await database.query(
    `SELECT * FROM remove_role_from_user($1, $2, $3, $4)`,
    [user_id, role_name, admin.id, reason || null]
  );
  
  const removalResult = result.rows[0]?.remove_role_from_user;
  
  if (!removalResult.success) {
    return next(new ErrorHandler(removalResult.message, 400));
  }
  
  res.status(200).json({
    success: true,
    message: "Role removed successfully.",
    data: removalResult.data,
  });
});

// controller: get user roles and permissions
export const getUserPermissions = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const { user_id } = req.params;
  
  // Get user info
  const userResult = await database.query(
    `SELECT id, username, email, account_type FROM accounts WHERE id = $1`,
    [user_id]
  );
  
  if (userResult.rows.length === 0) {
    return next(new ErrorHandler("User not found.", 404));
  }
  
  const user = userResult.rows[0];
  
  // Get user roles
  const rolesResult = await database.query(
    `SELECT * FROM get_user_roles($1)`,
    [user_id]
  );
  
  const roles = rolesResult.rows;
  
  // Get user permissions
  const permissionsResult = await database.query(
    `SELECT * FROM get_user_permissions($1)`,
    [user_id]
  );
  
  const permissions = permissionsResult.rows;
  
  // Get user's highest level
  const levelResult = await database.query(
    `SELECT COALESCE(MAX(r.level), 0) as user_level
     FROM role_assignments ra
     INNER JOIN roles r ON ra.role_id = r.id
     WHERE ra.account_id = $1 
       AND ra.is_active = true
       AND (ra.expires_at IS NULL OR ra.expires_at > CURRENT_TIMESTAMP)`,
    [user_id]
  );
  
  const userLevel = parseInt(levelResult.rows[0]?.user_level || 0);
  
  res.status(200).json({
    success: true,
    data: {
      user,
      roles,
      permissions,
      user_level: userLevel,
      total_roles: roles.length,
      total_permissions: permissions.length,
    },
  });
});

// controller: update role permissions (admin only)
export const updateRolePermissions = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const { role_id } = req.params;
  const { permissions } = req.body;
  
  if (!permissions || !Array.isArray(permissions)) {
    return next(new ErrorHandler("Permissions array is required.", 400));
  }
  
  // Check if role exists
  const roleResult = await database.query(
    `SELECT * FROM roles WHERE id = $1`,
    [role_id]
  );
  
  if (roleResult.rows.length === 0) {
    return next(new ErrorHandler("Role not found.", 404));
  }
  
  const role = roleResult.rows[0];
  
  // Don't allow modifying system roles
  if (role.is_system_role) {
    return next(new ErrorHandler("Cannot modify permissions of system roles.", 403));
  }
  
  // Begin transaction
  await database.query('BEGIN');
  
  try {
    // Remove all existing permissions for this role
    await database.query(
      `DELETE FROM role_permissions WHERE role_id = $1`,
      [role_id]
    );
    
    // Add new permissions
    for (const perm of permissions) {
      const { permission_id, is_allowed = true, constraints = {} } = perm;
      
      // Verify permission exists
      const permResult = await database.query(
        `SELECT id FROM permissions WHERE id = $1`,
        [permission_id]
      );
      
      if (permResult.rows.length === 0) {
        await database.query('ROLLBACK');
        return next(new ErrorHandler(`Permission not found: ${permission_id}`, 400));
      }
      
      await database.query(
        `INSERT INTO role_permissions (role_id, permission_id, is_allowed, constraints)
         VALUES ($1, $2, $3, $4)`,
        [role_id, permission_id, is_allowed, constraints]
      );
    }
    
    // Log the action
    const admin = (req as any).user;
    await database.query(
      `INSERT INTO permission_audit_log (
        action,
        target_role_id,
        performed_by,
        new_value
      ) VALUES ($1, $2, $3, $4)`,
      [
        'update_role',
        role_id,
        admin.id,
        JSON.stringify({ permissions_updated: permissions.length })
      ]
    );
    
    await database.query('COMMIT');
    
    // Get updated permissions
    const updatedPermissionsResult = await database.query(
      `SELECT 
        p.*,
        rp.is_allowed,
        rp.constraints
      FROM role_permissions rp
      INNER JOIN permissions p ON rp.permission_id = p.id
      WHERE rp.role_id = $1
      ORDER BY p.category, p.name`,
      [role_id]
    );
    
    res.status(200).json({
      success: true,
      message: "Role permissions updated successfully.",
      data: {
        role: sanitizeRole(role),
        permissions: updatedPermissionsResult.rows.map(sanitizePermission),
        permission_count: updatedPermissionsResult.rows.length,
      },
    });
    
  } catch (error) {
    await database.query('ROLLBACK');
    throw error;
  }
});

// controller: check user permission
export const checkUserPermission = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const { user_id } = req.params;
  const { permission_name } = req.query;
  
  if (!permission_name || typeof permission_name !== 'string') {
    return next(new ErrorHandler("Permission name is required.", 400));
  }
  
  // Check if user exists
  const userResult = await database.query(
    `SELECT id FROM accounts WHERE id = $1`,
    [user_id]
  );
  
  if (userResult.rows.length === 0) {
    return next(new ErrorHandler("User not found.", 404));
  }
  
  // Check permission
  const result = await database.query(
    `SELECT has_permission($1, $2) as has_permission`,
    [user_id, permission_name]
  );
  
  const hasPermission = result.rows[0]?.has_permission;
  
  res.status(200).json({
    success: true,
    data: {
      user_id,
      permission_name,
      has_permission: hasPermission,
    },
  });
});

// controller: get audit logs (admin only)
export const getAuditLogs = catchAsyncError(async (req: Request, res: Response, next: NextFunction) => {
  const {
    action,
    target_user_id,
    performer_id,
    start_date,
    end_date,
    page = 1,
    limit = 50
  } = req.query;
  
  const offset = (Number(page) - 1) * Number(limit);
  
  let whereConditions: string[] = [];
  const values: any[] = [];
  let paramCounter = 1;
  
  if (action) {
    whereConditions.push(`action = $${paramCounter}`);
    values.push(action);
    paramCounter++;
  }
  
  if (target_user_id) {
    whereConditions.push(`target_account_id = $${paramCounter}`);
    values.push(target_user_id);
    paramCounter++;
  }
  
  if (performer_id) {
    whereConditions.push(`performed_by = $${paramCounter}`);
    values.push(performer_id);
    paramCounter++;
  }
  
  if (start_date) {
    whereConditions.push(`created_at >= $${paramCounter}`);
    values.push(new Date(start_date as string));
    paramCounter++;
  }
  
  if (end_date) {
    whereConditions.push(`created_at <= $${paramCounter}`);
    values.push(new Date(end_date as string));
    paramCounter++;
  }
  
  const whereClause = whereConditions.length > 0 
    ? `WHERE ${whereConditions.join(' AND ')}` 
    : '';
  
  // Get count
  const countQuery = `
    SELECT COUNT(*) as total FROM permission_audit_log ${whereClause}
  `;
  
  const countResult = await database.query(countQuery, values);
  const total = parseInt(countResult.rows[0]?.total || 0);
  
  // Get logs with user details
  const logsQuery = `
    SELECT 
      al.*,
      p.username as performer_username,
      p.display_name as performer_display_name,
      t.username as target_username,
      t.display_name as target_display_name,
      r.name as role_name,
      r.display_name as role_display_name
    FROM permission_audit_log al
    LEFT JOIN accounts p ON al.performed_by = p.id
    LEFT JOIN accounts t ON al.target_account_id = t.id
    LEFT JOIN roles r ON al.target_role_id = r.id
    ${whereClause}
    ORDER BY al.created_at DESC
    LIMIT $${paramCounter} OFFSET $${paramCounter + 1}
  `;
  
  values.push(Number(limit));
  values.push(offset);
  
  const logsResult = await database.query(logsQuery, values);
  
  const logs = logsResult.rows.map((log: any) => ({
    id: log.id,
    action: log.action,
    created_at: log.created_at,
    reason: log.reason,
    performer: log.performed_by ? {
      id: log.performed_by,
      username: log.performer_username,
      display_name: log.performer_display_name,
    } : null,
    target_user: log.target_account_id ? {
      id: log.target_account_id,
      username: log.target_username,
      display_name: log.target_display_name,
    } : null,
    target_role: log.target_role_id ? {
      id: log.target_role_id,
      name: log.role_name,
      display_name: log.role_display_name,
    } : null,
    previous_value: log.previous_value,
    new_value: log.new_value,
  }));
  
  res.status(200).json({
    success: true,
    data: {
      logs,
      pagination: {
        page: Number(page),
        limit: Number(limit),
        total,
        pages: Math.ceil(total / Number(limit)),
      },
    },
  });
});