auth_framework/migration/
planners.rs

1//! Migration plan generators
2//!
3//! This module provides migration plan generation based on legacy
4//! system analysis and selected migration strategy.
5
6use super::{
7    BackupType, LegacySystemAnalysis, MigrationConfig, MigrationError, MigrationOperation,
8    MigrationPhase, MigrationPlan, MigrationStrategy, RiskLevel, RollbackPhase, RollbackPlan,
9    UserMigration, ValidationStep, ValidationType,
10};
11use std::collections::HashMap;
12use uuid::Uuid;
13
14/// Generate comprehensive migration plan
15pub async fn generate_migration_plan(
16    analysis: &LegacySystemAnalysis,
17    strategy: Option<MigrationStrategy>,
18    _config: &MigrationConfig,
19) -> Result<MigrationPlan, MigrationError> {
20    let selected_strategy = strategy.unwrap_or_else(|| analysis.recommended_strategy.clone());
21
22    let plan_id = Uuid::new_v4().to_string();
23
24    let mut plan = MigrationPlan {
25        id: plan_id,
26        source_analysis: analysis.clone(),
27        strategy: selected_strategy.clone(),
28        phases: Vec::new(),
29        role_mappings: HashMap::new(),
30        permission_mappings: HashMap::new(),
31        user_migrations: Vec::new(),
32        pre_validation_steps: Vec::new(),
33        post_validation_steps: Vec::new(),
34        rollback_plan: RollbackPlan {
35            phases: Vec::new(),
36            backup_locations: Vec::new(),
37            recovery_time_objective: chrono::Duration::hours(4),
38            manual_steps: Vec::new(),
39        },
40        estimated_duration: chrono::Duration::hours(1),
41        risk_level: assess_risk_level(analysis),
42        downtime_required: None,
43    };
44
45    match selected_strategy {
46        MigrationStrategy::DirectMapping => {
47            generate_direct_mapping_plan(&mut plan, analysis).await?;
48        }
49        MigrationStrategy::GradualMigration => {
50            generate_gradual_migration_plan(&mut plan, analysis).await?;
51        }
52        MigrationStrategy::Rebuild => {
53            generate_rebuild_plan(&mut plan, analysis).await?;
54        }
55        MigrationStrategy::Custom(ref description) => {
56            generate_custom_plan(&mut plan, analysis, description).await?;
57        }
58    }
59
60    // Generate validation steps
61    generate_validation_steps(&mut plan, analysis);
62
63    // Generate rollback plan
64    generate_rollback_plan(&mut plan, analysis);
65
66    // Estimate duration and assess risk
67    estimate_migration_duration(&mut plan);
68
69    Ok(plan)
70}
71
72/// Generate direct mapping migration plan
73async fn generate_direct_mapping_plan(
74    plan: &mut MigrationPlan,
75    analysis: &LegacySystemAnalysis,
76) -> Result<(), MigrationError> {
77    // Phase 1: Backup and preparation
78    let backup_phase = MigrationPhase {
79        id: "backup".to_string(),
80        name: "Backup and Preparation".to_string(),
81        description: "Create backups and prepare for migration".to_string(),
82        order: 1,
83        operations: vec![
84            MigrationOperation::Backup {
85                backup_location: std::path::PathBuf::from("./backups/pre_migration"),
86                backup_type: BackupType::Full,
87            },
88            MigrationOperation::ValidateIntegrity {
89                validation_type: "pre_migration_check".to_string(),
90                parameters: HashMap::new(),
91            },
92        ],
93        dependencies: Vec::new(),
94        estimated_duration: chrono::Duration::minutes(30),
95        rollback_operations: Vec::new(),
96    };
97
98    // Phase 2: Create roles
99    let mut role_operations = Vec::new();
100    for role in &analysis.roles {
101        // Direct mapping: use existing role structure
102        let role_id = format!("migrated_{}", role.id);
103        plan.role_mappings.insert(role.id.clone(), role_id.clone());
104
105        role_operations.push(MigrationOperation::CreateRole {
106            role_id: role_id.clone(),
107            name: role.name.clone(),
108            description: role.description.clone(),
109            permissions: role.permissions.clone(),
110            parent_role: role.parent_roles.first().map(|p| format!("migrated_{}", p)),
111        });
112    }
113
114    let roles_phase = MigrationPhase {
115        id: "create_roles".to_string(),
116        name: "Create Roles".to_string(),
117        description: "Create all roles in the new system".to_string(),
118        order: 2,
119        operations: role_operations,
120        dependencies: vec!["backup".to_string()],
121        estimated_duration: chrono::Duration::minutes(analysis.role_count as i64 * 2),
122        rollback_operations: Vec::new(),
123    };
124
125    // Phase 3: Create permissions
126    let mut permission_operations = Vec::new();
127    for permission in &analysis.permissions {
128        let permission_id = format!("migrated_{}", permission.id);
129        plan.permission_mappings
130            .insert(permission.id.clone(), permission_id.clone());
131
132        permission_operations.push(MigrationOperation::CreatePermission {
133            permission_id: permission_id.clone(),
134            action: permission.action.clone(),
135            resource: permission.resource.clone(),
136            conditions: permission.conditions.clone(),
137        });
138    }
139
140    let permissions_phase = MigrationPhase {
141        id: "create_permissions".to_string(),
142        name: "Create Permissions".to_string(),
143        description: "Create all permissions in the new system".to_string(),
144        order: 3,
145        operations: permission_operations,
146        dependencies: vec!["create_roles".to_string()],
147        estimated_duration: chrono::Duration::minutes(analysis.permission_count as i64),
148        rollback_operations: Vec::new(),
149    };
150
151    // Phase 4: Migrate user assignments
152    let mut user_operations = Vec::new();
153    for assignment in &analysis.user_assignments {
154        if let Some(role_id) = &assignment.role_id
155            && let Some(new_role_id) = plan.role_mappings.get(role_id)
156        {
157            user_operations.push(MigrationOperation::AssignUserRole {
158                user_id: assignment.user_id.clone(),
159                role_id: new_role_id.clone(),
160                expiration: assignment.expiration,
161            });
162
163            plan.user_migrations.push(UserMigration {
164                user_id: assignment.user_id.clone(),
165                legacy_roles: vec![role_id.clone()],
166                legacy_permissions: assignment.permissions.clone(),
167                new_roles: vec![new_role_id.clone()],
168                migration_notes: Some("Direct mapping migration".to_string()),
169            });
170        }
171    }
172
173    let users_phase = MigrationPhase {
174        id: "migrate_users".to_string(),
175        name: "Migrate User Assignments".to_string(),
176        description: "Migrate all user role assignments".to_string(),
177        order: 4,
178        operations: user_operations,
179        dependencies: vec!["create_permissions".to_string()],
180        estimated_duration: chrono::Duration::minutes(analysis.user_assignment_count as i64),
181        rollback_operations: Vec::new(),
182    };
183
184    // Phase 5: Final validation
185    let validation_phase = MigrationPhase {
186        id: "final_validation".to_string(),
187        name: "Final Validation".to_string(),
188        description: "Validate migration completeness and integrity".to_string(),
189        order: 5,
190        operations: vec![MigrationOperation::ValidateIntegrity {
191            validation_type: "post_migration_check".to_string(),
192            parameters: HashMap::new(),
193        }],
194        dependencies: vec!["migrate_users".to_string()],
195        estimated_duration: chrono::Duration::minutes(15),
196        rollback_operations: Vec::new(),
197    };
198
199    plan.phases = vec![
200        backup_phase,
201        roles_phase,
202        permissions_phase,
203        users_phase,
204        validation_phase,
205    ];
206    plan.downtime_required = Some(chrono::Duration::minutes(10));
207
208    Ok(())
209}
210
211/// Generate gradual migration plan
212async fn generate_gradual_migration_plan(
213    plan: &mut MigrationPlan,
214    analysis: &LegacySystemAnalysis,
215) -> Result<(), MigrationError> {
216    // Phase 1: Setup parallel systems
217    let setup_phase = MigrationPhase {
218        id: "setup_parallel".to_string(),
219        name: "Setup Parallel Systems".to_string(),
220        description: "Setup new role system alongside existing system".to_string(),
221        order: 1,
222        operations: vec![MigrationOperation::Backup {
223            backup_location: std::path::PathBuf::from("./backups/pre_gradual_migration"),
224            backup_type: BackupType::Full,
225        }],
226        dependencies: Vec::new(),
227        estimated_duration: chrono::Duration::hours(1),
228        rollback_operations: Vec::new(),
229    };
230
231    // Phase 2: Migrate critical roles first
232    let critical_roles = identify_critical_roles(analysis);
233    let mut critical_role_operations = Vec::new();
234
235    for role_id in &critical_roles {
236        if let Some(role) = analysis.roles.iter().find(|r| r.id == *role_id) {
237            let new_role_id = format!("migrated_{}", role.id);
238            plan.role_mappings
239                .insert(role.id.clone(), new_role_id.clone());
240
241            critical_role_operations.push(MigrationOperation::CreateRole {
242                role_id: new_role_id,
243                name: role.name.clone(),
244                description: role.description.clone(),
245                permissions: role.permissions.clone(),
246                parent_role: None, // Handle hierarchy in later phase
247            });
248        }
249    }
250
251    let critical_phase = MigrationPhase {
252        id: "migrate_critical_roles".to_string(),
253        name: "Migrate Critical Roles".to_string(),
254        description: "Migrate business-critical roles first".to_string(),
255        order: 2,
256        operations: critical_role_operations,
257        dependencies: vec!["setup_parallel".to_string()],
258        estimated_duration: chrono::Duration::minutes(critical_roles.len() as i64 * 5),
259        rollback_operations: Vec::new(),
260    };
261
262    // Phase 3: Migrate remaining roles in batches
263    let remaining_roles: Vec<_> = analysis
264        .roles
265        .iter()
266        .filter(|role| !critical_roles.contains(&role.id))
267        .collect();
268
269    let batch_size = 10;
270    let mut batch_phases = Vec::new();
271
272    for (batch_idx, batch) in remaining_roles.chunks(batch_size).enumerate() {
273        let mut batch_operations = Vec::new();
274
275        for role in batch {
276            let new_role_id = format!("migrated_{}", role.id);
277            plan.role_mappings
278                .insert(role.id.clone(), new_role_id.clone());
279
280            batch_operations.push(MigrationOperation::CreateRole {
281                role_id: new_role_id,
282                name: role.name.clone(),
283                description: role.description.clone(),
284                permissions: role.permissions.clone(),
285                parent_role: role.parent_roles.first().map(|p| format!("migrated_{}", p)),
286            });
287        }
288
289        let phase = MigrationPhase {
290            id: format!("migrate_batch_{}", batch_idx + 1),
291            name: format!("Migrate Role Batch {}", batch_idx + 1),
292            description: format!(
293                "Migrate roles in batch {} of {}",
294                batch_idx + 1,
295                remaining_roles.len().div_ceil(batch_size)
296            ),
297            order: 3 + batch_idx as u32,
298            operations: batch_operations,
299            dependencies: if batch_idx == 0 {
300                vec!["migrate_critical_roles".to_string()]
301            } else {
302                vec![format!("migrate_batch_{}", batch_idx)]
303            },
304            estimated_duration: chrono::Duration::minutes(batch.len() as i64 * 3),
305            rollback_operations: Vec::new(),
306        };
307
308        batch_phases.push(phase);
309    }
310
311    // Phase N: Migrate users gradually
312    let user_batches: Vec<_> = analysis.user_assignments.chunks(50).collect();
313    let mut user_phases = Vec::new();
314
315    for (batch_idx, batch) in user_batches.iter().enumerate() {
316        let mut user_operations = Vec::new();
317
318        for assignment in *batch {
319            if let Some(role_id) = &assignment.role_id
320                && let Some(new_role_id) = plan.role_mappings.get(role_id)
321            {
322                user_operations.push(MigrationOperation::AssignUserRole {
323                    user_id: assignment.user_id.clone(),
324                    role_id: new_role_id.clone(),
325                    expiration: assignment.expiration,
326                });
327            }
328        }
329
330        let phase = MigrationPhase {
331            id: format!("migrate_users_batch_{}", batch_idx + 1),
332            name: format!("Migrate User Batch {}", batch_idx + 1),
333            description: format!("Migrate user assignments in batch {}", batch_idx + 1),
334            order: 100 + batch_idx as u32,
335            operations: user_operations,
336            dependencies: vec![format!("migrate_batch_{}", batch_phases.len())],
337            estimated_duration: chrono::Duration::minutes(batch.len() as i64 * 2),
338            rollback_operations: Vec::new(),
339        };
340
341        user_phases.push(phase);
342    }
343
344    // Combine all phases
345    plan.phases = vec![setup_phase, critical_phase];
346    plan.phases.extend(batch_phases);
347    plan.phases.extend(user_phases);
348
349    // No downtime required for gradual migration
350    plan.downtime_required = None;
351
352    Ok(())
353}
354
355/// Generate rebuild migration plan
356async fn generate_rebuild_plan(
357    plan: &mut MigrationPlan,
358    analysis: &LegacySystemAnalysis,
359) -> Result<(), MigrationError> {
360    // Phase 1: Analysis and design
361    let analysis_phase = MigrationPhase {
362        id: "analyze_and_design".to_string(),
363        name: "Analyze and Design New Structure".to_string(),
364        description: "Analyze existing system and design optimized role structure".to_string(),
365        order: 1,
366        operations: vec![MigrationOperation::Backup {
367            backup_location: std::path::PathBuf::from("./backups/pre_rebuild"),
368            backup_type: BackupType::Full,
369        }],
370        dependencies: Vec::new(),
371        estimated_duration: chrono::Duration::hours(2),
372        rollback_operations: Vec::new(),
373    };
374
375    // Phase 2: Create consolidated roles
376    let consolidated_roles = consolidate_roles(analysis);
377    let mut role_operations = Vec::new();
378
379    for (new_role_id, role_data) in &consolidated_roles {
380        plan.role_mappings.extend(role_data.legacy_mappings.clone());
381
382        role_operations.push(MigrationOperation::CreateRole {
383            role_id: new_role_id.clone(),
384            name: role_data.name.clone(),
385            description: role_data.description.clone(),
386            permissions: role_data.permissions.clone(),
387            parent_role: role_data.parent_role.clone(),
388        });
389    }
390
391    let roles_phase = MigrationPhase {
392        id: "create_consolidated_roles".to_string(),
393        name: "Create Consolidated Roles".to_string(),
394        description: "Create optimized, consolidated role structure".to_string(),
395        order: 2,
396        operations: role_operations,
397        dependencies: vec!["analyze_and_design".to_string()],
398        estimated_duration: chrono::Duration::minutes(consolidated_roles.len() as i64 * 3),
399        rollback_operations: Vec::new(),
400    };
401
402    // Phase 3: Migrate users to new structure
403    let mut user_operations = Vec::new();
404    for assignment in &analysis.user_assignments {
405        if let Some(role_id) = &assignment.role_id
406            && let Some(new_role_id) = plan.role_mappings.get(role_id)
407        {
408            user_operations.push(MigrationOperation::AssignUserRole {
409                user_id: assignment.user_id.clone(),
410                role_id: new_role_id.clone(),
411                expiration: assignment.expiration,
412            });
413
414            plan.user_migrations.push(UserMigration {
415                user_id: assignment.user_id.clone(),
416                legacy_roles: vec![role_id.clone()],
417                legacy_permissions: assignment.permissions.clone(),
418                new_roles: vec![new_role_id.clone()],
419                migration_notes: Some("Rebuilt with consolidated roles".to_string()),
420            });
421        }
422    }
423
424    let users_phase = MigrationPhase {
425        id: "migrate_to_new_structure".to_string(),
426        name: "Migrate to New Structure".to_string(),
427        description: "Migrate users to the new consolidated role structure".to_string(),
428        order: 3,
429        operations: user_operations,
430        dependencies: vec!["create_consolidated_roles".to_string()],
431        estimated_duration: chrono::Duration::minutes(analysis.user_assignment_count as i64),
432        rollback_operations: Vec::new(),
433    };
434
435    plan.phases = vec![analysis_phase, roles_phase, users_phase];
436    plan.downtime_required = Some(chrono::Duration::hours(1));
437
438    Ok(())
439}
440
441/// Generate custom migration plan
442async fn generate_custom_plan(
443    plan: &mut MigrationPlan,
444    _analysis: &LegacySystemAnalysis,
445    _description: &str,
446) -> Result<(), MigrationError> {
447    // For custom plans, create a template that can be manually customized
448    let template_phase = MigrationPhase {
449        id: "custom_migration".to_string(),
450        name: "Custom Migration Template".to_string(),
451        description: "Template for custom migration - requires manual customization".to_string(),
452        order: 1,
453        operations: vec![MigrationOperation::Backup {
454            backup_location: std::path::PathBuf::from("./backups/pre_custom_migration"),
455            backup_type: BackupType::Full,
456        }],
457        dependencies: Vec::new(),
458        estimated_duration: chrono::Duration::hours(4),
459        rollback_operations: Vec::new(),
460    };
461
462    plan.phases = vec![template_phase];
463    plan.downtime_required = Some(chrono::Duration::hours(2));
464
465    // Add note that this requires manual customization
466    plan.user_migrations.push(UserMigration {
467        user_id: "TEMPLATE".to_string(),
468        legacy_roles: vec!["REQUIRES_MANUAL_MAPPING".to_string()],
469        legacy_permissions: vec!["REQUIRES_MANUAL_MAPPING".to_string()],
470        new_roles: vec!["REQUIRES_MANUAL_MAPPING".to_string()],
471        migration_notes: Some(
472            "Custom migration plan requires manual customization based on specific requirements"
473                .to_string(),
474        ),
475    });
476
477    Ok(())
478}
479
480/// Identify critical roles that should be migrated first
481fn identify_critical_roles(analysis: &LegacySystemAnalysis) -> Vec<String> {
482    let mut critical_roles = Vec::new();
483
484    // Roles with many permissions are likely critical
485    for role in &analysis.roles {
486        if role.permissions.len() > 5 {
487            critical_roles.push(role.id.clone());
488        }
489    }
490
491    // Roles that are parents to other roles are critical
492    let parent_roles: std::collections::HashSet<_> = analysis
493        .roles
494        .iter()
495        .flat_map(|role| &role.parent_roles)
496        .collect();
497
498    for parent_role in parent_roles {
499        if !critical_roles.contains(parent_role) {
500            critical_roles.push(parent_role.clone());
501        }
502    }
503
504    // If no critical roles identified, pick the first few
505    if critical_roles.is_empty() {
506        critical_roles.extend(analysis.roles.iter().take(3).map(|role| role.id.clone()));
507    }
508
509    critical_roles
510}
511
512/// Consolidated role data for rebuild strategy
513#[derive(Debug, Clone)]
514struct ConsolidatedRole {
515    name: String,
516    description: Option<String>,
517    permissions: Vec<String>,
518    parent_role: Option<String>,
519    legacy_mappings: HashMap<String, String>,
520}
521
522/// Consolidate roles for rebuild strategy
523fn consolidate_roles(analysis: &LegacySystemAnalysis) -> HashMap<String, ConsolidatedRole> {
524    let mut consolidated = HashMap::new();
525
526    // Group roles by similar permission sets
527    let mut permission_groups: HashMap<Vec<String>, Vec<&super::LegacyRole>> = HashMap::new();
528
529    for role in &analysis.roles {
530        let mut permissions = role.permissions.clone();
531        permissions.sort();
532
533        permission_groups.entry(permissions).or_default().push(role);
534    }
535
536    // Create consolidated roles
537    for (permissions, roles) in permission_groups {
538        if roles.len() == 1 {
539            // Single role, keep as-is
540            let role = roles[0];
541            let new_id = format!("consolidated_{}", role.id);
542            let mut mappings = HashMap::new();
543            mappings.insert(role.id.clone(), new_id.clone());
544
545            consolidated.insert(
546                new_id.clone(),
547                ConsolidatedRole {
548                    name: role.name.clone(),
549                    description: role.description.clone(),
550                    permissions: role.permissions.clone(),
551                    parent_role: role.parent_roles.first().cloned(),
552                    legacy_mappings: mappings,
553                },
554            );
555        } else {
556            // Multiple roles with same permissions, consolidate them
557            let consolidated_name = format!("Consolidated_{}", roles[0].name);
558            let new_id = format!("consolidated_group_{}", roles[0].id);
559            let mut mappings = HashMap::new();
560
561            for role in &roles {
562                mappings.insert(role.id.clone(), new_id.clone());
563            }
564
565            consolidated.insert(
566                new_id.clone(),
567                ConsolidatedRole {
568                    name: consolidated_name,
569                    description: Some(format!(
570                        "Consolidated from: {}",
571                        roles
572                            .iter()
573                            .map(|r| r.name.as_str())
574                            .collect::<Vec<_>>()
575                            .join(", ")
576                    )),
577                    permissions,
578                    parent_role: None, // Simplified hierarchy
579                    legacy_mappings: mappings,
580                },
581            );
582        }
583    }
584
585    consolidated
586}
587
588/// Generate validation steps for migration plan
589fn generate_validation_steps(plan: &mut MigrationPlan, analysis: &LegacySystemAnalysis) {
590    // Pre-migration validation
591    plan.pre_validation_steps = vec![
592        ValidationStep {
593            id: "backup_validation".to_string(),
594            name: "Backup Validation".to_string(),
595            description: "Verify backup integrity and completeness".to_string(),
596            validation_type: ValidationType::Custom("backup_check".to_string()),
597            parameters: HashMap::new(),
598            required: true,
599        },
600        ValidationStep {
601            id: "system_health_check".to_string(),
602            name: "System Health Check".to_string(),
603            description: "Verify system is ready for migration".to_string(),
604            validation_type: ValidationType::Custom("health_check".to_string()),
605            parameters: HashMap::new(),
606            required: true,
607        },
608    ];
609
610    // Post-migration validation
611    plan.post_validation_steps = vec![
612        ValidationStep {
613            id: "role_hierarchy_validation".to_string(),
614            name: "Role Hierarchy Validation".to_string(),
615            description: "Verify role hierarchy integrity".to_string(),
616            validation_type: ValidationType::HierarchyIntegrity,
617            parameters: HashMap::new(),
618            required: true,
619        },
620        ValidationStep {
621            id: "permission_consistency_validation".to_string(),
622            name: "Permission Consistency Validation".to_string(),
623            description: "Verify permission assignments are consistent".to_string(),
624            validation_type: ValidationType::PermissionConsistency,
625            parameters: HashMap::new(),
626            required: true,
627        },
628        ValidationStep {
629            id: "user_assignment_validation".to_string(),
630            name: "User Assignment Validation".to_string(),
631            description: "Verify all user assignments migrated correctly".to_string(),
632            validation_type: ValidationType::UserAssignmentValidity,
633            parameters: HashMap::new(),
634            required: true,
635        },
636        ValidationStep {
637            id: "privilege_escalation_check".to_string(),
638            name: "Privilege Escalation Check".to_string(),
639            description: "Verify no unintended privilege escalation occurred".to_string(),
640            validation_type: ValidationType::PrivilegeEscalationCheck,
641            parameters: HashMap::new(),
642            required: true,
643        },
644    ];
645
646    // Add complexity-specific validations
647    if !analysis.circular_dependencies.is_empty() {
648        plan.post_validation_steps.push(ValidationStep {
649            id: "circular_dependency_check".to_string(),
650            name: "Circular Dependency Check".to_string(),
651            description: "Verify circular dependencies were resolved".to_string(),
652            validation_type: ValidationType::Custom("circular_check".to_string()),
653            parameters: HashMap::new(),
654            required: true,
655        });
656    }
657}
658
659/// Generate rollback plan for migration
660fn generate_rollback_plan(plan: &mut MigrationPlan, _analysis: &LegacySystemAnalysis) {
661    plan.rollback_plan = RollbackPlan {
662        phases: vec![
663            RollbackPhase {
664                id: "stop_migration".to_string(),
665                name: "Stop Migration Process".to_string(),
666                operations: vec![MigrationOperation::ValidateIntegrity {
667                    validation_type: "stop_migration".to_string(),
668                    parameters: HashMap::new(),
669                }],
670                order: 1,
671            },
672            RollbackPhase {
673                id: "restore_backup".to_string(),
674                name: "Restore from Backup".to_string(),
675                operations: vec![MigrationOperation::Backup {
676                    backup_location: std::path::PathBuf::from("./restore"),
677                    backup_type: BackupType::Full,
678                }],
679                order: 2,
680            },
681        ],
682        backup_locations: vec![
683            std::path::PathBuf::from("./backups/pre_migration"),
684            std::path::PathBuf::from("./backups/incremental"),
685        ],
686        recovery_time_objective: chrono::Duration::hours(2),
687        manual_steps: vec![
688            "Verify system state after rollback".to_string(),
689            "Check user access and permissions".to_string(),
690            "Validate application functionality".to_string(),
691        ],
692    };
693}
694
695/// Assess risk level for migration
696fn assess_risk_level(analysis: &LegacySystemAnalysis) -> RiskLevel {
697    match analysis.complexity_score {
698        1..=3 => RiskLevel::Low,
699        4..=6 => RiskLevel::Medium,
700        7..=8 => RiskLevel::High,
701        _ => RiskLevel::Critical,
702    }
703}
704
705/// Estimate migration duration
706fn estimate_migration_duration(plan: &mut MigrationPlan) {
707    let total_duration = plan
708        .phases
709        .iter()
710        .map(|phase| phase.estimated_duration)
711        .fold(chrono::Duration::zero(), |acc, duration| acc + duration);
712
713    // Add 20% buffer for unexpected issues
714    plan.estimated_duration = total_duration + (total_duration / 5);
715}
716
717#[cfg(test)]
718mod tests {
719    use super::*;
720    use crate::migration::{LegacyRole, LegacySystemType, LegacyUserAssignment};
721
722    fn create_test_analysis() -> LegacySystemAnalysis {
723        LegacySystemAnalysis {
724            system_type: LegacySystemType::BasicRbac,
725            role_count: 5,
726            permission_count: 10,
727            user_assignment_count: 3,
728            roles: vec![
729                LegacyRole {
730                    id: "admin".to_string(),
731                    name: "Administrator".to_string(),
732                    description: Some("Admin role".to_string()),
733                    permissions: vec![
734                        "read".to_string(),
735                        "write".to_string(),
736                        "delete".to_string(),
737                        "admin".to_string(),
738                        "manage_users".to_string(),
739                        "manage_system".to_string(),
740                    ],
741                    parent_roles: vec![],
742                    metadata: HashMap::new(),
743                },
744                LegacyRole {
745                    id: "moderator".to_string(),
746                    name: "Moderator".to_string(),
747                    description: Some("Moderator role".to_string()),
748                    permissions: vec![
749                        "read".to_string(),
750                        "write".to_string(),
751                        "moderate".to_string(),
752                    ],
753                    parent_roles: vec![],
754                    metadata: HashMap::new(),
755                },
756                LegacyRole {
757                    id: "user".to_string(),
758                    name: "User".to_string(),
759                    description: Some("User role".to_string()),
760                    permissions: vec!["read".to_string()],
761                    parent_roles: vec![],
762                    metadata: HashMap::new(),
763                },
764                LegacyRole {
765                    id: "guest".to_string(),
766                    name: "Guest".to_string(),
767                    description: Some("Guest role".to_string()),
768                    permissions: vec!["read_public".to_string()],
769                    parent_roles: vec![],
770                    metadata: HashMap::new(),
771                },
772                LegacyRole {
773                    id: "support".to_string(),
774                    name: "Support".to_string(),
775                    description: Some("Support role".to_string()),
776                    permissions: vec!["read".to_string(), "support_tickets".to_string()],
777                    parent_roles: vec![],
778                    metadata: HashMap::new(),
779                },
780            ],
781            permissions: vec![],
782            user_assignments: vec![
783                LegacyUserAssignment {
784                    user_id: "user1".to_string(),
785                    role_id: Some("admin".to_string()),
786                    permissions: vec!["admin:read".to_string(), "admin:write".to_string()],
787                    attributes: HashMap::new(),
788                    expiration: None,
789                },
790                LegacyUserAssignment {
791                    user_id: "user2".to_string(),
792                    role_id: Some("user".to_string()),
793                    permissions: vec!["user:read".to_string()],
794                    attributes: HashMap::new(),
795                    expiration: None,
796                },
797                LegacyUserAssignment {
798                    user_id: "user3".to_string(),
799                    role_id: Some("moderator".to_string()),
800                    permissions: vec!["mod:moderate".to_string()],
801                    attributes: HashMap::new(),
802                    expiration: None,
803                },
804            ],
805            hierarchy_depth: 0,
806            duplicates_found: false,
807            orphaned_permissions: vec![],
808            circular_dependencies: vec![],
809            custom_attributes: std::collections::HashSet::new(),
810            complexity_score: 8,
811            recommended_strategy: MigrationStrategy::GradualMigration,
812        }
813    }
814
815    #[tokio::test]
816    async fn test_generate_direct_mapping_plan() {
817        let analysis = create_test_analysis();
818        let config = MigrationConfig::default();
819
820        let plan =
821            generate_migration_plan(&analysis, Some(MigrationStrategy::DirectMapping), &config)
822                .await
823                .unwrap();
824
825        assert_eq!(plan.strategy, MigrationStrategy::DirectMapping);
826        assert_eq!(plan.phases.len(), 5); // backup, roles, permissions, users, validation
827        assert_eq!(plan.role_mappings.len(), 5); // Now we have 5 roles
828    }
829
830    #[tokio::test]
831    async fn test_generate_gradual_migration_plan() {
832        let analysis = create_test_analysis();
833        let config = MigrationConfig::default();
834
835        let plan = generate_migration_plan(
836            &analysis,
837            Some(MigrationStrategy::GradualMigration),
838            &config,
839        )
840        .await
841        .unwrap();
842
843        assert_eq!(plan.strategy, MigrationStrategy::GradualMigration);
844        assert!(plan.phases.len() >= 3); // At least setup, critical roles, and user batches
845    }
846
847    #[test]
848    fn test_assess_risk_level() {
849        let mut analysis = create_test_analysis();
850
851        analysis.complexity_score = 2;
852        assert_eq!(assess_risk_level(&analysis), RiskLevel::Low);
853
854        analysis.complexity_score = 5;
855        assert_eq!(assess_risk_level(&analysis), RiskLevel::Medium);
856
857        analysis.complexity_score = 8;
858        assert_eq!(assess_risk_level(&analysis), RiskLevel::High);
859
860        analysis.complexity_score = 10;
861        assert_eq!(assess_risk_level(&analysis), RiskLevel::Critical);
862    }
863
864    #[test]
865    fn test_identify_critical_roles() {
866        let analysis = create_test_analysis();
867        let critical_roles = identify_critical_roles(&analysis);
868
869        // Should identify admin role as critical (has more permissions)
870        assert!(critical_roles.contains(&"admin".to_string()));
871    }
872}
873
874