// Project:   scalo
// File:      src/deployment/generate/argocd.rs
// Purpose:   ArgoCD Application generation
// Language:  Rust
//
// License:   Apache-2.0
// Copyright: (c) 2026 HYPERI PTY LIMITED

#![allow(clippy::format_push_string)]

use crate::deployment::contract::DeploymentContract;

// ============================================================================
// ArgoCD Application
// ============================================================================

/// Configuration for ArgoCD `Application` generation.
///
/// All fields have sensible defaults. The Helm chart that the Application
/// points at is the one [`generate_chart`](crate::deployment::generate_chart)
/// writes to a repo path.
#[derive(Debug, Clone)]
pub struct ArgocdConfig {
    /// ArgoCD namespace (where the Application CR lives). Default: `argocd`.
    pub argocd_namespace: String,
    /// Destination namespace for the deployed app. Default: `dfe`.
    pub dest_namespace: String,
    /// Destination cluster (`server` field). Default: `https://kubernetes.default.svc`.
    pub dest_server: String,
    /// Source git repo URL (where the Helm chart lives). Required.
    pub repo_url: String,
    /// Source git revision (branch/tag). Default: `main`.
    pub target_revision: String,
    /// Path within the repo to the Helm chart. Default: `chart`.
    pub chart_path: String,
    /// ArgoCD project. Default: `default`.
    pub project: String,
    /// Sync wave (lower runs first). Default: [`crate::deployment::WAVE_APPS`].
    pub sync_wave: i32,
    /// Additional `ignoreDifferences` entries appended to the canonical
    /// defaults (HPA replicas, ClusterIP, webhook caBundle). Each entry
    /// is a raw YAML fragment matching the ArgoCD `ignoreDifferences` item
    /// shape. The fragment must start with `- group:` and use two-space
    /// indentation internally (the generator indents each line by four
    /// spaces to nest under `ignoreDifferences:`).
    ///
    /// Example:
    /// ```text
    /// "- group: apps\n  kind: Deployment\n  jsonPointers:\n    - /spec/template/spec/containers/0/image"
    /// ```
    ///
    /// Defaults to empty (no extra entries beyond the canonical defaults).
    pub extra_ignore_differences: Vec<String>,
}

impl Default for ArgocdConfig {
    fn default() -> Self {
        Self {
            argocd_namespace: "argocd".into(),
            dest_namespace: "dfe".into(),
            dest_server: "https://kubernetes.default.svc".into(),
            repo_url: String::new(),
            target_revision: "main".into(),
            chart_path: "chart".into(),
            project: "default".into(),
            sync_wave: crate::deployment::WAVE_APPS,
            extra_ignore_differences: Vec::new(),
        }
    }
}

/// Generate an ArgoCD `Application` custom-resource YAML from the deployment
/// contract.
///
/// The CR points at a Helm chart in a git repo (typically the chart that
/// [`generate_chart`](crate::deployment::generate_chart) produces). Apply with:
///
/// ```bash
/// kubectl apply -n argocd -f application.yaml
/// ```
///
/// # Example
///
/// ```rust,no_run
/// use scalo::deployment::{ArgocdConfig, generate_argocd_application};
/// # use scalo::deployment::DeploymentContract;
/// # let contract: DeploymentContract = unimplemented!();
/// let argo = ArgocdConfig {
///     repo_url: "https://github.com/hyperi-io/dfe-loader".into(),
///     ..Default::default()
/// };
/// let yaml = generate_argocd_application(&contract, &argo, None);
/// ```
#[must_use]
pub fn generate_argocd_application(
    contract: &DeploymentContract,
    argo: &ArgocdConfig,
    identity: Option<&crate::deployment::ContractIdentity>,
) -> String {
    // Contract Identity Annotation Scheme v1 -- three extra annotations
    // alongside the existing sync-wave entry. Indented to match the
    // 4-space `metadata.annotations:` block below.
    let identity_block = identity
        .map(|id| format!("\n{ann}", ann = id.as_yaml_annotations(4)))
        .unwrap_or_default();

    // Build the extras block: each entry is a raw YAML fragment starting with
    // `- group: ...`. Indent every line by 4 spaces to nest under
    // `ignoreDifferences:`.
    let extras_block = if argo.extra_ignore_differences.is_empty() {
        String::new()
    } else {
        let mut buf = String::new();
        for entry in &argo.extra_ignore_differences {
            for line in entry.lines() {
                buf.push_str("    ");
                buf.push_str(line);
                buf.push('\n');
            }
        }
        buf
    };

    format!(
        r#"# AUTOGENERATED -- do not edit by hand.
# Generated by scalo::deployment::generate_argocd_application()
# Schema version: {schema_version}
# Source contract: {app_name}::deployment::contract()
# Regenerate with: `{binary} emit-argocd > application.yaml`
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: {app_name}
  namespace: {argocd_namespace}
  annotations:
    argocd.argoproj.io/sync-wave: "{sync_wave}"{identity_block}
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: {project}

  source:
    repoURL: {repo_url}
    targetRevision: {target_revision}
    path: {chart_path}
    helm:
      releaseName: {app_name}

  destination:
    server: {dest_server}
    namespace: {dest_namespace}

  syncPolicy:
    automated:
      prune: true
      selfHeal: true
      allowEmpty: false
    syncOptions:
      - CreateNamespace=true
      - PrunePropagationPolicy=foreground
      - PruneLast=true
      - ServerSideApply=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

  ignoreDifferences:
    - group: apps
      kind: Deployment
      jsonPointers:
        - /spec/replicas
    - group: ""
      kind: Service
      jsonPointers:
        - /spec/clusterIP
        - /spec/clusterIPs
    - group: admissionregistration.k8s.io
      kind: ValidatingWebhookConfiguration
      jqPathExpressions:
        - .webhooks[].clientConfig.caBundle
{extras_block}"#,
        schema_version = contract.schema_version,
        app_name = contract.app_name,
        binary = contract.binary(),
        argocd_namespace = argo.argocd_namespace,
        sync_wave = argo.sync_wave,
        project = argo.project,
        repo_url = argo.repo_url,
        target_revision = argo.target_revision,
        chart_path = argo.chart_path,
        dest_server = argo.dest_server,
        dest_namespace = argo.dest_namespace,
        extras_block = extras_block,
        identity_block = identity_block,
    )
}
