use anyhow::Result;
use std::path::Path;

use crate::parsers::ParsedFile;
use crate::rules::{get_all_rules, Rule, RulePattern};
use crate::types::{Language, Location, MigrationImpact, Severity, Vulnerability};
use uuid::Uuid;

pub mod ast_analyzer;
pub mod pattern_matcher;
pub mod impact_analyzer;
pub mod library_detector;
pub mod languages;

pub use ast_analyzer::AstAnalyzer;
pub use pattern_matcher::PatternMatcher;
pub use impact_analyzer::ImpactAnalyzer;
pub use library_detector::LibraryDetector;

/// Main analyzer that coordinates vulnerability detection
pub struct Analyzer {
    rules: Vec<Rule>,
    min_severity: Severity,
    analyze_db_impact: bool,
}

impl Analyzer {
    pub fn new(min_severity: Severity, analyze_db_impact: bool) -> Self {
        Self {
            rules: get_all_rules(),
            min_severity,
            analyze_db_impact,
        }
    }

    /// Analyze a parsed file for vulnerabilities
    pub fn analyze(&self, parsed: &ParsedFile, file_path: &Path) -> Result<Vec<Vulnerability>> {
        let mut vulnerabilities = Vec::new();

        // Use AST-based analysis
        let ast_analyzer = AstAnalyzer::new();
        let ast_vulns = ast_analyzer.analyze(&parsed.tree, &parsed.source, file_path, parsed.language)?;
        vulnerabilities.extend(ast_vulns);

        // Use pattern-based analysis as fallback/supplement
        let pattern_matcher = PatternMatcher::new();
        let pattern_vulns = pattern_matcher.analyze(&parsed.source, file_path, parsed.language, &self.rules)?;

        // Merge and deduplicate
        for vuln in pattern_vulns {
            if !vulnerabilities.iter().any(|v| {
                v.location.file == vuln.location.file
                    && v.location.line == vuln.location.line
                    && std::mem::discriminant(&v.vuln_type) == std::mem::discriminant(&vuln.vuln_type)
            }) {
                vulnerabilities.push(vuln);
            }
        }

        // Detect vulnerable library imports
        let library_detector = LibraryDetector::new();
        let library_vulns = library_detector.detect_imports(&parsed.source, file_path, parsed.language);
        
        // Add library vulnerabilities (deduplicate by line)
        for lib_vuln in library_vulns {
            if !vulnerabilities.iter().any(|v| {
                v.location.file == lib_vuln.location.file
                    && v.location.line == lib_vuln.location.line
            }) {
                vulnerabilities.push(lib_vuln);
            }
        }

        // Analyze migration impacts if enabled
        if self.analyze_db_impact {
            let impact_analyzer = ImpactAnalyzer::new();
            for vuln in &mut vulnerabilities {
                let additional_impacts = impact_analyzer.analyze_context(
                    &parsed.source,
                    &vuln.location,
                    &vuln.vuln_type,
                );
                vuln.migration_impacts.extend(additional_impacts);
            }
        }

        // Filter by minimum severity
        vulnerabilities.retain(|v| v.severity >= self.min_severity);

        Ok(vulnerabilities)
    }
}

/// Helper to extract code snippet around a location
pub fn extract_snippet(source: &str, line: usize, context_lines: usize) -> Option<String> {
    let lines: Vec<&str> = source.lines().collect();

    if line == 0 || line > lines.len() {
        return None;
    }

    let start = line.saturating_sub(context_lines + 1);
    let end = (line + context_lines).min(lines.len());

    let snippet: Vec<String> = lines[start..end]
        .iter()
        .enumerate()
        .map(|(i, l)| format!("{:4} | {}", start + i + 1, l))
        .collect();

    Some(snippet.join("\n"))
}

/// Get line and column from byte offset
pub fn offset_to_position(source: &str, offset: usize) -> (usize, usize) {
    let mut line = 1;
    let mut col = 1;

    for (i, c) in source.char_indices() {
        if i >= offset {
            break;
        }
        if c == '\n' {
            line += 1;
            col = 1;
        } else {
            col += 1;
        }
    }

    (line, col)
}
