pub mod ast;
pub mod cache;
pub mod error;
pub mod git;
pub mod metrics;

use anyhow::Result;
use ast::parser::TypeScriptAnalyzer;
use cache::{AnalysisCache, CacheManager};
use chrono::{DateTime, Utc};
use git::GitAnalyzer;
use git2::Repository;
use ignore::WalkBuilder;
use metrics::{
    AnalysisMetadata, Distributions, FunctionMetrics, MaxValues, NormalizedMetrics,
    PercentileMetrics, Report, RiskMetrics, SummaryStats,
};
use rayon::iter::{ParallelBridge, ParallelIterator};
use std::cmp::Ordering as CmpOrdering;
use std::collections::HashMap;
use std::path::Path;
use std::sync::{
    atomic::{AtomicBool, Ordering},
    Arc,
};

const SCHEMA_VERSION: &str = "0.1.0";
const FALLBACK_TIMESTAMP: &str = "1970-01-01T00:00:00+00:00";

pub fn analyze_repository(
    repo_path: &Path,
    _sort_by: &str,
    _limit: Option<usize>,
    shutdown: Arc<AtomicBool>,
) -> Result<Report> {
    log::info!("Analyzing repository: {}", repo_path.display());

    let repo = match Repository::open(repo_path) {
        Ok(repo) => Some(repo),
        Err(err) => {
            log::warn!("Git repository unavailable: {}", err);
            None
        }
    };
    let (branch_name, commit_hash, timestamp) = match repo.as_ref() {
        Some(repo) => repository_metadata(repo),
        None => (
            "unknown".to_string(),
            String::new(),
            FALLBACK_TIMESTAMP.to_string(),
        ),
    };

    let cache_manager = CacheManager::new(repo_path);
    let mut cache = match cache_manager.load() {
        Ok(cache) => cache,
        Err(err) => {
            log::warn!("Failed to load cache: {}", err);
            AnalysisCache::default()
        }
    };

    // Análise Incremental de Git preservando integridade de autores
    let git_cache = match repo.as_ref() {
        Some(repo) => match GitAnalyzer::get_all_file_metrics(
            repo,
            cache.git_cache.clone(),
            cache.last_commit_oid.clone(),
        ) {
            Ok(git_cache) => git_cache,
            Err(err) => {
                log::warn!("Failed to collect Git metrics: {}", err);
                HashMap::new()
            }
        },
        None => HashMap::new(),
    };

    let repo_path_abs = repo_path.canonicalize()?;
    let repo_available = repo.is_some();

    let walker = WalkBuilder::new(&repo_path_abs)
        .standard_filters(true)
        .hidden(false)
        .build();

    let (mut functions, new_cache_files) = walker
        .par_bridge()
        .filter_map(|entry| {
            if shutdown.load(Ordering::Relaxed) {
                return None;
            }

            let entry = match entry {
                Ok(e) => e,
                Err(err) => {
                    log::warn!("Failed to read directory entry: {}", err);
                    return None;
                }
            };

            let path = entry.path();
            if !path.is_file() {
                return None;
            }

            let ext = path.extension()?;
            if ext != "ts" && ext != "tsx" && ext != "js" && ext != "jsx" {
                return None;
            }

            let rel_path = path.strip_prefix(&repo_path_abs).ok()?;
            let rel_path_str = rel_path.to_string_lossy().to_string();

            let current_oid = if repo_available {
                match GitAnalyzer::get_file_oid_tls(&repo_path_abs, rel_path) {
                    Ok(Some(oid)) => oid.to_string(),
                    Ok(None) => String::new(),
                    Err(err) => {
                        log::warn!("Failed to read Git object for {}: {}", rel_path_str, err);
                        String::new()
                    }
                }
            } else {
                String::new()
            };

            let mut functions =
                if let Some((cached_oid, cached_funcs)) = cache.files.get(&rel_path_str) {
                    if cached_oid == &current_oid && !current_oid.is_empty() {
                        cached_funcs.clone()
                    } else {
                        match AnalysisWorker::process_file(path, &rel_path_str) {
                            Ok(functions) => functions,
                            Err(err) => {
                                log::warn!("Failed to analyze {}: {}", rel_path_str, err);
                                Vec::new()
                            }
                        }
                    }
                } else {
                    match AnalysisWorker::process_file(path, &rel_path_str) {
                        Ok(functions) => functions,
                        Err(err) => {
                            log::warn!("Failed to analyze {}: {}", rel_path_str, err);
                            Vec::new()
                        }
                    }
                };

            if let Some(entry) = git_cache.get(&rel_path_str) {
                let churn = GitAnalyzer::compute_churn_metrics(entry);
                for func in &mut functions {
                    func.times_modified = churn.times_modified;
                    func.bug_fix_commits = churn.bug_fix_commits;
                    func.authors_count = churn.authors_count;
                    func.churn_score = churn.churn_score;
                    func.file = rel_path_str.clone();
                }
            }

            let cache_entry = if current_oid.is_empty() {
                None
            } else {
                Some((rel_path_str, (current_oid, functions.clone())))
            };

            Some((functions, cache_entry))
        })
        .fold(
            || (Vec::new(), HashMap::new()),
            |mut acc, (functions, cache_entry)| {
                acc.0.extend(functions);
                if let Some((path, entry)) = cache_entry {
                    acc.1.insert(path, entry);
                }
                acc
            },
        )
        .reduce(
            || (Vec::new(), HashMap::new()),
            |mut acc, (functions, cache_entries)| {
                acc.0.extend(functions);
                acc.1.extend(cache_entries);
                acc
            },
        );

    // Análise Estatística Global
    if functions.is_empty() {
        log::warn!("No functions found to analyze.");
        return Ok(Report {
            schema_version: SCHEMA_VERSION.to_string(),
            analysis: AnalysisMetadata {
                repository: repo_path_abs.to_string_lossy().to_string(),
                commit: commit_hash,
                branch: branch_name,
                timestamp,
            },
            summary: SummaryStats {
                total_functions: 0,
                max_values: None,
                distributions: None,
            },
            functions,
        });
    }

    // 1. Cálculo de Max e Distribuição Percentil
    let max_values = MaxValues {
        cyclomatic: functions
            .iter()
            .map(|f| f.cyclomatic_complexity)
            .max()
            .unwrap_or(1),
        cognitive: functions
            .iter()
            .map(|f| f.cognitive_complexity)
            .max()
            .unwrap_or(1),
        churn: functions.iter().map(|f| f.churn_score).fold(0.0, f64::max),
        loc: functions.iter().map(|f| f.lines_of_code).max().unwrap_or(1),
    };

    let mut cog_vals: Vec<u32> = functions.iter().map(|f| f.cognitive_complexity).collect();
    let mut churn_vals: Vec<f64> = functions.iter().map(|f| f.churn_score).collect();
    let mut loc_vals: Vec<u32> = functions.iter().map(|f| f.lines_of_code).collect();
    let mut cyc_vals: Vec<u32> = functions.iter().map(|f| f.cyclomatic_complexity).collect();
    let mut auth_vals: Vec<usize> = functions.iter().map(|f| f.authors_count).collect();

    cog_vals.sort();
    churn_vals.sort_by(|a, b| a.total_cmp(b));
    loc_vals.sort();
    cyc_vals.sort();
    auth_vals.sort();

    let p95_idx = (functions.len() * 95 / 100).min(functions.len() - 1);
    let p99_idx = (functions.len() * 99 / 100).min(functions.len() - 1);

    let cognitive_p95 = cog_vals[p95_idx] as f64;
    let churn_p95 = churn_vals[p95_idx];
    let loc_p95 = loc_vals[p95_idx] as f64;
    let cyc_p95 = cyc_vals[p95_idx] as f64;
    let auth_p95 = auth_vals[p95_idx] as f64;

    let cognitive_p99 = cog_vals[p99_idx] as f64;
    let churn_p99 = churn_vals[p99_idx];
    let loc_p99 = loc_vals[p99_idx] as f64;
    let cyc_p99 = cyc_vals[p99_idx] as f64;
    let auth_p99 = auth_vals[p99_idx] as f64;

    // Normalização com proteção contra outliers
    let cap_cog = if (max_values.cognitive as f64) > 3.0 * cognitive_p95 {
        cognitive_p99
    } else {
        max_values.cognitive as f64
    }
    .max(1.0);
    let cap_churn = if max_values.churn > 3.0 * churn_p95 {
        churn_p99
    } else {
        max_values.churn
    }
    .max(1.0);
    let cap_loc = if (max_values.loc as f64) > 3.0 * loc_p95 {
        loc_p99
    } else {
        max_values.loc as f64
    }
    .max(1.0);
    let cap_cyc = if (max_values.cyclomatic as f64) > 3.0 * cyc_p95 {
        cyc_p99
    } else {
        max_values.cyclomatic as f64
    }
    .max(1.0);
    let cap_auth = if (auth_vals.iter().max().cloned().unwrap_or(0) as f64) > 3.0 * auth_p95 {
        auth_p99
    } else {
        *auth_vals.iter().max().unwrap_or(&0) as f64
    }
    .max(1.0);

    // 2. Cálculo de scores de risco
    for func in &mut functions {
        let norm_cog = (1.0 + func.cognitive_complexity as f64).ln() / (1.0 + cap_cog).ln();
        let norm_cyc = (1.0 + func.cyclomatic_complexity as f64).ln() / (1.0 + cap_cyc).ln();
        let norm_churn = (1.0 + func.churn_score).ln() / (1.0 + cap_churn).ln();
        let norm_loc = (1.0 + func.lines_of_code as f64).ln() / (1.0 + cap_loc).ln();
        let norm_auth = (1.0 + func.authors_count as f64).ln() / (1.0 + cap_auth).ln();

        func.normalized = Some(NormalizedMetrics {
            cyclomatic: norm_cyc,
            churn: norm_churn,
            cognitive: norm_cog,
            loc: norm_loc,
            authors: norm_auth,
        });

        let base_score = (0.35 * norm_cog)
            + (0.15 * norm_cyc)
            + (0.30 * norm_churn)
            + (0.10 * norm_loc)
            + (0.10 * norm_auth);
        let nesting_penalty = 1.0 + (func.nesting_depth as f64 / 4.0).powi(2) * 0.20;
        let final_score = base_score * nesting_penalty;

        func.risk = Some(RiskMetrics {
            base_score,
            nesting_penalty,
            final_score,
            level: String::new(),
            primary_driver: String::new(),
        });
    }

    // 3. Cálculo de Rankings Percentis e Metadados de Risco
    let mut risk_vals: Vec<f64> = functions
        .iter()
        .filter_map(|f| f.risk.as_ref().map(|risk| risk.final_score))
        .collect();
    risk_vals.sort_by(|a, b| a.total_cmp(b));

    let total_funcs = functions.len() as f64;
    for func in &mut functions {
        let Some(risk_score) = func.risk.as_ref().map(|risk| risk.final_score) else {
            continue;
        };
        let churn = func.churn_score;
        let cog = func.cognitive_complexity as f64;

        let risk_pct = percentile_f64(&risk_vals, risk_score, total_funcs);

        func.percentile = Some(PercentileMetrics {
            risk: risk_pct,
            churn: percentile_f64(&churn_vals, churn, total_funcs),
            cognitive: percentile_u32(&cog_vals, cog as u32, total_funcs),
        });

        let level = match risk_pct {
            p if p > 95.0 => "critical",
            p if p > 80.0 => "high",
            p if p > 50.0 => "medium",
            _ => "low",
        }
        .to_string();

        if let Some(norm) = &func.normalized {
            let mut drivers = vec![
                ("cognitive", norm.cognitive),
                ("churn", norm.churn),
                ("cyclomatic", norm.cyclomatic),
                ("loc", norm.loc),
                ("authors", norm.authors),
            ];
            drivers.sort_by(|a, b| b.1.total_cmp(&a.1));
            if let Some(risk) = func.risk.as_mut() {
                risk.level = level;
                risk.primary_driver = drivers[0].0.to_string();
            }
        }
    }

    // Geração do Report Final
    let risk_p95 = risk_vals[p95_idx];

    functions.sort_by(|a, b| {
        a.file
            .cmp(&b.file)
            .then(a.name.cmp(&b.name))
            .then(a.line.cmp(&b.line))
    });

    let report = Report {
        schema_version: SCHEMA_VERSION.to_string(),
        analysis: AnalysisMetadata {
            repository: repo_path_abs.to_string_lossy().to_string(),
            commit: commit_hash.clone(),
            branch: branch_name,
            timestamp,
        },
        summary: SummaryStats {
            total_functions: functions.len(),
            max_values: Some(max_values),
            distributions: Some(Distributions {
                risk_p95,
                churn_p95,
                cognitive_p95,
            }),
        },
        functions,
    };

    // Finaliza cache com integridade Git total
    cache.files = new_cache_files;
    cache.git_cache = git_cache;
    cache.last_commit_oid = if commit_hash.is_empty() {
        None
    } else {
        Some(commit_hash)
    };
    if let Err(err) = cache_manager.save(cache) {
        log::warn!("Failed to save cache: {}", err);
    }

    Ok(report)
}

fn repository_metadata(repo: &Repository) -> (String, String, String) {
    let head = match repo.head() {
        Ok(head) => head,
        Err(err) => {
            log::warn!("Git HEAD unavailable: {}", err);
            return (
                "unknown".to_string(),
                String::new(),
                FALLBACK_TIMESTAMP.to_string(),
            );
        }
    };
    let branch_name = head.shorthand().unwrap_or("unknown").to_string();

    let commit = match head.peel_to_commit() {
        Ok(commit) => commit,
        Err(err) => {
            log::warn!("Git HEAD commit unavailable: {}", err);
            return (branch_name, String::new(), FALLBACK_TIMESTAMP.to_string());
        }
    };

    let timestamp = commit_timestamp(&commit).unwrap_or_else(|| FALLBACK_TIMESTAMP.to_string());
    (branch_name, commit.id().to_string(), timestamp)
}

fn commit_timestamp(commit: &git2::Commit) -> Option<String> {
    DateTime::<Utc>::from_timestamp(commit.time().seconds(), 0).map(|dt| dt.to_rfc3339())
}

fn percentile_f64(sorted_values: &[f64], value: f64, total: f64) -> f64 {
    let idx = match sorted_values.binary_search_by(|probe| {
        if probe.total_cmp(&value) == CmpOrdering::Less {
            CmpOrdering::Less
        } else {
            CmpOrdering::Greater
        }
    }) {
        Ok(idx) | Err(idx) => idx,
    };
    (idx as f64 / total) * 100.0
}

fn percentile_u32(sorted_values: &[u32], value: u32, total: f64) -> f64 {
    let idx = match sorted_values.binary_search_by(|probe| {
        if *probe < value {
            CmpOrdering::Less
        } else {
            CmpOrdering::Greater
        }
    }) {
        Ok(idx) | Err(idx) => idx,
    };
    (idx as f64 / total) * 100.0
}

struct AnalysisWorker;
impl AnalysisWorker {
    fn process_file(path: &Path, rel_path_str: &str) -> Result<Vec<FunctionMetrics>> {
        let source = std::fs::read_to_string(path)?;
        TypeScriptAnalyzer::analyze_source(&source, rel_path_str)
    }
}
