//! YAML type detection and per-format validation functions.
//!
//! ## Main entry point
//!
//! [`validate_auto`] — auto-detects the YAML format and dispatches to the
//! appropriate validator. Handles multi-document YAML (`---` separator).
//!
//! ## Per-format validators
//!
//! - [`validate_k8s_manifest`] — all Kubernetes resource kinds
//! - [`validate_gitlab_ci`], [`validate_github_actions`]
//! - [`validate_docker_compose`]
//! - [`validate_prometheus`], [`validate_alertmanager`]
//! - [`validate_helm_values`], [`validate_ansible`]
//!
//! ## Helper functions
//!
//! - [`parse_yaml`] — parse YAML string into [`serde_json::Value`]
//! - [`detect_yaml_type`] — detect format from parsed content

use devops_models::models::ansible::AnsiblePlay;
use devops_models::models::docker_compose::DockerCompose;
use devops_models::models::github_actions::GitHubActions;
use devops_models::models::gitlab::GitLabCI;
use devops_models::models::helm::HelmValues;
use devops_models::models::k8s::*;
use devops_models::models::k8s_networking::{K8sIngress, K8sNetworkPolicy};
use devops_models::models::k8s_rbac::{K8sRole, K8sRoleBinding, K8sServiceAccount};
use devops_models::models::k8s_storage::K8sPVC;
use devops_models::models::k8s_workloads::{K8sCronJob, K8sDaemonSet, K8sHPA, K8sJob, K8sStatefulSet};
use devops_models::models::prometheus::{AlertmanagerConfig, PrometheusConfig};
use devops_models::models::validation::{
    ConfigValidator, Diagnostic, ValidationResult, YamlType,
};

/// Parse a YAML string into a [`serde_json::Value`].
///
/// Accepts both mapping (object) and array top-level documents.
/// Scalar YAML (a bare string, number, or boolean) is rejected because
/// none of the DevOps formats use a scalar root.
///
/// # Errors
///
/// Returns `Err(String)` when:
/// - The input is not valid YAML syntax.
/// - The YAML root is a scalar (null, bool, number, or string) rather than
///   a mapping or array.
///
/// # Example
///
/// ```rust
/// use devops_validate::validator::parse_yaml;
///
/// let data = parse_yaml("key: value").unwrap();
/// assert!(data.is_object());
///
/// assert!(parse_yaml("just a string").is_err());
/// ```
pub fn parse_yaml(content: &str) -> Result<serde_json::Value, String> {
    let value: serde_json::Value =
        serde_yaml::from_str(content).map_err(|e| format!("YAML parse error: {e}"))?;
    if !value.is_object() && !value.is_array() {
        return Err(format!(
            "YAML must be a mapping or array, got: {}",
            value_type_name(&value)
        ));
    }
    Ok(value)
}

/// Detect the format of a parsed YAML document.
///
/// Uses explicit markers first (`apiVersion`/`kind` for Kubernetes, `$schema`
/// in the validator), then structural heuristics for Docker Compose, GitLab CI,
/// GitHub Actions, Prometheus, Alertmanager, Helm, Ansible, and OpenAPI.
/// Returns [`YamlType::Generic`] when no format is recognised.
///
/// # Example
///
/// ```rust
/// use devops_validate::validator::{parse_yaml, detect_yaml_type};
/// use devops_models::models::validation::YamlType;
///
/// let data = parse_yaml("apiVersion: apps/v1\nkind: Deployment\nmetadata:\n  name: x\nspec:\n  selector:\n    matchLabels:\n      app: x\n  template:\n    metadata:\n      labels:\n        app: x\n    spec:\n      containers: []").unwrap();
/// assert_eq!(detect_yaml_type(&data), YamlType::K8sDeployment);
/// ```
pub fn detect_yaml_type(data: &serde_json::Value) -> YamlType {
    // Top-level array — could be Ansible Playbook
    if data.is_array() && AnsiblePlay::looks_like_playbook(data) {
        return YamlType::Ansible;
    }

    let obj = match data.as_object() {
        Some(o) => o,
        None => return YamlType::Generic,
    };

    // K8s resources have apiVersion + kind
    if obj.contains_key("apiVersion") && obj.contains_key("kind") {
        let kind = obj.get("kind").and_then(|v| v.as_str()).unwrap_or("");
        return match kind {
            "Deployment" => YamlType::K8sDeployment,
            "Service" => YamlType::K8sService,
            "ConfigMap" => YamlType::K8sConfigMap,
            "Secret" => YamlType::K8sSecret,
            "Ingress" => YamlType::K8sIngress,
            "HorizontalPodAutoscaler" => YamlType::K8sHPA,
            "CronJob" => YamlType::K8sCronJob,
            "Job" => YamlType::K8sJob,
            "PersistentVolumeClaim" => YamlType::K8sPVC,
            "NetworkPolicy" => YamlType::K8sNetworkPolicy,
            "StatefulSet" => YamlType::K8sStatefulSet,
            "DaemonSet" => YamlType::K8sDaemonSet,
            "Role" => YamlType::K8sRole,
            "ClusterRole" => YamlType::K8sClusterRole,
            "RoleBinding" => YamlType::K8sRoleBinding,
            "ClusterRoleBinding" => YamlType::K8sClusterRoleBinding,
            "ServiceAccount" => YamlType::K8sServiceAccount,
            _ => YamlType::K8sGeneric,
        };
    }

    // Docker Compose: has `services` key with sub-objects
    if obj.contains_key("services")
        && let Some(svcs) = obj.get("services").and_then(|v| v.as_object()) {
            // Distinguish from generic YAML with a "services" key —
            // Compose services have sub-objects, not arrays or scalars.
            let looks_like_compose = svcs.values().any(|v| v.is_object());
            if looks_like_compose {
                return YamlType::DockerCompose;
            }
        }

    // GitHub Actions: has `on` + `jobs`
    if obj.contains_key("on") && obj.contains_key("jobs") {
        return YamlType::GitHubActions;
    }

    // GitLab CI has stages and/or jobs with script keys
    if obj.contains_key("stages") || obj.values().any(|v| v.get("script").is_some()) {
        return YamlType::GitLabCI;
    }

    // Prometheus: has global.scrape_interval or scrape_configs
    if obj.contains_key("scrape_configs")
        || obj
            .get("global")
            .and_then(|g| g.get("scrape_interval"))
            .is_some()
    {
        return YamlType::Prometheus;
    }

    // Alertmanager: has route + receivers
    if obj.contains_key("route") && obj.contains_key("receivers") {
        return YamlType::Alertmanager;
    }

    // Helm values.yaml: heuristic detection
    if HelmValues::looks_like_helm(data) {
        return YamlType::HelmValues;
    }

    // OpenAPI has openapi or swagger key
    if obj.contains_key("openapi") || obj.contains_key("swagger") {
        return YamlType::OpenAPI;
    }

    YamlType::Generic
}

/// Validate a Kubernetes manifest (all supported resource kinds) via serde.
///
/// Detects the resource `kind` from the YAML and dispatches to the appropriate
/// typed struct validator.  For known kinds this provides structural validation
/// (required fields, type checks) plus semantic warnings (replicas=1,
/// missing resource limits, `:latest` image tags, etc.).
///
/// Unknown `kind` values are accepted as valid with a warning.
///
/// # Example
///
/// ```rust
/// use devops_validate::validator::validate_k8s_manifest;
///
/// let yaml = r#"
/// apiVersion: apps/v1
/// kind: Deployment
/// metadata:
///   name: test
/// spec:
///   replicas: 1
///   selector:
///     matchLabels:
///       app: test
///   template:
///     metadata:
///       labels:
///         app: test
///     spec:
///       containers:
///         - name: app
///           image: test:latest
/// "#;
///
/// let result = validate_k8s_manifest(yaml);
/// assert!(result.valid);
/// assert!(!result.warnings.is_empty()); // warns on replicas=1 and :latest
/// ```
pub fn validate_k8s_manifest(content: &str) -> ValidationResult {
    let data = match parse_yaml(content) {
        Ok(d) => d,
        Err(e) => {
            return ValidationResult {
                valid: false,
                errors: vec![e],
                warnings: vec![],
                diagnostics: vec![],
                hints: vec![],
                yaml_type: None,
            }
        }
    };

    let kind = data
        .get("kind")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_string();

    // Collect heuristic warnings before consuming `data`.
    let extra_warnings = collect_k8s_warnings(&data);

    match kind.as_str() {
        "Deployment" => match serde_json::from_value::<K8sDeployment>(data) {
            Ok(dep) => {
                let mut warnings = extra_warnings;
                warnings.extend(dep.validate());
                ValidationResult {
                    valid: true,
                    errors: vec![],
                    diagnostics: vec![],
                    warnings,
                    hints: vec![],
                    yaml_type: Some(YamlType::K8sDeployment),
                }
            }
            Err(e) => ValidationResult {
                valid: false,
                errors: format_serde_errors(&e),
                warnings: vec![],
                diagnostics: vec![],
                hints: vec![],
                yaml_type: Some(YamlType::K8sDeployment),
            },
        },
        "Service" => match serde_json::from_value::<K8sService>(data) {
            Ok(svc) => {
                let mut warnings = extra_warnings;
                warnings.extend(validate_service_semantics(&svc));
                ValidationResult {
                    valid: true,
                    errors: vec![],
                    diagnostics: vec![],
                    warnings,
                    hints: vec![],
                    yaml_type: Some(YamlType::K8sService),
                }
            }
            Err(e) => ValidationResult {
                valid: false,
                errors: format_serde_errors(&e),
                diagnostics: vec![],
                warnings: vec![],
                hints: vec![],
                yaml_type: Some(YamlType::K8sService),
            },
        },
        "ConfigMap" => match serde_json::from_value::<K8sConfigMap>(data) {
            Ok(cm) => {
                let mut warnings = extra_warnings;
                if cm.data.is_empty() && cm.binary_data.is_none() {
                    warnings.push("ConfigMap has no data and no binaryData".to_string());
                }
                ValidationResult {
                    valid: true,
                    errors: vec![],
                    diagnostics: vec![],
                    warnings,
                    hints: vec![],
                    yaml_type: Some(YamlType::K8sConfigMap),
                }
            }
            Err(e) => ValidationResult {
                valid: false,
                errors: format_serde_errors(&e),
                diagnostics: vec![],
                warnings: vec![],
                hints: vec![],
                yaml_type: Some(YamlType::K8sConfigMap),
            },
        },
        "Secret" => match serde_json::from_value::<K8sSecret>(data) {
            Ok(sec) => {
                let mut warnings = extra_warnings;
                warnings.extend(validate_secret_semantics(&sec));
                ValidationResult {
                    valid: true,
                    errors: vec![],
                    diagnostics: vec![],
                    warnings,
                    hints: vec![],
                    yaml_type: Some(YamlType::K8sSecret),
                }
            }
            Err(e) => ValidationResult {
                valid: false,
                errors: format_serde_errors(&e),
                diagnostics: vec![],
                warnings: vec![],
                hints: vec![],
                yaml_type: Some(YamlType::K8sSecret),
            },
        },
        // ── New K8s kinds via ConfigValidator trait ──────────────────────
        "Ingress" => validate_k8s_with_trait::<K8sIngress>(data, YamlType::K8sIngress),
        "HorizontalPodAutoscaler" => validate_k8s_with_trait::<K8sHPA>(data, YamlType::K8sHPA),
        "CronJob" => validate_k8s_with_trait::<K8sCronJob>(data, YamlType::K8sCronJob),
        "Job" => validate_k8s_with_trait::<K8sJob>(data, YamlType::K8sJob),
        "PersistentVolumeClaim" => validate_k8s_with_trait::<K8sPVC>(data, YamlType::K8sPVC),
        "NetworkPolicy" => {
            validate_k8s_with_trait::<K8sNetworkPolicy>(data, YamlType::K8sNetworkPolicy)
        }
        "StatefulSet" => {
            validate_k8s_with_trait::<K8sStatefulSet>(data, YamlType::K8sStatefulSet)
        }
        "DaemonSet" => validate_k8s_with_trait::<K8sDaemonSet>(data, YamlType::K8sDaemonSet),
        "Role" | "ClusterRole" => validate_k8s_with_trait::<K8sRole>(data, YamlType::K8sRole),
        "RoleBinding" | "ClusterRoleBinding" => {
            validate_k8s_with_trait::<K8sRoleBinding>(data, YamlType::K8sRoleBinding)
        }
        "ServiceAccount" => {
            validate_k8s_with_trait::<K8sServiceAccount>(data, YamlType::K8sServiceAccount)
        }
        // ── Unknown K8s kind → generic K8s validation ──
        _ => validate_k8s_generic(data, &kind),
    }
}

/// Validate a K8s resource using its ConfigValidator trait implementation.
fn validate_k8s_with_trait<T>(data: serde_json::Value, yaml_type: YamlType) -> ValidationResult
where
    T: serde::de::DeserializeOwned + ConfigValidator,
{
    match serde_json::from_value::<T>(data) {
        Ok(resource) => resource.validate(),
        Err(e) => ValidationResult {
            valid: false,
            errors: format_serde_errors(&e),
            warnings: vec![],
            diagnostics: vec![],
            hints: vec![],
            yaml_type: Some(yaml_type),
        },
    }
}

/// Validate unknown K8s kinds with a basic structure check.
fn validate_k8s_generic(data: serde_json::Value, kind: &str) -> ValidationResult {
    let mut warnings = vec![format!(
        "Kind '{}' has no specific validator — only basic structure checked",
        kind
    )];
    let obj = data.as_object();
    if let Some(o) = obj {
        if !o.contains_key("metadata") {
            warnings
                .push(format!("Kind '{}' is missing 'metadata' — unusual for a K8s resource", kind));
        }
        if !o.contains_key("spec") && !o.contains_key("data") && !o.contains_key("rules") {
            warnings.push(format!("Kind '{}' has no 'spec', 'data', or 'rules' field", kind));
        }
    }
    ValidationResult {
        valid: true,
        errors: vec![],
        diagnostics: vec![],
        warnings,
        hints: vec![],
        yaml_type: Some(YamlType::K8sGeneric),
    }
}

/// Validate a GitLab CI pipeline YAML
pub fn validate_gitlab_ci(content: &str) -> ValidationResult {
    let data = match parse_yaml(content) {
        Ok(d) => d,
        Err(e) => {
            return ValidationResult {
                valid: false,
                errors: vec![e],
                warnings: vec![],
                diagnostics: vec![],
                hints: vec![],
                yaml_type: Some(YamlType::GitLabCI),
            }
        }
    };

    match GitLabCI::from_value(&data) {
        Ok(ci) => {
            let warnings = ci.validate();
            ValidationResult {
                valid: true,
                errors: vec![],
                warnings,
                diagnostics: vec![],
                hints: vec![],
                yaml_type: Some(YamlType::GitLabCI),
            }
        }
        Err(e) => ValidationResult {
            valid: false,
            errors: vec![e],
            warnings: vec![],
            diagnostics: vec![],
            hints: vec![],
            yaml_type: Some(YamlType::GitLabCI),
        },
    }
}

/// Validate a Docker Compose file
pub fn validate_docker_compose(content: &str) -> ValidationResult {
    let data = match parse_yaml(content) {
        Ok(d) => d,
        Err(e) => {
            return ValidationResult {
                valid: false,
                errors: vec![e],
                warnings: vec![],
                diagnostics: vec![],
                hints: vec![],
                yaml_type: Some(YamlType::DockerCompose),
            }
        }
    };

    match DockerCompose::from_value(data) {
        Ok(compose) => compose.validate(),
        Err(e) => ValidationResult {
            valid: false,
            errors: vec![e],
            warnings: vec![],
            diagnostics: vec![],
            hints: vec![],
            yaml_type: Some(YamlType::DockerCompose),
        },
    }
}

/// Validate a GitHub Actions workflow
pub fn validate_github_actions(content: &str) -> ValidationResult {
    let data = match parse_yaml(content) {
        Ok(d) => d,
        Err(e) => {
            return ValidationResult {
                valid: false,
                errors: vec![e],
                warnings: vec![],
                diagnostics: vec![],
                hints: vec![],
                yaml_type: Some(YamlType::GitHubActions),
            }
        }
    };

    match GitHubActions::from_value(&data) {
        Ok(actions) => actions.validate(),
        Err(e) => ValidationResult {
            valid: false,
            errors: vec![e],
            warnings: vec![],
            diagnostics: vec![],
            hints: vec![],
            yaml_type: Some(YamlType::GitHubActions),
        },
    }
}

/// Validate a Prometheus configuration
pub fn validate_prometheus(content: &str) -> ValidationResult {
    let data = match parse_yaml(content) {
        Ok(d) => d,
        Err(e) => {
            return ValidationResult {
                valid: false,
                errors: vec![e],
                warnings: vec![],
                diagnostics: vec![],
                hints: vec![],
                yaml_type: Some(YamlType::Prometheus),
            }
        }
    };

    match PrometheusConfig::from_value(data) {
        Ok(config) => config.validate(),
        Err(e) => ValidationResult {
            valid: false,
            errors: vec![e],
            warnings: vec![],
            diagnostics: vec![],
            hints: vec![],
            yaml_type: Some(YamlType::Prometheus),
        },
    }
}

/// Validate an Alertmanager configuration
pub fn validate_alertmanager(content: &str) -> ValidationResult {
    let data = match parse_yaml(content) {
        Ok(d) => d,
        Err(e) => {
            return ValidationResult {
                valid: false,
                errors: vec![e],
                warnings: vec![],
                diagnostics: vec![],
                hints: vec![],
                yaml_type: Some(YamlType::Alertmanager),
            }
        }
    };

    match AlertmanagerConfig::from_value(data) {
        Ok(config) => config.validate(),
        Err(e) => ValidationResult {
            valid: false,
            errors: vec![e],
            warnings: vec![],
            diagnostics: vec![],
            hints: vec![],
            yaml_type: Some(YamlType::Alertmanager),
        },
    }
}

/// Validate a Helm values.yaml file
pub fn validate_helm_values(content: &str) -> ValidationResult {
    let data = match parse_yaml(content) {
        Ok(d) => d,
        Err(e) => {
            return ValidationResult {
                valid: false,
                errors: vec![e],
                warnings: vec![],
                diagnostics: vec![],
                hints: vec![],
                yaml_type: Some(YamlType::HelmValues),
            }
        }
    };

    match HelmValues::from_value(&data) {
        Ok(values) => values.validate(),
        Err(e) => ValidationResult {
            valid: false,
            errors: vec![e],
            warnings: vec![],
            diagnostics: vec![],
            hints: vec![],
            yaml_type: Some(YamlType::HelmValues),
        },
    }
}

/// Validate an Ansible playbook
pub fn validate_ansible(content: &str) -> ValidationResult {
    let data = match parse_yaml(content) {
        Ok(d) => d,
        Err(e) => {
            return ValidationResult {
                valid: false,
                errors: vec![e],
                warnings: vec![],
                diagnostics: vec![],
                hints: vec![],
                yaml_type: Some(YamlType::Ansible),
            }
        }
    };

    // Ansible playbooks are top-level arrays
    let playbook: Vec<AnsiblePlay> = match serde_json::from_value(data) {
        Ok(p) => p,
        Err(e) => {
            return ValidationResult {
                valid: false,
                errors: vec![format!("Failed to parse Ansible playbook: {e}")],
                warnings: vec![],
                diagnostics: vec![],
                hints: vec![],
                yaml_type: Some(YamlType::Ansible),
            }
        }
    };

    playbook.validate()
}

/// Auto-detect the YAML format and validate it.
///
/// This is the **main entry point** for one-call validation.  It:
///
/// 1. Splits multi-document YAML files (separated by `---`) and validates
///    each document independently, merging the results.
/// 2. Calls [`detect_yaml_type`] on the parsed content.
/// 3. Dispatches to the appropriate per-format validator.
///
/// The returned [`ValidationResult`] is always populated — if parsing
/// fails, `valid` is `false` and `errors` contains the parse error.
///
/// # Example
///
/// ```rust
/// use devops_validate::validator::validate_auto;
///
/// // Valid multi-replica deployment — no errors, but possible warnings
/// let yaml = r#"
/// apiVersion: apps/v1
/// kind: Deployment
/// metadata:
///   name: my-app
/// spec:
///   replicas: 3
///   selector:
///     matchLabels:
///       app: my-app
///   template:
///     metadata:
///       labels:
///         app: my-app
///     spec:
///       containers:
///         - name: app
///           image: my-app:v1.0.0
/// "#;
///
/// let result = validate_auto(yaml);
/// assert!(result.valid);
/// assert_eq!(result.yaml_type.unwrap().to_string(), "K8s Deployment");
/// ```
pub fn validate_auto(content: &str) -> ValidationResult {
    // Check for multi-document YAML
    let documents = split_yaml_documents(content);
    if documents.len() > 1 {
        return validate_multi_document(&documents);
    }

    let data = match parse_yaml(content) {
        Ok(d) => d,
        Err(e) => {
            return ValidationResult {
                valid: false,
                errors: vec![e],
                warnings: vec![],
                diagnostics: vec![],
                hints: vec![],
                yaml_type: None,
            }
        }
    };

    let yaml_type = detect_yaml_type(&data);
    match yaml_type {
        YamlType::K8sDeployment
        | YamlType::K8sService
        | YamlType::K8sConfigMap
        | YamlType::K8sSecret
        | YamlType::K8sIngress
        | YamlType::K8sHPA
        | YamlType::K8sCronJob
        | YamlType::K8sJob
        | YamlType::K8sPVC
        | YamlType::K8sNetworkPolicy
        | YamlType::K8sStatefulSet
        | YamlType::K8sDaemonSet
        | YamlType::K8sRole
        | YamlType::K8sClusterRole
        | YamlType::K8sRoleBinding
        | YamlType::K8sClusterRoleBinding
        | YamlType::K8sServiceAccount
        | YamlType::K8sGeneric => validate_k8s_manifest(content),
        YamlType::GitLabCI => validate_gitlab_ci(content),
        YamlType::DockerCompose => validate_docker_compose(content),
        YamlType::GitHubActions => validate_github_actions(content),
        YamlType::Prometheus => validate_prometheus(content),
        YamlType::Alertmanager => validate_alertmanager(content),
        YamlType::HelmValues => validate_helm_values(content),
        YamlType::Ansible => validate_ansible(content),
        YamlType::OpenAPI => ValidationResult {
            valid: true,
            errors: vec![],
            warnings: vec!["OpenAPI validation not yet implemented — parsed OK".to_string()],
            diagnostics: vec![],
            hints: vec![],
            yaml_type: Some(YamlType::OpenAPI),
        },
        YamlType::Generic => ValidationResult {
            valid: true,
            errors: vec![],
            warnings: vec!["Generic YAML — no schema validation applied".to_string()],
            diagnostics: vec![],
            hints: vec![],
            yaml_type: Some(YamlType::Generic),
        },
    }
}

// ═══════════════════════════════════════════════════════════════════════════
// Multi-document YAML support
// ═══════════════════════════════════════════════════════════════════════════

/// Split a YAML string into individual documents.
fn split_yaml_documents(content: &str) -> Vec<String> {
    let mut documents = Vec::new();
    let mut current = String::new();

    for line in content.lines() {
        if line.trim() == "---" && !current.trim().is_empty() {
            documents.push(current.clone());
            current.clear();
        } else if line.trim() != "---" {
            current.push_str(line);
            current.push('\n');
        }
    }
    if !current.trim().is_empty() {
        documents.push(current);
    }
    documents
}

/// Validate a multi-document YAML file and merge results.
fn validate_multi_document(documents: &[String]) -> ValidationResult {
    let mut all_errors = Vec::new();
    let mut all_warnings = Vec::new();
    let mut all_diagnostics = Vec::new();
    let mut all_valid = true;
    let mut types_seen = Vec::new();

    for (i, doc) in documents.iter().enumerate() {
        let prefix = format!("[doc {}] ", i + 1);
        let result = validate_auto(doc.trim());

        if !result.valid {
            all_valid = false;
        }
        for e in &result.errors {
            all_errors.push(format!("{}{}", prefix, e));
        }
        for w in &result.warnings {
            all_warnings.push(format!("{}{}", prefix, w));
        }
        for d in &result.diagnostics {
            all_diagnostics.push(Diagnostic {
                severity: d.severity.clone(),
                message: format!("{}{}", prefix, d.message),
                path: d.path.as_ref().map(|p| format!("{}{}", prefix, p)),
            });
        }
        if let Some(t) = &result.yaml_type {
            types_seen.push(format!("{}", t));
        }
    }

    let type_summary = if types_seen.is_empty() {
        "Generic".to_string()
    } else {
        types_seen.join(", ")
    };

    all_warnings.insert(
        0,
        format!(
            "Multi-document YAML: {} documents detected ({})",
            documents.len(),
            type_summary
        ),
    );

    ValidationResult {
        valid: all_valid,
        errors: all_errors,
        warnings: all_warnings,
        diagnostics: all_diagnostics,
        hints: vec![],
        yaml_type: Some(YamlType::Generic), // multi-doc is treated as generic container
    }
}

// ═══════════════════════════════════════════════════════════════════════════
// Semantic warning helpers
// ═══════════════════════════════════════════════════════════════════════════

/// Extended heuristic warnings for valid-but-risky K8s Deployments
fn collect_k8s_warnings(data: &serde_json::Value) -> Vec<String> {
    let mut warnings = Vec::new();
    let kind = data.get("kind").and_then(|v| v.as_str()).unwrap_or("");

    if kind == "Deployment" {
        let spec = data.get("spec");
        let replicas = spec
            .and_then(|s| s.get("replicas"))
            .and_then(|r| r.as_u64())
            .unwrap_or(1);

        if replicas == 1 {
            let ns = data
                .get("metadata")
                .and_then(|m| m.get("namespace"))
                .and_then(|n| n.as_str())
                .unwrap_or("default");
            warnings.push(format!(
                "replicas=1 in namespace '{}' — consider >=2 for high availability",
                ns
            ));
        }

        if let Some(containers) = spec
            .and_then(|s| s.get("template"))
            .and_then(|t| t.get("spec"))
            .and_then(|s| s.get("containers"))
            .and_then(|c| c.as_array())
        {
            for c in containers {
                let name = c.get("name").and_then(|n| n.as_str()).unwrap_or("?");

                // Resource limits
                let has_limits = c
                    .get("resources")
                    .and_then(|r| r.get("limits"))
                    .is_some();
                if !has_limits {
                    warnings.push(format!(
                        "Container '{}' has no resource limits — may cause OOM kills",
                        name
                    ));
                }

                // Probes
                let has_liveness = c.get("livenessProbe").is_some();
                let has_readiness = c.get("readinessProbe").is_some();
                if !has_liveness {
                    warnings.push(format!(
                        "Container '{}' has no livenessProbe — Kubernetes won't detect hangs",
                        name
                    ));
                }
                if !has_readiness {
                    warnings.push(format!(
                        "Container '{}' has no readinessProbe — traffic may be sent to unready pods",
                        name
                    ));
                }

                // Image tag: `:latest` or no tag
                if let Some(image) = c.get("image").and_then(|i| i.as_str())
                    && (image.ends_with(":latest") || !image.contains(':')) {
                        warnings.push(format!(
                            "Container '{}': image '{}' uses ':latest' or no tag — pin a specific version for reproducibility",
                            name, image
                        ));
                    }

                // imagePullPolicy in production
                if let Some(policy) = c.get("imagePullPolicy").and_then(|p| p.as_str())
                    && policy == "Never" {
                        warnings.push(format!(
                            "Container '{}': imagePullPolicy=Never — image must be pre-loaded on every node",
                            name
                        ));
                    }
            }
        }
    }

    warnings
}

/// Semantic warnings for K8s Service
fn validate_service_semantics(svc: &K8sService) -> Vec<String> {
    let mut warnings = Vec::new();
    if let Some(svc_type) = &svc.spec.service_type {
        if svc_type == "LoadBalancer" {
            warnings.push("type=LoadBalancer creates a cloud load balancer — ensure this is intentional (cost implications)".to_string());
        }
        if svc_type == "NodePort" {
            for port in &svc.spec.ports {
                if port.port > 32767 {
                    warnings.push(format!(
                        "Port {} exceeds default NodePort range (30000-32767)",
                        port.port
                    ));
                }
            }
        }
    }
    if svc.spec.selector.is_empty() {
        warnings.push("Service has an empty selector — will match no pods".to_string());
    }
    warnings
}

/// Semantic warnings for K8s Secret
fn validate_secret_semantics(sec: &K8sSecret) -> Vec<String> {
    let mut warnings = Vec::new();
    if sec.data.is_empty() && sec.string_data.is_none() {
        warnings.push("Secret has no data and no stringData".to_string());
    }
    // Check if 'data' values look like valid base64
    for (key, val) in &sec.data {
        if base64_decode_check(val).is_err() {
            warnings.push(format!(
                "Secret key '{}': value does not appear to be valid base64",
                key
            ));
        }
    }
    warnings
}

/// Quick check if a string is valid base64.
fn base64_decode_check(s: &str) -> Result<(), ()> {
    // Simple validation: base64 chars + padding
    let stripped = s.trim();
    if stripped.is_empty() {
        return Ok(());
    }
    let base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r ";
    if stripped.chars().all(|c| base64_chars.contains(c)) {
        Ok(())
    } else {
        Err(())
    }
}

// ═══════════════════════════════════════════════════════════════════════════
// Helpers
// ═══════════════════════════════════════════════════════════════════════════

fn value_type_name(v: &serde_json::Value) -> &'static str {
    match v {
        serde_json::Value::Null => "null",
        serde_json::Value::Bool(_) => "boolean",
        serde_json::Value::Number(_) => "number",
        serde_json::Value::String(_) => "string",
        serde_json::Value::Array(_) => "array",
        serde_json::Value::Object(_) => "object",
    }
}

fn format_serde_errors(e: &serde_json::Error) -> Vec<String> {
    vec![e.to_string()]
}

// ═══════════════════════════════════════════════════════════════════════════
// Tests
// ═══════════════════════════════════════════════════════════════════════════

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

    // ── Detection ──────────────────────────────────────────────────────

    #[test]
    fn detect_k8s_deployment() {
        let yaml = r#"apiVersion: apps/v1
kind: Deployment
metadata:
  name: test
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test
  template:
    metadata:
      labels:
        app: test
    spec:
      containers:
        - name: app
          image: nginx:1.25"#;
        let data = parse_yaml(yaml).unwrap();
        assert_eq!(detect_yaml_type(&data), YamlType::K8sDeployment);
    }

    #[test]
    fn detect_k8s_ingress() {
        let yaml = r#"apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: test
spec:
  rules: []"#;
        let data = parse_yaml(yaml).unwrap();
        assert_eq!(detect_yaml_type(&data), YamlType::K8sIngress);
    }

    #[test]
    fn detect_k8s_generic_unknown_kind() {
        let yaml = r#"apiVersion: custom.io/v1
kind: MyCustomResource
metadata:
  name: test
spec:
  foo: bar"#;
        let data = parse_yaml(yaml).unwrap();
        assert_eq!(detect_yaml_type(&data), YamlType::K8sGeneric);
    }

    #[test]
    fn detect_docker_compose() {
        let yaml = r#"services:
  web:
    image: nginx
    ports:
      - "8080:80"
  db:
    image: postgres:15"#;
        let data = parse_yaml(yaml).unwrap();
        assert_eq!(detect_yaml_type(&data), YamlType::DockerCompose);
    }

    #[test]
    fn detect_github_actions() {
        let yaml = r#"name: CI
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4"#;
        let data = parse_yaml(yaml).unwrap();
        assert_eq!(detect_yaml_type(&data), YamlType::GitHubActions);
    }

    #[test]
    fn detect_gitlab_ci() {
        let yaml = r#"stages:
  - build
  - test
build_job:
  stage: build
  script:
    - echo hello"#;
        let data = parse_yaml(yaml).unwrap();
        assert_eq!(detect_yaml_type(&data), YamlType::GitLabCI);
    }

    #[test]
    fn detect_prometheus() {
        let yaml = r#"global:
  scrape_interval: 15s
scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: ['localhost:9090']"#;
        let data = parse_yaml(yaml).unwrap();
        assert_eq!(detect_yaml_type(&data), YamlType::Prometheus);
    }

    #[test]
    fn detect_alertmanager() {
        let yaml = r#"route:
  receiver: default
receivers:
  - name: default"#;
        let data = parse_yaml(yaml).unwrap();
        assert_eq!(detect_yaml_type(&data), YamlType::Alertmanager);
    }

    // ── Validation ─────────────────────────────────────────────────────

    #[test]
    fn validate_valid_deployment() {
        let yaml = r#"apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: app
          image: myapp:v1.2.3
          ports:
            - containerPort: 8080
          resources:
            limits:
              memory: "128Mi"
              cpu: "500m"
            requests:
              memory: "64Mi"
              cpu: "250m"
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080"#;
        let result = validate_auto(yaml);
        assert!(result.valid, "Expected valid, got errors: {:?}", result.errors);
    }

    #[test]
    fn validate_hpa_min_gt_max() {
        let yaml = r#"apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: test
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: test
  minReplicas: 10
  maxReplicas: 5"#;
        let result = validate_auto(yaml);
        assert!(!result.valid);
        assert!(result.errors.iter().any(|e| e.contains("minReplicas")));
    }

    #[test]
    fn validate_cronjob_invalid_schedule() {
        let yaml = r#"apiVersion: batch/v1
kind: CronJob
metadata:
  name: test
spec:
  schedule: "not a cron"
  jobTemplate:
    spec:
      template:
        metadata:
          labels:
            app: test
        spec:
          containers:
            - name: job
              image: busybox
          restartPolicy: OnFailure"#;
        let result = validate_auto(yaml);
        assert!(!result.valid);
        assert!(result.errors.iter().any(|e| e.contains("cron schedule")));
    }

    #[test]
    fn validate_docker_compose_no_image() {
        let yaml = r#"services:
  web:
    ports:
      - "8080:80""#;
        let result = validate_auto(yaml);
        assert!(!result.valid);
        assert!(result.errors.iter().any(|e| e.contains("image") || e.contains("build")));
    }

    #[test]
    fn validate_github_actions_no_on() {
        let yaml = r#"name: CI
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: echo hello"#;
        // This still detects as GitLabCI due to heuristics, but let's test explicit
        let result = validate_github_actions(yaml);
        assert!(!result.valid);
        assert!(result.errors.iter().any(|e| e.contains("'on'")));
    }

    #[test]
    fn validate_prometheus_duplicate_jobs() {
        let yaml = r#"scrape_configs:
  - job_name: myapp
    static_configs:
      - targets: ['localhost:9090']
  - job_name: myapp
    static_configs:
      - targets: ['localhost:8080']"#;
        let result = validate_prometheus(yaml);
        assert!(!result.valid);
        assert!(result.errors.iter().any(|e| e.contains("Duplicate job_name")));
    }

    #[test]
    fn validate_alertmanager_missing_receiver() {
        let yaml = r#"route:
  receiver: missing
receivers:
  - name: default"#;
        let result = validate_alertmanager(yaml);
        assert!(!result.valid);
        assert!(result.errors.iter().any(|e| e.contains("not defined")));
    }

    #[test]
    fn validate_k8s_generic_unknown_kind() {
        let yaml = r#"apiVersion: custom.io/v1
kind: MyWidget
metadata:
  name: test
spec:
  replicas: 1"#;
        let result = validate_auto(yaml);
        assert!(result.valid);
        assert!(result.warnings.iter().any(|w| w.contains("no specific validator")));
    }

    // ── Multi-document ─────────────────────────────────────────────────

    #[test]
    fn validate_multi_document_yaml() {
        let yaml = r#"apiVersion: v1
kind: ConfigMap
metadata:
  name: test
data:
  key: value
---
apiVersion: v1
kind: Service
metadata:
  name: test
spec:
  selector:
    app: test
  ports:
    - port: 80"#;
        let result = validate_auto(yaml);
        assert!(result.valid);
        assert!(result.warnings.iter().any(|w| w.contains("Multi-document")));
    }

    #[test]
    fn split_documents() {
        let yaml = "a: 1\n---\nb: 2\n---\nc: 3";
        let docs = split_yaml_documents(yaml);
        assert_eq!(docs.len(), 3);
    }

    // ── Secret validation ──────────────────────────────────────────────

    #[test]
    fn validate_secret_invalid_base64() {
        let yaml = r#"apiVersion: v1
kind: Secret
metadata:
  name: test
data:
  password: "not-valid-base64!@#""#;
        let result = validate_auto(yaml);
        assert!(result.valid); // structurally valid
        assert!(result.warnings.iter().any(|w| w.contains("base64")));
    }
}
