//! Project health check handlers
//!
//! This module provides functionality for checking overall project health
//! by running multiple quality checks and generating consolidated reports.

use crate::cli::OutputFormat;
use anyhow::Result;
use serde::Serialize;
use std::path::PathBuf;
use tokio::task::JoinSet;

/// Configuration for health checks
#[derive(Debug, Clone)]
pub struct HealthCheckConfig {
    pub quick: bool,
    pub all: bool,
    pub check_build: bool,
    pub check_tests: bool,
    pub check_coverage: bool,
    pub check_complexity: bool,
    pub check_satd: bool,
}

impl HealthCheckConfig {
    /// Create config from individual flags
    pub fn new(
        quick: bool,
        all: bool,
        check_build: bool,
        check_tests: bool,
        check_coverage: bool,
        check_complexity: bool,
        check_satd: bool,
    ) -> Self {
        Self {
            quick,
            all,
            check_build,
            check_tests,
            check_coverage,
            check_complexity,
            check_satd,
        }
    }
}

/// Health check result
#[derive(Debug, Serialize)]
pub struct HealthReport {
    pub healthy: bool,
    pub checks: Vec<HealthCheck>,
    pub summary: HealthSummary,
}

/// Individual health check
#[derive(Debug, Serialize)]
pub struct HealthCheck {
    pub name: String,
    pub status: CheckStatus,
    pub message: String,
    pub details: Option<String>,
}

#[derive(Debug, Serialize, PartialEq)]
pub enum CheckStatus {
    Pass,
    Warn,
    Fail,
    Skip,
}

/// Health summary
#[derive(Debug, Serialize)]
pub struct HealthSummary {
    pub total_checks: usize,
    pub passed: usize,
    pub warned: usize,
    pub failed: usize,
    pub skipped: usize,
}

/// Check type for parallel execution (TICKET-PMAT-6010)
#[derive(Debug, Clone, Copy)]
enum CheckType {
    Build,
    Tests,
    Coverage,
    Complexity,
    Satd,
}

/// Run health checks and return report (internal, reusable)
/// (TICKET-PMAT-6020)
///
/// # Complexity
/// - Time: O(n) where n is project size
/// - Cyclomatic: 7
pub async fn run_health_checks_internal(
    project_dir: &PathBuf,
    config: &HealthCheckConfig,
) -> Result<HealthReport> {
    // Determine which checks to run based on flags
    let checks_to_run = determine_checks_to_run(
        config.quick,
        config.all,
        config.check_build,
        config.check_tests,
        config.check_coverage,
        config.check_complexity,
        config.check_satd,
    );

    // Build list of check types to run (TICKET-PMAT-6010: parallel execution)
    let mut check_types = Vec::new();
    if checks_to_run.build {
        check_types.push(CheckType::Build);
    }
    if checks_to_run.tests {
        check_types.push(CheckType::Tests);
    }
    if checks_to_run.coverage {
        check_types.push(CheckType::Coverage);
    }
    if checks_to_run.complexity {
        check_types.push(CheckType::Complexity);
    }
    if checks_to_run.satd {
        check_types.push(CheckType::Satd);
    }

    // Run checks in parallel (TICKET-PMAT-6010)
    let checks = run_checks_parallel(project_dir, check_types).await?;

    let summary = calculate_summary(&checks);
    let report = HealthReport {
        healthy: summary.failed == 0,
        checks,
        summary,
    };

    Ok(report)
}

/// Handle project health check command (CLI wrapper)
/// (TICKET-PMAT-6001, PMAT-6010)
pub async fn handle_maintain_health(
    project_dir: PathBuf,
    format: OutputFormat,
    config: HealthCheckConfig,
) -> Result<()> {
    let report = run_health_checks_internal(&project_dir, &config).await?;

    print_health_report(&report, &format)?;

    if !report.healthy {
        std::process::exit(1);
    }

    Ok(())
}

/// Run build health check
async fn run_build_check(project_dir: &PathBuf) -> Result<HealthCheck> {
    use crate::cli::progress::ProgressIndicator;

    // Check if Cargo.toml exists
    let cargo_toml = project_dir.join("Cargo.toml");

    if !cargo_toml.exists() {
        return Ok(HealthCheck {
            name: "Build".to_string(),
            status: CheckStatus::Skip,
            message: "No Cargo.toml found".to_string(),
            details: None,
        });
    }

    // Show progress for build check
    let progress = ProgressIndicator::new("Running build check...");

    // Try to build
    let start = std::time::Instant::now();
    let output = tokio::process::Command::new("cargo")
        .arg("check")
        .arg("--quiet")
        .current_dir(project_dir)
        .output()
        .await?;
    let duration = start.elapsed();

    if output.status.success() {
        progress.finish_with_message(&format!(
            "Build check passed ({:.1}s)",
            duration.as_secs_f64()
        ));
        Ok(HealthCheck {
            name: "Build".to_string(),
            status: CheckStatus::Pass,
            message: "Project builds successfully".to_string(),
            details: None,
        })
    } else {
        progress.finish_with_error("Build check failed");
        let stderr = String::from_utf8_lossy(&output.stderr);
        Ok(HealthCheck {
            name: "Build".to_string(),
            status: CheckStatus::Fail,
            message: "Build failed".to_string(),
            details: Some(stderr.lines().take(5).collect::<Vec<_>>().join("\n")),
        })
    }
}

/// Run test health check
async fn run_test_check(project_dir: &PathBuf) -> Result<HealthCheck> {
    use crate::cli::progress::ProgressIndicator;

    let progress = ProgressIndicator::new("Running tests...");
    let start = std::time::Instant::now();

    let output = tokio::process::Command::new("cargo")
        .arg("test")
        .arg("--quiet")
        .arg("--no-fail-fast")
        .current_dir(project_dir)
        .output()
        .await?;

    let duration = start.elapsed();

    if output.status.success() {
        progress.finish_with_message(&format!("Tests passed ({:.1}s)", duration.as_secs_f64()));
        Ok(HealthCheck {
            name: "Tests".to_string(),
            status: CheckStatus::Pass,
            message: "All tests passing".to_string(),
            details: None,
        })
    } else {
        progress.finish_with_error("Tests failed");
        Ok(HealthCheck {
            name: "Tests".to_string(),
            status: CheckStatus::Fail,
            message: "Some tests failing".to_string(),
            details: None,
        })
    }
}

/// Run coverage health check
async fn run_coverage_check(project_dir: &PathBuf) -> Result<HealthCheck> {
    use crate::cli::progress::ProgressIndicator;

    let progress = ProgressIndicator::new("Running coverage check...");
    let start = std::time::Instant::now();

    // Use cargo llvm-cov to get coverage
    let output = tokio::process::Command::new("cargo")
        .arg("llvm-cov")
        .arg("--quiet")
        .arg("--summary-only")
        .current_dir(project_dir)
        .output()
        .await;

    let duration = start.elapsed();

    match output {
        Ok(result) if result.status.success() => {
            let stdout = String::from_utf8_lossy(&result.stdout);
            // Parse coverage percentage from output
            let coverage = parse_coverage_percentage(&stdout);

            let status = if coverage >= 80.0 {
                CheckStatus::Pass
            } else if coverage >= 60.0 {
                CheckStatus::Warn
            } else {
                CheckStatus::Fail
            };

            progress.finish_with_message(&format!(
                "Coverage: {:.1}% ({:.1}s)",
                coverage,
                duration.as_secs_f64()
            ));

            Ok(HealthCheck {
                name: "Coverage".to_string(),
                status,
                message: format!("Coverage: {:.1}%", coverage),
                details: Some(format!("Target: ≥80%, Current: {:.1}%", coverage)),
            })
        }
        _ => {
            progress.finish_with_message("cargo-llvm-cov not available");
            Ok(HealthCheck {
                name: "Coverage".to_string(),
                status: CheckStatus::Skip,
                message: "cargo-llvm-cov not available".to_string(),
                details: Some("Install with: cargo install cargo-llvm-cov".to_string()),
            })
        }
    }
}

/// Run complexity health check
async fn run_complexity_check(project_dir: &PathBuf) -> Result<HealthCheck> {
    use crate::cli::progress::ProgressIndicator;

    let progress = ProgressIndicator::new("Running complexity check...");
    let start = std::time::Instant::now();

    // Detect project toolchain
    let toolchain = super::super::analysis_utilities::detect_toolchain(project_dir);

    // Analyze project files with default thresholds
    let include: Vec<String> = Vec::new();
    let metrics = super::super::analysis_utilities::analyze_project_files(
        project_dir,
        toolchain.as_deref(),
        &include,
        20, // cyclomatic threshold
        15, // cognitive threshold
    ).await;

    let duration = start.elapsed();

    match metrics {
        Ok(file_metrics) => {
            // Count functions exceeding threshold (cyclomatic > 20)
            let mut total_functions = 0;
            let mut violations = 0;
            let mut max_complexity: u16 = 0;

            for file in &file_metrics {
                for func in &file.functions {
                    total_functions += 1;
                    if func.metrics.cyclomatic > max_complexity {
                        max_complexity = func.metrics.cyclomatic;
                    }
                    if func.metrics.cyclomatic > 20 {
                        violations += 1;
                    }
                }
            }

            let status = if violations == 0 {
                CheckStatus::Pass
            } else if violations <= 5 {
                CheckStatus::Warn
            } else {
                CheckStatus::Fail
            };

            progress.finish_with_message(&format!(
                "Complexity: {} functions, {} violations ({:.1}s)",
                total_functions,
                violations,
                duration.as_secs_f64()
            ));

            Ok(HealthCheck {
                name: "Complexity".to_string(),
                status,
                message: format!(
                    "{} functions analyzed, {} exceed threshold (max: {})",
                    total_functions, violations, max_complexity
                ),
                details: if violations > 0 {
                    Some("Run 'pmat analyze complexity' for detailed report".to_string())
                } else {
                    None
                },
            })
        }
        Err(_) => {
            progress.finish_with_message("Complexity check skipped");
            Ok(HealthCheck {
                name: "Complexity".to_string(),
                status: CheckStatus::Skip,
                message: "Could not analyze project complexity".to_string(),
                details: Some("Use 'pmat analyze complexity' for detailed analysis".to_string()),
            })
        }
    }
}

/// Run SATD health check
async fn run_satd_check(project_dir: &PathBuf) -> Result<HealthCheck> {
    use crate::cli::progress::ProgressIndicator;
    use crate::services::detection::satd::Severity;
    use crate::services::detection::UnifiedDetectionProcessor;

    let progress = ProgressIndicator::new("Running SATD check...");
    let start = std::time::Instant::now();

    // Use unified detection processor for SATD analysis
    let processor = UnifiedDetectionProcessor::new();
    let result = processor.detect_satd(project_dir).await;

    let duration = start.elapsed();

    match result {
        Ok(satd_result) => {
            // Count SATD items by severity
            let total_items = satd_result.items.len();
            let high_severity = satd_result
                .items
                .iter()
                .filter(|m| matches!(m.severity, Severity::High | Severity::Critical))
                .count();

            let status = if total_items == 0 {
                CheckStatus::Pass
            } else if high_severity == 0 {
                CheckStatus::Warn
            } else {
                CheckStatus::Fail
            };

            progress.finish_with_message(&format!(
                "SATD: {} items found ({} high severity) ({:.1}s)",
                total_items,
                high_severity,
                duration.as_secs_f64()
            ));

            Ok(HealthCheck {
                name: "SATD".to_string(),
                status,
                message: format!(
                    "{} technical debt items ({} high severity)",
                    total_items, high_severity
                ),
                details: if total_items > 0 {
                    Some(format!(
                        "Files with debt: {}. Run 'pmat analyze satd' for details",
                        satd_result.files_with_debt
                    ))
                } else {
                    None
                },
            })
        }
        Err(_) => {
            progress.finish_with_message("SATD check skipped");
            Ok(HealthCheck {
                name: "SATD".to_string(),
                status: CheckStatus::Skip,
                message: "Could not analyze technical debt".to_string(),
                details: Some("Use 'pmat analyze satd' for detailed analysis".to_string()),
            })
        }
    }
}

/// Run multiple health checks in parallel (TICKET-PMAT-6010)
///
/// # Complexity
/// - Time: O(max(check_times)) instead of O(sum(check_times))
/// - Cyclomatic: 4
async fn run_checks_parallel(
    project_dir: &PathBuf,
    check_types: Vec<CheckType>,
) -> Result<Vec<HealthCheck>> {
    let mut set = JoinSet::new();

    // Spawn parallel tasks for each check
    for check_type in check_types {
        let dir = project_dir.clone();
        set.spawn(async move {
            match check_type {
                CheckType::Build => run_build_check(&dir).await,
                CheckType::Tests => run_test_check(&dir).await,
                CheckType::Coverage => run_coverage_check(&dir).await,
                CheckType::Complexity => run_complexity_check(&dir).await,
                CheckType::Satd => run_satd_check(&dir).await,
            }
        });
    }

    // Collect results as they complete
    let mut results = Vec::new();
    while let Some(res) = set.join_next().await {
        results.push(res??);
    }

    Ok(results)
}

/// Parse coverage percentage from llvm-cov output
fn parse_coverage_percentage(output: &str) -> f64 {
    for line in output.lines() {
        if line.contains("TOTAL") {
            // Expected format: "TOTAL   1234   1000   80.0%"
            if let Some(pct_str) = line.split_whitespace().last() {
                if let Some(num_str) = pct_str.strip_suffix('%') {
                    if let Ok(pct) = num_str.parse::<f64>() {
                        return pct;
                    }
                }
            }
        }
    }
    0.0
}

/// Calculate summary from checks
fn calculate_summary(checks: &[HealthCheck]) -> HealthSummary {
    let mut summary = HealthSummary {
        total_checks: checks.len(),
        passed: 0,
        warned: 0,
        failed: 0,
        skipped: 0,
    };

    for check in checks {
        match check.status {
            CheckStatus::Pass => summary.passed += 1,
            CheckStatus::Warn => summary.warned += 1,
            CheckStatus::Fail => summary.failed += 1,
            CheckStatus::Skip => summary.skipped += 1,
        }
    }

    summary
}

/// Print health report
fn print_health_report(report: &HealthReport, format: &OutputFormat) -> Result<()> {
    match format {
        OutputFormat::Json => {
            println!("{}", serde_json::to_string_pretty(report)?);
        }
        OutputFormat::Yaml => {
            print_health_yaml(report);
        }
        OutputFormat::Table => {
            print_health_table(report);
        }
    }
    Ok(())
}

/// Print health report as table
fn print_health_table(report: &HealthReport) {
    let overall_icon = if report.healthy { "✅" } else { "❌" };
    eprintln!("{} Project Health Report\n", overall_icon);

    for check in &report.checks {
        let icon = match check.status {
            CheckStatus::Pass => "✅",
            CheckStatus::Warn => "⚠️ ",
            CheckStatus::Fail => "❌",
            CheckStatus::Skip => "⏭️ ",
        };

        eprintln!("{} {}: {}", icon, check.name, check.message);
        if let Some(details) = &check.details {
            eprintln!("   {}", details);
        }
    }

    eprintln!("\n📊 Summary:");
    eprintln!("   Total:   {}", report.summary.total_checks);
    eprintln!("   Passed:  {}", report.summary.passed);
    eprintln!("   Warned:  {}", report.summary.warned);
    eprintln!("   Failed:  {}", report.summary.failed);
    eprintln!("   Skipped: {}", report.summary.skipped);

    if report.healthy {
        eprintln!("\n✨ Project is healthy!");
    } else {
        eprintln!("\n⚠️  Project has {} issue(s)", report.summary.failed);
    }
}

/// Print health report as YAML
fn print_health_yaml(report: &HealthReport) {
    println!("healthy: {}", report.healthy);
    println!("checks:");
    for check in &report.checks {
        println!("  - name: {}", check.name);
        println!("    status: {:?}", check.status);
        println!("    message: {}", check.message);
        if let Some(details) = &check.details {
            println!("    details: {}", details);
        }
    }
    println!("summary:");
    println!("  total_checks: {}", report.summary.total_checks);
    println!("  passed: {}", report.summary.passed);
    println!("  warned: {}", report.summary.warned);
    println!("  failed: {}", report.summary.failed);
    println!("  skipped: {}", report.summary.skipped);
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_calculate_summary_all_pass() {
        let checks = vec![
            HealthCheck {
                name: "Test1".to_string(),
                status: CheckStatus::Pass,
                message: "OK".to_string(),
                details: None,
            },
            HealthCheck {
                name: "Test2".to_string(),
                status: CheckStatus::Pass,
                message: "OK".to_string(),
                details: None,
            },
        ];

        let summary = calculate_summary(&checks);
        assert_eq!(summary.total_checks, 2);
        assert_eq!(summary.passed, 2);
        assert_eq!(summary.failed, 0);
    }

    #[test]
    fn test_calculate_summary_mixed() {
        let checks = vec![
            HealthCheck {
                name: "Test1".to_string(),
                status: CheckStatus::Pass,
                message: "OK".to_string(),
                details: None,
            },
            HealthCheck {
                name: "Test2".to_string(),
                status: CheckStatus::Warn,
                message: "Warning".to_string(),
                details: None,
            },
            HealthCheck {
                name: "Test3".to_string(),
                status: CheckStatus::Fail,
                message: "Failed".to_string(),
                details: None,
            },
        ];

        let summary = calculate_summary(&checks);
        assert_eq!(summary.total_checks, 3);
        assert_eq!(summary.passed, 1);
        assert_eq!(summary.warned, 1);
        assert_eq!(summary.failed, 1);
    }

    #[test]
    fn test_parse_coverage_valid() {
        let output = "Filename                      Regions    Missed Regions     Cover   Functions  Missed Functions  Executed\n\
                      TOTAL   1234   234   81.0%";
        let coverage = parse_coverage_percentage(output);
        assert_eq!(coverage, 81.0);
    }

    #[test]
    fn test_parse_coverage_invalid() {
        let output = "No coverage data";
        let coverage = parse_coverage_percentage(output);
        assert_eq!(coverage, 0.0);
    }

    #[test]
    fn test_determine_checks_quick_mode() {
        let checks = determine_checks_to_run(true, false, false, false, false, false, false);
        assert!(checks.build);
        assert!(!checks.tests);
        assert!(!checks.coverage);
        assert!(!checks.complexity);
        assert!(!checks.satd);
    }

    #[test]
    fn test_determine_checks_all_mode() {
        let checks = determine_checks_to_run(false, true, false, false, false, false, false);
        assert!(checks.build);
        assert!(checks.tests);
        assert!(checks.coverage);
        assert!(checks.complexity);
        assert!(checks.satd);
    }

    #[test]
    fn test_determine_checks_default_no_flags() {
        let checks = determine_checks_to_run(false, false, false, false, false, false, false);
        assert!(checks.build);
        assert!(!checks.tests);
        assert!(!checks.coverage);
        assert!(!checks.complexity);
        assert!(!checks.satd);
    }

    #[test]
    fn test_determine_checks_specific_flags() {
        let checks = determine_checks_to_run(false, false, true, true, false, false, false);
        assert!(checks.build);
        assert!(checks.tests);
        assert!(!checks.coverage);
        assert!(!checks.complexity);
        assert!(!checks.satd);
    }

    #[test]
    fn test_determine_checks_quick_overrides_all() {
        let checks = determine_checks_to_run(true, true, false, false, false, false, false);
        assert!(checks.build);
        assert!(!checks.tests);
    }
}

/// Checks configuration (TICKET-PMAT-6001)
struct ChecksToRun {
    build: bool,
    tests: bool,
    coverage: bool,
    complexity: bool,
    satd: bool,
}

/// Determine which checks to run based on flags (TICKET-PMAT-6001)
///
/// # Logic
/// - quick: only build
/// - all: enable everything
/// - no flags: only build (default)
/// - specific flags: only those checks
///
/// # Complexity
/// - Cyclomatic: 7
fn determine_checks_to_run(
    quick: bool,
    all: bool,
    check_build: bool,
    check_tests: bool,
    check_coverage: bool,
    check_complexity: bool,
    check_satd: bool,
) -> ChecksToRun {
    // Quick mode: only build
    if quick {
        return ChecksToRun {
            build: true,
            tests: false,
            coverage: false,
            complexity: false,
            satd: false,
        };
    }

    // All mode: enable everything
    if all {
        return ChecksToRun {
            build: true,
            tests: true,
            coverage: true,
            complexity: true,
            satd: true,
        };
    }

    // Check if any specific flags are set
    let has_specific_flags =
        check_build || check_tests || check_coverage || check_complexity || check_satd;

    // If no flags specified, default to build only
    if !has_specific_flags {
        return ChecksToRun {
            build: true,
            tests: false,
            coverage: false,
            complexity: false,
            satd: false,
        };
    }

    // Use specified flags
    ChecksToRun {
        build: check_build,
        tests: check_tests,
        coverage: check_coverage,
        complexity: check_complexity,
        satd: check_satd,
    }
}

#[cfg(test)]
mod property_tests {
    use super::*;
    use proptest::prelude::*;

    proptest! {
        #[test]
        fn summary_totals_match(passed in 0u32..100, warned in 0u32..100, failed in 0u32..100, skipped in 0u32..100) {
            let mut checks = Vec::new();

            for _ in 0..passed {
                checks.push(HealthCheck {
                    name: "Pass".to_string(),
                    status: CheckStatus::Pass,
                    message: "OK".to_string(),
                    details: None,
                });
            }

            for _ in 0..warned {
                checks.push(HealthCheck {
                    name: "Warn".to_string(),
                    status: CheckStatus::Warn,
                    message: "Warning".to_string(),
                    details: None,
                });
            }

            for _ in 0..failed {
                checks.push(HealthCheck {
                    name: "Fail".to_string(),
                    status: CheckStatus::Fail,
                    message: "Failed".to_string(),
                    details: None,
                });
            }

            for _ in 0..skipped {
                checks.push(HealthCheck {
                    name: "Skip".to_string(),
                    status: CheckStatus::Skip,
                    message: "Skipped".to_string(),
                    details: None,
                });
            }

            let summary = calculate_summary(&checks);

            prop_assert_eq!(summary.total_checks, checks.len());
            prop_assert_eq!(summary.passed, passed as usize);
            prop_assert_eq!(summary.warned, warned as usize);
            prop_assert_eq!(summary.failed, failed as usize);
            prop_assert_eq!(summary.skipped, skipped as usize);
        }
    }
}

// TICKET-PMAT-6010: Tests for parallel health check execution
#[cfg(test)]
mod parallel_tests {
    use super::*;

    #[tokio::test]
    #[ignore] // Slow test (115s) - zero tolerance for slow tests in coverage
    async fn test_run_checks_parallel_returns_all_results() {
        let project_dir = PathBuf::from(".");
        let check_types = vec![CheckType::Build, CheckType::Complexity, CheckType::Satd];

        let results = run_checks_parallel(&project_dir, check_types).await;

        assert!(results.is_ok());
        let checks = results.unwrap();
        assert_eq!(checks.len(), 3);

        // Verify all check names are present
        let names: Vec<_> = checks.iter().map(|c| c.name.as_str()).collect();
        assert!(names.contains(&"Build"));
        assert!(names.contains(&"Complexity"));
        assert!(names.contains(&"SATD"));
    }

    #[tokio::test]
    async fn test_run_checks_parallel_empty_list() {
        let project_dir = PathBuf::from(".");
        let check_types = vec![];

        let results = run_checks_parallel(&project_dir, check_types).await;

        assert!(results.is_ok());
        let checks = results.unwrap();
        assert_eq!(checks.len(), 0);
    }

    /// SLOW: 100s - excluded from fast test suite
    #[tokio::test]
    #[ignore]
    async fn test_run_checks_parallel_single_check() {
        let project_dir = PathBuf::from(".");
        let check_types = vec![CheckType::Build];

        let results = run_checks_parallel(&project_dir, check_types).await;

        assert!(results.is_ok());
        let checks = results.unwrap();
        assert_eq!(checks.len(), 1);
        assert_eq!(checks[0].name, "Build");
    }

    #[test]
    fn test_check_type_coverage() {
        // Verify CheckType enum has all expected variants
        let types = [
            CheckType::Build,
            CheckType::Tests,
            CheckType::Coverage,
            CheckType::Complexity,
            CheckType::Satd,
        ];

        // If this compiles, all types exist
        assert_eq!(types.len(), 5);
    }
}
