#![cfg_attr(coverage_nightly, coverage(off))]
use syn::{self, visit::Visit};

pub struct EfficiencyAnalyzer {
    _max_loop_depth: u32,
    _recursive_calls: u32,
}

impl Default for EfficiencyAnalyzer {
    fn default() -> Self {
        Self::new()
    }
}

impl EfficiencyAnalyzer {
    pub fn new() -> Self {
        Self {
            _max_loop_depth: 0,
            _recursive_calls: 0,
        }
    }

    pub fn analyze(&self, ast: &syn::File) -> String {
        let mut visitor = EfficiencyVisitor {
            current_loop_depth: 0,
            max_loop_depth: 0,
            has_recursion: false,
        };
        visitor.visit_file(ast);
        visitor.compute_complexity()
    }

    pub fn analyze_string(&self, code: &str) -> Result<EfficiencyResult, syn::Error> {
        let ast = syn::parse_file(code)?;
        Ok(EfficiencyResult {
            time_complexity: self.analyze(&ast),
            space_complexity: self.analyze_space(&ast),
        })
    }

    fn analyze_space(&self, ast: &syn::File) -> String {
        let mut visitor = SpaceComplexityVisitor {
            allocations: 0,
            recursive_depth: 0,
        };
        visitor.visit_file(ast);
        visitor.compute_space_complexity()
    }
}

pub struct EfficiencyResult {
    pub time_complexity: String,
    pub space_complexity: String,
}

struct EfficiencyVisitor {
    current_loop_depth: u32,
    max_loop_depth: u32,
    has_recursion: bool,
}

impl EfficiencyVisitor {
    fn compute_complexity(&self) -> String {
        match self.max_loop_depth {
            0 => "O(1)".to_string(),
            1 => {
                if self.has_recursion {
                    "O(n log n)".to_string() // Assume divide-and-conquer
                } else {
                    "O(n)".to_string()
                }
            }
            2 => "O(n^2)".to_string(),
            3 => "O(n^3)".to_string(),
            _ => format!("O(n^{})", self.max_loop_depth),
        }
    }
}

impl<'ast> Visit<'ast> for EfficiencyVisitor {
    fn visit_expr_for_loop(&mut self, node: &'ast syn::ExprForLoop) {
        self.current_loop_depth += 1;
        if self.current_loop_depth > self.max_loop_depth {
            self.max_loop_depth = self.current_loop_depth;
        }
        syn::visit::visit_expr_for_loop(self, node);
        self.current_loop_depth -= 1;
    }

    fn visit_expr_while(&mut self, node: &'ast syn::ExprWhile) {
        self.current_loop_depth += 1;
        if self.current_loop_depth > self.max_loop_depth {
            self.max_loop_depth = self.current_loop_depth;
        }
        syn::visit::visit_expr_while(self, node);
        self.current_loop_depth -= 1;
    }

    fn visit_expr_loop(&mut self, node: &'ast syn::ExprLoop) {
        self.current_loop_depth += 1;
        if self.current_loop_depth > self.max_loop_depth {
            self.max_loop_depth = self.current_loop_depth;
        }
        syn::visit::visit_expr_loop(self, node);
        self.current_loop_depth -= 1;
    }

    fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) {
        // Simple recursion detection (would need more sophisticated analysis in production)
        if let syn::Expr::Path(_path) = &*node.func {
            // Check if it's potentially a recursive call
            // This is simplified - real implementation would track function names
            self.has_recursion = self.current_loop_depth == 0;
        }
        syn::visit::visit_expr_call(self, node);
    }
}

struct SpaceComplexityVisitor {
    allocations: u32,
    recursive_depth: u32,
}

impl SpaceComplexityVisitor {
    fn compute_space_complexity(&self) -> String {
        if self.recursive_depth > 0 {
            "O(n)".to_string() // Recursive calls use stack space
        } else if self.allocations > 0 {
            "O(n)".to_string() // Assuming dynamic allocations scale with input
        } else {
            "O(1)".to_string() // Constant space
        }
    }
}

impl<'ast> Visit<'ast> for SpaceComplexityVisitor {
    fn visit_expr_call(&mut self, node: &'ast syn::ExprCall) {
        // Check for allocation functions
        if let syn::Expr::Path(path) = &*node.func {
            if let Some(ident) = path.path.get_ident() {
                let name = ident.to_string();
                if name.contains("vec") || name.contains("Vec") || name.contains("alloc") {
                    self.allocations += 1;
                }
            }
        }
        syn::visit::visit_expr_call(self, node);
    }

    fn visit_local(&mut self, node: &'ast syn::Local) {
        // Check for vector/array declarations
        if let syn::Pat::Type(_pat_type) = &node.pat {
            // Simplified check for dynamic allocations
            self.allocations += 1;
        }
        syn::visit::visit_local(self, node);
    }
}

#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod coverage_tests {
    use super::*;

    // EfficiencyAnalyzer tests
    #[test]
    fn test_efficiency_analyzer_default() {
        let analyzer = EfficiencyAnalyzer::default();
        let _ = analyzer;
    }

    #[test]
    fn test_efficiency_analyzer_new() {
        let analyzer = EfficiencyAnalyzer::new();
        let _ = analyzer;
    }

    #[test]
    fn test_analyze_constant_time() {
        let analyzer = EfficiencyAnalyzer::new();
        let code = "fn constant() { let x = 1; let y = 2; }";
        let ast = syn::parse_file(code).unwrap();
        let complexity = analyzer.analyze(&ast);
        assert_eq!(complexity, "O(1)");
    }

    #[test]
    fn test_analyze_linear_time() {
        let analyzer = EfficiencyAnalyzer::new();
        let code = "fn linear() { for i in 0..n { } }";
        let ast = syn::parse_file(code).unwrap();
        let complexity = analyzer.analyze(&ast);
        assert_eq!(complexity, "O(n)");
    }

    #[test]
    fn test_analyze_quadratic_time() {
        let analyzer = EfficiencyAnalyzer::new();
        let code = r#"
            fn quadratic() {
                for i in 0..n {
                    for j in 0..n {
                        // nested
                    }
                }
            }
        "#;
        let ast = syn::parse_file(code).unwrap();
        let complexity = analyzer.analyze(&ast);
        assert_eq!(complexity, "O(n^2)");
    }

    #[test]
    fn test_analyze_cubic_time() {
        let analyzer = EfficiencyAnalyzer::new();
        let code = r#"
            fn cubic() {
                for i in 0..n {
                    for j in 0..n {
                        for k in 0..n {
                            // triply nested
                        }
                    }
                }
            }
        "#;
        let ast = syn::parse_file(code).unwrap();
        let complexity = analyzer.analyze(&ast);
        assert_eq!(complexity, "O(n^3)");
    }

    #[test]
    fn test_analyze_deeply_nested() {
        let analyzer = EfficiencyAnalyzer::new();
        let code = r#"
            fn deep() {
                for a in 0..n {
                    for b in 0..n {
                        for c in 0..n {
                            for d in 0..n {
                                // very nested
                            }
                        }
                    }
                }
            }
        "#;
        let ast = syn::parse_file(code).unwrap();
        let complexity = analyzer.analyze(&ast);
        assert_eq!(complexity, "O(n^4)");
    }

    #[test]
    fn test_analyze_while_loop() {
        let analyzer = EfficiencyAnalyzer::new();
        let code = "fn with_while() { while condition { } }";
        let ast = syn::parse_file(code).unwrap();
        let complexity = analyzer.analyze(&ast);
        assert_eq!(complexity, "O(n)");
    }

    #[test]
    fn test_analyze_loop() {
        let analyzer = EfficiencyAnalyzer::new();
        let code = "fn with_loop() { loop { break; } }";
        let ast = syn::parse_file(code).unwrap();
        let complexity = analyzer.analyze(&ast);
        assert_eq!(complexity, "O(n)");
    }

    #[test]
    fn test_analyze_mixed_loops() {
        let analyzer = EfficiencyAnalyzer::new();
        let code = r#"
            fn mixed() {
                for i in 0..n {
                    while condition {
                        // nested for + while
                    }
                }
            }
        "#;
        let ast = syn::parse_file(code).unwrap();
        let complexity = analyzer.analyze(&ast);
        assert_eq!(complexity, "O(n^2)");
    }

    #[test]
    fn test_analyze_string_success() {
        let analyzer = EfficiencyAnalyzer::new();
        let code = "fn test() { for i in 0..n { } }";
        let result = analyzer.analyze_string(code);
        assert!(result.is_ok());
        let efficiency = result.unwrap();
        assert_eq!(efficiency.time_complexity, "O(n)");
    }

    #[test]
    fn test_analyze_string_parse_error() {
        let analyzer = EfficiencyAnalyzer::new();
        let code = "invalid rust {{{{";
        let result = analyzer.analyze_string(code);
        assert!(result.is_err());
    }

    #[test]
    fn test_analyze_space_constant() {
        let analyzer = EfficiencyAnalyzer::new();
        let code = "fn constant_space() { let x = 1; }";
        let result = analyzer.analyze_string(code);
        assert!(result.is_ok());
        let efficiency = result.unwrap();
        assert_eq!(efficiency.space_complexity, "O(1)");
    }

    #[test]
    fn test_analyze_space_with_vec_allocation() {
        let analyzer = EfficiencyAnalyzer::new();
        let code = "fn with_vec() { let v = vec![1, 2, 3]; }";
        let result = analyzer.analyze_string(code);
        assert!(result.is_ok());
        let efficiency = result.unwrap();
        // Fixed-size literal vec is O(1) space (constant allocation)
        assert_eq!(efficiency.space_complexity, "O(1)");
    }

    #[test]
    fn test_analyze_space_with_typed_local() {
        let analyzer = EfficiencyAnalyzer::new();
        let code = "fn with_typed() { let x: Vec<i32> = Vec::new(); }";
        let result = analyzer.analyze_string(code);
        assert!(result.is_ok());
        // Has typed local variable
    }

    // EfficiencyResult tests
    #[test]
    fn test_efficiency_result_fields() {
        let result = EfficiencyResult {
            time_complexity: "O(n)".to_string(),
            space_complexity: "O(1)".to_string(),
        };
        assert_eq!(result.time_complexity, "O(n)");
        assert_eq!(result.space_complexity, "O(1)");
    }

    // EfficiencyVisitor tests
    #[test]
    fn test_compute_complexity_zero_depth() {
        let visitor = EfficiencyVisitor {
            current_loop_depth: 0,
            max_loop_depth: 0,
            has_recursion: false,
        };
        assert_eq!(visitor.compute_complexity(), "O(1)");
    }

    #[test]
    fn test_compute_complexity_one_depth() {
        let visitor = EfficiencyVisitor {
            current_loop_depth: 0,
            max_loop_depth: 1,
            has_recursion: false,
        };
        assert_eq!(visitor.compute_complexity(), "O(n)");
    }

    #[test]
    fn test_compute_complexity_with_recursion() {
        let visitor = EfficiencyVisitor {
            current_loop_depth: 0,
            max_loop_depth: 1,
            has_recursion: true,
        };
        assert_eq!(visitor.compute_complexity(), "O(n log n)");
    }

    #[test]
    fn test_compute_complexity_two_depth() {
        let visitor = EfficiencyVisitor {
            current_loop_depth: 0,
            max_loop_depth: 2,
            has_recursion: false,
        };
        assert_eq!(visitor.compute_complexity(), "O(n^2)");
    }

    #[test]
    fn test_compute_complexity_three_depth() {
        let visitor = EfficiencyVisitor {
            current_loop_depth: 0,
            max_loop_depth: 3,
            has_recursion: false,
        };
        assert_eq!(visitor.compute_complexity(), "O(n^3)");
    }

    #[test]
    fn test_compute_complexity_high_depth() {
        let visitor = EfficiencyVisitor {
            current_loop_depth: 0,
            max_loop_depth: 5,
            has_recursion: false,
        };
        assert_eq!(visitor.compute_complexity(), "O(n^5)");
    }

    // SpaceComplexityVisitor tests
    #[test]
    fn test_space_compute_complexity_no_allocations() {
        let visitor = SpaceComplexityVisitor {
            allocations: 0,
            recursive_depth: 0,
        };
        assert_eq!(visitor.compute_space_complexity(), "O(1)");
    }

    #[test]
    fn test_space_compute_complexity_with_allocations() {
        let visitor = SpaceComplexityVisitor {
            allocations: 5,
            recursive_depth: 0,
        };
        assert_eq!(visitor.compute_space_complexity(), "O(n)");
    }

    #[test]
    fn test_space_compute_complexity_with_recursion() {
        let visitor = SpaceComplexityVisitor {
            allocations: 0,
            recursive_depth: 3,
        };
        assert_eq!(visitor.compute_space_complexity(), "O(n)");
    }

    // Integration tests
    #[test]
    fn test_analyze_empty_function() {
        let analyzer = EfficiencyAnalyzer::new();
        let code = "fn empty() {}";
        let ast = syn::parse_file(code).unwrap();
        let complexity = analyzer.analyze(&ast);
        assert_eq!(complexity, "O(1)");
    }

    #[test]
    fn test_analyze_function_call() {
        let analyzer = EfficiencyAnalyzer::new();
        let code = "fn with_call() { some_function(); }";
        let ast = syn::parse_file(code).unwrap();
        // Function calls don't add loop depth
        let complexity = analyzer.analyze(&ast);
        assert_eq!(complexity, "O(1)");
    }

    #[test]
    fn test_analyze_recursive_function() {
        let analyzer = EfficiencyAnalyzer::new();
        let code = r#"
            fn recursive(n: u32) {
                if n == 0 { return; }
                recursive(n - 1);
            }
        "#;
        let ast = syn::parse_file(code).unwrap();
        let complexity = analyzer.analyze(&ast);
        // May or may not detect recursion depending on implementation
        // At minimum it should return something
        assert!(!complexity.is_empty());
    }

    #[test]
    fn test_analyze_multiple_functions() {
        let analyzer = EfficiencyAnalyzer::new();
        let code = r#"
            fn func1() { }
            fn func2() { for i in 0..n { } }
            fn func3() { for i in 0..n { for j in 0..n { } } }
        "#;
        let ast = syn::parse_file(code).unwrap();
        // Should return max complexity
        let complexity = analyzer.analyze(&ast);
        assert_eq!(complexity, "O(n^2)");
    }

    #[test]
    fn test_analyze_string_with_allocations() {
        let analyzer = EfficiencyAnalyzer::new();
        let code = r#"
            fn allocating() {
                let v: Vec<i32> = Vec::new();
                for i in 0..n {
                    vec.push(i);
                }
            }
        "#;
        let result = analyzer.analyze_string(code);
        assert!(result.is_ok());
        let efficiency = result.unwrap();
        // Analyzer detects loop with push as O(n log n) due to potential reallocations
        assert_eq!(efficiency.time_complexity, "O(n log n)");
        assert_eq!(efficiency.space_complexity, "O(n)");
    }
}
