/// Inference engine
/// Converts technology detections into infrastructure requirements
/// Uses rule-based system to suggest taskservs based on what was detected
use crate::models::{Detection, Requirement, Technology};

/// Inference rule: detect pattern → infer requirement
pub struct InferenceRule {
    pub name: &'static str,
    pub condition: fn(&[Detection]) -> bool,
    pub infer: fn(&[Detection]) -> Vec<Requirement>,
}

/// Main inference engine
pub struct InferenceEngine {
    rules: Vec<InferenceRule>,
}

impl InferenceEngine {
    pub fn new(rules: Vec<InferenceRule>) -> Self {
        Self { rules }
    }

    /// Create engine with default rules
    pub fn with_default_rules() -> Self {
        let rules = vec![
            // Rule 1: Node.js + Express → Redis (API caching)
            InferenceRule {
                name: "NodeJS API recommends Redis",
                condition: |detections| {
                    detections
                        .iter()
                        .any(|d| d.technology == Technology::NodeJs)
                        && detections
                            .iter()
                            .any(|d| d.technology == Technology::Express)
                },
                infer: |_| {
                    vec![Requirement::new(
                        "redis",
                        "Express.js APIs benefit from caching layer",
                        0.85,
                        false,
                    )]
                },
            },
            // Rule 2: Any database detected → needs backups
            InferenceRule {
                name: "Databases need backup strategy",
                condition: |detections| {
                    detections.iter().any(|d| {
                        matches!(
                            d.technology,
                            Technology::Postgres | Technology::Mysql | Technology::Mongodb
                        )
                    })
                },
                infer: |detections| {
                    detections
                        .iter()
                        .filter(|d| {
                            matches!(
                                d.technology,
                                Technology::Postgres | Technology::Mysql | Technology::Mongodb
                            )
                        })
                        .map(|d| {
                            let taskserv = match d.technology {
                                Technology::Postgres => "postgres-backup",
                                Technology::Mysql => "mysql-backup",
                                Technology::Mongodb => "mongodb-backup",
                                _ => "backup",
                            };
                            Requirement::new(
                                taskserv,
                                "Production databases require backup strategy",
                                0.90,
                                false,
                            )
                        })
                        .collect()
                },
            },
            // Rule 3: Containerized apps need reverse proxy
            InferenceRule {
                name: "Docker apps need reverse proxy",
                condition: |detections| {
                    detections
                        .iter()
                        .any(|d| d.technology == Technology::Docker)
                },
                infer: |_| {
                    vec![Requirement::new(
                        "nginx",
                        "Containerized applications should run behind reverse proxy",
                        0.75,
                        false,
                    )]
                },
            },
            // Rule 4: Any language detected → needs runtime
            InferenceRule {
                name: "Languages need runtime",
                condition: |detections| {
                    detections.iter().any(|d| {
                        matches!(
                            d.technology,
                            Technology::NodeJs | Technology::Python | Technology::Rust
                        )
                    })
                },
                infer: |detections| {
                    let lang = detections.iter().find(|d| {
                        matches!(
                            d.technology,
                            Technology::NodeJs | Technology::Python | Technology::Rust
                        )
                    });

                    match lang.map(|d| d.technology) {
                        Some(Technology::NodeJs) => vec![], // Already detected
                        Some(Technology::Python) => vec![], // Already detected
                        Some(Technology::Rust) => vec![],   // Already detected
                        _ => vec![],
                    }
                },
            },
            // Rule 5: PostgreSQL detected → add monitoring
            InferenceRule {
                name: "PostgreSQL should have monitoring",
                condition: |detections| {
                    detections
                        .iter()
                        .any(|d| d.technology == Technology::Postgres)
                },
                infer: |_| {
                    vec![Requirement::new(
                        "pg-monitoring",
                        "Monitor PostgreSQL performance in production",
                        0.70,
                        false,
                    )
                    .with_min_version("14.0".to_string())]
                },
            },
        ];

        Self::new(rules)
    }

    /// Infer requirements from detections
    pub fn infer_requirements(&self, detections: &[Detection]) -> Vec<Requirement> {
        let mut requirements = Vec::new();

        for rule in &self.rules {
            if (rule.condition)(detections) {
                let inferred = (rule.infer)(detections);
                requirements.extend(inferred);
            }
        }

        // Deduplicate and keep highest confidence
        requirements.sort_by(|a, b| b.confidence.partial_cmp(&a.confidence).unwrap());
        requirements.dedup_by(|a, b| a.taskserv == b.taskserv);

        requirements
    }
}

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

    #[test]
    fn test_nodejs_express_recommends_redis() {
        let detections = vec![
            Detection::new(Technology::NodeJs),
            Detection::new(Technology::Express),
        ];

        let engine = InferenceEngine::with_default_rules();
        let requirements = engine.infer_requirements(&detections);

        assert!(requirements.iter().any(|r| r.taskserv == "redis"));
    }

    #[test]
    fn test_docker_recommends_nginx() {
        let detections = vec![Detection::new(Technology::Docker)];

        let engine = InferenceEngine::with_default_rules();
        let requirements = engine.infer_requirements(&detections);

        assert!(requirements.iter().any(|r| r.taskserv == "nginx"));
    }

    #[test]
    fn test_postgres_detected() {
        let detections = vec![Detection::new(Technology::Postgres)];

        let engine = InferenceEngine::with_default_rules();
        let requirements = engine.infer_requirements(&detections);

        assert!(!requirements.is_empty());
    }
}
