//! Controller for the `GitHook` CRD — Deployment + ConfigMap + PVC.

use crate::context::ReconcileState;
use crate::controller::app::{apply_deployment, patch_status};
use crate::controller::helpers::{child_meta, env_var_to_json, merge_env, owner_ref, query_deployment_status, std_labels};
use crate::crd::{GitHook, GitHookSpec, JsonResource};
use serde_json::{Value, json};
use std::sync::Arc;
use tracing::info;

pub async fn reconcile(gh: Arc<GitHook>, ctx: Arc<ReconcileState>) -> Result<(), kube::Error> {
    let ns = gh.metadata.namespace.as_deref().unwrap_or("default");
    let name = gh.metadata.name.as_deref().unwrap_or("");
    let spec = &gh.spec;
    let client = &ctx.client;

    let or = owner_ref(&gh.metadata, &gh.api_version, &gh.kind);
    let labels = std_labels();
    let cm_name = format!("{}-config", name);

    // ---- PVC ----
    let pvc = build_pvc(ns, name, spec, &or, &labels);
    apply_pvc(client, ns, &format!("{}-data", name), &pvc).await?;

    // ---- ConfigMap ----
    let configmap = build_configmap(ns, &cm_name, &or, &labels);
    apply_configmap(client, ns, &cm_name, &configmap).await?;

    // ---- Deployment ----
    let deployment = build_deployment(ns, name, &cm_name, spec, &or, &labels);
    apply_deployment(client, ns, name, &deployment).await?;

    let (ready_replicas, phase) = query_deployment_status(client, ns, name).await?;
    let status = json!({ "status": { "readyReplicas": ready_replicas, "phase": phase } });
    patch_status::<GitHook>(client, ns, name, &status).await?;

    Ok(())
}

fn build_configmap(
    ns: &str,
    cm_name: &str,
    or: &crate::crd::OwnerReference,
    labels: &std::collections::BTreeMap<String, String>,
) -> Value {
    let pool_config = serde_yaml::to_string(&serde_json::json!({
        "max_concurrent": 8,
        "cpu_threshold": 80.0,
        "redis_list_prefix": "{hook}",
        "redis_log_channel": "hook:logs",
        "redis_block_timeout_secs": 5,
        "redis_max_retries": 3,
    }))
    .unwrap_or_default();

    json!({
        "metadata": child_meta(cm_name, ns, or, labels.clone()),
        "data": {
            "pool.yaml": pool_config
        }
    })
}

fn build_deployment(
    ns: &str,
    name: &str,
    cm_name: &str,
    spec: &GitHookSpec,
    or: &crate::crd::OwnerReference,
    labels: &std::collections::BTreeMap<String, String>,
) -> Value {
    let env = merge_env(&[], &spec.env);
    let image = if spec.image.is_empty() {
        "myapp/git-hook:latest".to_string()
    } else {
        spec.image.clone()
    };
    let pull = if spec.image_pull_policy.is_empty() {
        "IfNotPresent".to_string()
    } else {
        spec.image_pull_policy.clone()
    };
    let resources = super::app::build_resources(&spec.resources);

    // Add WORKER_ID and APP_REPOS_ROOT env vars
    let worker_id = spec
        .worker_id
        .clone()
        .unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
    let mut env_vars: Vec<serde_json::Value> = env.iter().map(env_var_to_json).collect();
    env_vars.push(json!({ "name": "HOOK_POOL_WORKER_ID", "value": worker_id }));
    env_vars.push(json!({ "name": "APP_REPOS_ROOT", "value": "/data/repos" }));

    json!({
        "metadata": child_meta(name, ns, or, labels.clone()),
        "spec": {
            "replicas": 1,
            "selector": { "matchLabels": labels },
            "template": {
                "metadata": { "labels": labels.clone() },
                "spec": {
                    "containers": [{
                        "name": "git-hook",
                        "image": image,
                        "env": env_vars,
                        "imagePullPolicy": pull,
                        "resources": resources,
                        "volumeMounts": [
                            { "name": "hook-data", "mountPath": "/data" },
                            { "name": "hook-config", "mountPath": "/config" }
                        ]
                    }],
                    "volumes": [
                        {
                            "name": "hook-data",
                            "persistentVolumeClaim": { "claimName": format!("{}-data", name) }
                        },
                        {
                            "name": "hook-config",
                            "configMap": { "name": cm_name }
                        }
                    ]
                }
            }
        }
    })
}

async fn apply_configmap(
    client: &kube::Client,
    ns: &str,
    name: &str,
    body: &Value,
) -> Result<(), kube::Error> {
    let api: kube::Api<JsonResource> = kube::Api::namespaced(client.clone(), ns);
    let jr = JsonResource::new(Default::default(), body.clone());
    match api.get(name).await {
        Ok(_) => {
            let _ = api
                .replace(name, &kube::api::PostParams::default(), &jr)
                .await?;
            Ok(())
        }
        Err(kube::Error::Api(e)) if e.code == 404 => {
            info!(name, ns, "creating git-hook configmap");
            let _ = api.create(&kube::api::PostParams::default(), &jr).await?;
            Ok(())
        }
        Err(e) => Err(e),
    }
}

fn build_pvc(
    ns: &str,
    name: &str,
    spec: &GitHookSpec,
    or: &crate::crd::OwnerReference,
    labels: &std::collections::BTreeMap<String, String>,
) -> Value {
    json!({
        "metadata": child_meta(&format!("{}-data", name), ns, or, labels.clone()),
        "spec": {
            "accessModes": ["ReadWriteOnce"],
            "resources": { "requests": { "storage": spec.storage_size } }
        }
    })
}

async fn apply_pvc(
    client: &kube::Client,
    ns: &str,
    name: &str,
    body: &Value,
) -> Result<(), kube::Error> {
    let api: kube::Api<JsonResource> = kube::Api::namespaced(client.clone(), ns);
    let jr = JsonResource::new(Default::default(), body.clone());
    match api.get(name).await {
        Ok(_) => {
            // PVCs are immutable except for labels/annotations — skip update
            Ok(())
        }
        Err(kube::Error::Api(e)) if e.code == 404 => {
            info!(name, ns, "creating git-hook PVC");
            let _ = api.create(&kube::api::PostParams::default(), &jr).await?;
            Ok(())
        }
        Err(e) => Err(e),
    }
}
