//! Controller for the `Migrate` CRD — creates a one-shot Job on reconcile.
//!
//! The Job is re-created on every reconcile (idempotent). Once the Job
//! succeeds, the Migrate status is patched to "Completed".

use crate::context::ReconcileState;
use crate::controller::helpers::{child_meta, env_var_to_json, merge_env, owner_ref, std_labels};
use crate::crd::{JsonResource, K8sObjectMeta, Migrate, MigrateSpec};
use chrono::Utc;
use serde_json::{Value, json};
use std::sync::Arc;
use tracing::info;

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

    let or = owner_ref(&mig.metadata, &mig.api_version, &mig.kind);
    let labels = std_labels();

    let job_meta = child_meta(name, ns, &or, labels.clone());
    let job = build_job(spec, job_meta, &labels);

    // Use JsonResource for Job create/replace (spec part)
    let jobs_api: kube::Api<JsonResource> = kube::Api::namespaced(client.clone(), ns);
    match jobs_api.get(name).await {
        Ok(_) => {
            info!(name, ns, "replacing migrate job");
            let _ = jobs_api
                .replace(name, &kube::api::PostParams::default(), &job)
                .await?;
        }
        Err(kube::Error::Api(e)) if e.code == 404 => {
            info!(name, ns, "creating migrate job");
            let _ = jobs_api.create(&kube::api::PostParams::default(), &job).await?;
        }
        Err(e) => return Err(e),
    }

    // Query real Job status via k8s-openapi (reads status subresource)
    let job_status = query_job_status(client, ns, name).await?;
    patch_migrate_status_from_job(client, ns, name, &job_status).await?;

    Ok(())
}

/// Query actual Job status and derive Migrate phase + timestamps.
async fn query_job_status(
    client: &kube::Client,
    ns: &str,
    name: &str,
) -> Result<JobStatusResult, kube::Error> {
    use k8s_openapi::api::batch::v1::Job;
    let api: kube::Api<Job> = kube::Api::namespaced(client.clone(), ns);
    match api.get(name).await {
        Ok(job) => {
            let status = job.status.as_ref();
            let succeeded = status.and_then(|s| s.succeeded).unwrap_or(0);
            let failed = status.and_then(|s| s.failed).unwrap_or(0);
            let active = status.and_then(|s| s.active).unwrap_or(0);

            let phase = if succeeded > 0 {
                "Completed"
            } else if failed > 0 {
                "Failed"
            } else if active > 0 {
                "Running"
            } else {
                "Pending"
            };

            let start_time = status.and_then(|s| s.start_time.as_ref()).map(|t| t.0.to_rfc3339());
            let completion_time = status.and_then(|s| s.completion_time.as_ref()).map(|t| t.0.to_rfc3339());

            Ok(JobStatusResult { phase: phase.to_string(), start_time, completion_time })
        }
        Err(kube::Error::Api(e)) if e.code == 404 => {
            Ok(JobStatusResult { phase: "Pending".to_string(), start_time: None, completion_time: None })
        }
        Err(e) => Err(e),
    }
}

struct JobStatusResult {
    phase: String,
    start_time: Option<String>,
    completion_time: Option<String>,
}

async fn patch_migrate_status_from_job(
    client: &kube::Client,
    ns: &str,
    name: &str,
    job: &JobStatusResult,
) -> Result<(), kube::Error> {
    let api: kube::Api<JsonResource> = kube::Api::namespaced(client.clone(), ns);
    let mut status_obj = json!({ "phase": job.phase });
    if let Some(ref st) = job.start_time {
        status_obj["startTime"] = json!(st);
    }
    if let Some(ref ct) = job.completion_time {
        status_obj["completionTime"] = json!(ct);
    }
    let patch = json!({ "status": status_obj });
    let _ = api
        .patch_status(
            name,
            &kube::api::PatchParams::default(),
            &kube::api::Patch::Merge(&patch),
        )
        .await?;
    Ok(())
}

fn build_job(
    spec: &MigrateSpec,
    meta: K8sObjectMeta,
    labels: &std::collections::BTreeMap<String, String>,
) -> JsonResource {
    let image = if spec.image.is_empty() {
        "myapp/migrate:latest".to_string()
    } else {
        spec.image.clone()
    };

    let env = merge_env(&[], &spec.env);
    let env_vars: Vec<Value> = env.iter().map(env_var_to_json).collect();

    let cmd_parts: Vec<&str> = spec.command.split_whitespace().collect();
    let cmd: Vec<&str> = if cmd_parts.is_empty() {
        vec!["up"]
    } else {
        cmd_parts
    };

    let now = Utc::now().to_rfc3339();

    let mut meta_with_anno = meta.clone();
    meta_with_anno.annotations = Some(std::collections::BTreeMap::from([(
        "code.dev/last-migrate".to_string(),
        now,
    )]));

    let body = json!({
        "metadata": meta_with_anno,
        "spec": {
            "backoffLimit": spec.backoff_limit,
            "ttlSecondsAfterFinished": 300,
            "template": {
                "metadata": {
                    "labels": labels.clone()
                },
                "spec": {
                    "restartPolicy": "Never",
                    "containers": [{
                        "name": "migrate",
                        "image": image,
                        "command": ["/app/migrate"],
                        "args": cmd,
                        "env": env_vars,
                        "imagePullPolicy": "IfNotPresent"
                    }]
                }
            }
        }
    });

    JsonResource::new(meta, body)
}
