use std::path::PathBuf;

/// Integration tests for the full Detection + Completion workflow
///
/// Tests the complete Infrastructure-from-Code pipeline:
/// 1. Project technology detection
/// 2. Infrastructure requirement inference
/// 3. Gap analysis
/// 4. Declaration completion
use provisioning_detector::{Completer, GapAnalyzer, StackDetector, Technology};
use tempfile::TempDir;

/// Helper function to create a Node.js + Express project structure
fn create_nodejs_express_project() -> (TempDir, PathBuf) {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let project_path = temp_dir.path().to_path_buf();

    // Create package.json with Express dependency
    let package_json = r#"{
        "name": "express-app",
        "version": "1.0.0",
        "dependencies": {
            "express": "^4.18.0",
            "redis": "^4.0.0",
            "pg": "^8.8.0"
        }
    }"#;

    std::fs::write(project_path.join("package.json"), package_json)
        .expect("Failed to write package.json");

    // Create .nvmrc
    std::fs::write(project_path.join(".nvmrc"), "18.0.0").expect("Failed to write .nvmrc");

    // Create Dockerfile
    std::fs::write(
        project_path.join("Dockerfile"),
        "FROM node:18\nWORKDIR /app\nCOPY . .\nRUN npm install\nCMD [\"npm\", \"start\"]",
    )
    .expect("Failed to write Dockerfile");

    // Create source directory
    std::fs::create_dir_all(project_path.join("src")).expect("Failed to create src dir");
    std::fs::write(
        project_path.join("src/index.js"),
        "const express = require('express');\nconst app = express();\napp.listen(3000);",
    )
    .expect("Failed to write index.js");

    (temp_dir, project_path)
}

/// Helper function to create a Python + Django project structure
fn create_python_django_project() -> (TempDir, PathBuf) {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let project_path = temp_dir.path().to_path_buf();

    // Create requirements.txt
    let requirements = "django==4.2\npsycopg2-binary==2.9\ncelery==5.3\n";
    std::fs::write(project_path.join("requirements.txt"), requirements)
        .expect("Failed to write requirements.txt");

    // Create manage.py
    std::fs::write(
        project_path.join("manage.py"),
        "#!/usr/bin/env python\nimport django",
    )
    .expect("Failed to write manage.py");

    // Create migrations directory
    std::fs::create_dir_all(project_path.join("migrations")).expect("Failed to create migrations");
    std::fs::write(
        project_path.join("migrations/0001_initial.py"),
        "# Django migration",
    )
    .expect("Failed to write migration");

    (temp_dir, project_path)
}

#[test]
fn test_detect_nodejs_express_project() {
    let (_temp_dir, project_path) = create_nodejs_express_project();

    // Run detection
    let detector = StackDetector::new();
    let analysis = detector
        .detect_all(&project_path)
        .expect("Detection failed");

    // Verify Node.js was detected from package.json and .nvmrc
    assert!(
        !analysis.detections.is_empty(),
        "Should detect at least one technology"
    );
    assert!(
        analysis
            .detections
            .iter()
            .any(|d| d.technology == Technology::NodeJs),
        "Should detect Node.js technology"
    );

    // Verify overall confidence is calculated
    assert!(analysis.overall_confidence >= 0.0);

    // Should have some inferred requirements
    println!(
        "Requirements: {:?}",
        analysis
            .requirements
            .iter()
            .map(|r| &r.taskserv)
            .collect::<Vec<_>>()
    );

    // Requirements inference depends on the inference engine
    // At minimum, technology detection should succeed
    assert!(!analysis.detections.is_empty());
}

#[test]
fn test_detect_python_django_project() {
    let (_temp_dir, project_path) = create_python_django_project();

    // Run detection
    let detector = StackDetector::new();
    let analysis = detector
        .detect_all(&project_path)
        .expect("Detection failed");

    // Verify Python was detected from requirements.txt, manage.py, or migrations
    let detections = analysis
        .detections
        .iter()
        .map(|d| d.technology)
        .collect::<Vec<_>>();
    println!("Detections: {:?}", detections);

    // Should detect Python from requirements.txt or manage.py
    assert!(
        detections.contains(&Technology::Python),
        "Should detect Python: {:?}",
        detections
    );

    println!(
        "Requirements: {:?}",
        analysis
            .requirements
            .iter()
            .map(|r| &r.taskserv)
            .collect::<Vec<_>>()
    );
}

#[test]
fn test_gap_analysis_with_empty_declaration() {
    let (_temp_dir, project_path) = create_nodejs_express_project();

    // Run detection
    let detector = StackDetector::new();
    let analysis = detector
        .detect_all(&project_path)
        .expect("Detection failed");

    // Extract required taskservs
    let required_taskservs: Vec<String> = analysis
        .requirements
        .iter()
        .filter(|r| r.required)
        .map(|r| r.taskserv.clone())
        .collect();

    // Run gap analysis (assuming empty declaration - no current taskservs)
    let gaps = GapAnalyzer::analyze(&analysis, &required_taskservs);

    // Should calculate completeness based on required taskservs
    println!("Completeness: {:.1}%", gaps.completeness * 100.0);
    println!("Gaps: {}", gaps.gaps.len());
    println!("Required taskservs: {}", required_taskservs.len());

    // If there are no required taskservs, completeness will be 100%
    // If there are required taskservs but none detected, completeness will be 0%
    assert!(gaps.completeness >= 0.0 && gaps.completeness <= 1.0);
}

#[test]
fn test_complete_declaration_workflow() {
    let (_temp_dir, project_path) = create_nodejs_express_project();

    // Step 1: Detect
    let detector = StackDetector::new();
    let analysis = detector
        .detect_all(&project_path)
        .expect("Detection failed");

    // Step 2: Plan completion
    let completer = Completer::new();
    let completion_result = completer.complete(&analysis, Vec::new());

    // Verify completion plan
    assert!(
        completion_result.changes_needed > 0,
        "Should identify changes needed"
    );
    println!("Changes needed: {}", completion_result.changes_needed);
    println!("Is safe: {}", completion_result.is_safe);
    println!("Summary: {}", completion_result.change_summary);

    // Adding taskservs without removals is safe
    assert!(completion_result.is_safe);
}

#[test]
fn test_multiple_technologies_detection() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let project_path = temp_dir.path().to_path_buf();

    // Create a project with multiple indicators
    // Node.js + Rust combo (monorepo style)
    let package_json = r#"{
        "name": "full-stack-app",
        "dependencies": {
            "express": "^4.18.0",
            "postgresql": "^14.0"
        }
    }"#;
    std::fs::write(project_path.join("package.json"), package_json)
        .expect("Failed to write package.json");

    // Create Cargo.toml for Rust component
    let cargo_toml = r#"[package]
name = "backend-service"
version = "0.1.0"
edition = "2021"

[dependencies]
tokio = { version = "1", features = ["full"] }
"#;
    std::fs::create_dir_all(project_path.join("backend")).expect("Failed to create backend");
    std::fs::write(project_path.join("backend/Cargo.toml"), cargo_toml)
        .expect("Failed to write Cargo.toml");

    // Run detection
    let detector = StackDetector::new();
    let analysis = detector
        .detect_all(&project_path)
        .expect("Detection failed");

    // Should detect both Node.js and Rust
    let detections = analysis
        .detections
        .iter()
        .map(|d| d.technology)
        .collect::<Vec<_>>();

    println!("Detected: {:?}", detections);
    assert!(detections.contains(&Technology::NodeJs) || detections.contains(&Technology::Rust));
}

#[test]
fn test_confidence_scoring() {
    let (_temp_dir, project_path) = create_nodejs_express_project();

    // Run detection
    let detector = StackDetector::new();
    let analysis = detector
        .detect_all(&project_path)
        .expect("Detection failed");

    // Each detection should have a confidence score
    for detection in &analysis.detections {
        assert!(detection.confidence >= 0.0 && detection.confidence <= 1.0);
        assert!(!detection.evidence.is_empty(), "Should have evidence");
    }

    // Overall confidence should be calculated
    assert!(analysis.overall_confidence >= 0.0 && analysis.overall_confidence <= 1.0);
}

#[test]
fn test_requirement_classification() {
    let (_temp_dir, project_path) = create_nodejs_express_project();

    // Run detection
    let detector = StackDetector::new();
    let analysis = detector
        .detect_all(&project_path)
        .expect("Detection failed");

    // Separate requirements by classification
    let required: Vec<_> = analysis
        .requirements
        .iter()
        .filter(|r| r.required)
        .collect();
    let optional: Vec<_> = analysis
        .requirements
        .iter()
        .filter(|r| !r.required)
        .collect();

    println!("Required: {}", required.len());
    println!("Optional: {}", optional.len());

    // Should have at least some inferred requirements
    assert!(!analysis.requirements.is_empty());

    // Requirements should be classified as either required or optional
    assert_eq!(
        required.len() + optional.len(),
        analysis.requirements.len(),
        "All requirements should be classified"
    );
}

#[test]
fn test_requirement_inference_logic() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let project_path = temp_dir.path().to_path_buf();

    // Create a project with specific inference triggers
    let package_json = r#"{
        "name": "test-app",
        "dependencies": {
            "express": "^4.18.0",
            "redis": "^4.0.0"
        }
    }"#;
    std::fs::write(project_path.join("package.json"), package_json)
        .expect("Failed to write package.json");

    // Run detection and inference
    let detector = StackDetector::new();
    let analysis = detector
        .detect_all(&project_path)
        .expect("Detection failed");

    // Verify inference rules were applied:
    // Express should trigger Nginx recommendation
    let has_express = analysis
        .detections
        .iter()
        .any(|d| d.technology == Technology::Express);
    if has_express {
        // Could have inferred nginx
        let services: Vec<_> = analysis
            .requirements
            .iter()
            .map(|r| r.taskserv.as_str())
            .collect();
        println!("Inferred services: {:?}", services);
    }
}

#[test]
fn test_detection_with_nonexistent_path() {
    let detector = StackDetector::new();
    let nonexistent = PathBuf::from("/nonexistent/path");

    // Detection should complete even if path doesn't exist
    // but will have no detections
    match detector.detect_all(&nonexistent) {
        Ok(analysis) => {
            // May succeed with empty detections
            println!("Detections: {}", analysis.detections.len());
        }
        Err(_e) => {
            // Or may fail, both are acceptable
            println!("Path detection failed as expected");
        }
    }
}

#[test]
fn test_empty_project_detection() {
    let temp_dir = TempDir::new().expect("Failed to create temp dir");
    let project_path = temp_dir.path().to_path_buf();

    // Empty project with no tech indicators
    let detector = StackDetector::new();
    let analysis = detector
        .detect_all(&project_path)
        .expect("Detection failed");

    // Should have no detections but not crash
    println!("Detections: {}", analysis.detections.len());
    println!("Requirements: {}", analysis.requirements.len());

    // Empty projects should result in low or zero confidence
    assert!(analysis.overall_confidence == 0.0 || !analysis.detections.is_empty());
}
