"""
Export EDA CRD resources from a Kubernetes cluster to YAML files.

Uses kubectl only (no EDA URL or Keycloak). Ensure kubectl is in PATH and
kubeconfig (or in-cluster config) has credentials for the target namespace.
"""

import json
import logging
import os
import subprocess
from collections.abc import Callable
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path

import yaml

logger = logging.getLogger(__name__)


def run_kubectl(args: list[str]) -> dict:
    result = subprocess.run(
        ["kubectl", *args],
        check=True,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        text=True,
    )
    return json.loads(result.stdout)


def get_eda_crd_resources(group_suffix: str) -> list[str]:
    crds = run_kubectl(["get", "crd", "-o", "json"])
    resources: list[str] = []
    for crd in crds.get("items", []):
        spec = crd.get("spec", {})
        group = spec.get("group")
        if not group or not group.endswith(group_suffix):
            continue
        plural = spec.get("names", {}).get("plural")
        if plural:
            resources.append(f"{plural}.{group}")
    return resources


def _export_resource(
    resource: str,
    namespace: str,
    set_namespace: str | None,
) -> dict:
    """Fetch one resource and trim metadata. Returns {status, ...} with either ok+filename+content or error+message or skip."""
    try:
        data = run_kubectl(["get", resource, "-n", namespace, "-o", "json"])
    except subprocess.CalledProcessError as e:
        msg = (e.stderr or str(e) or "kubectl failed").strip() or "kubectl failed"
        return {"status": "error", "resource": resource, "message": msg}
    items = data.get("items", [])
    if not items:
        return {"status": "skip", "resource": resource}

    plural = resource.split(".", 1)[0]
    suffix = resource.split(".", 1)[1].replace("eda.", "", 1)
    filename = f"{plural}.{suffix}.yaml"

    for item in items:
        metadata = item.get("metadata") or {}
        labels = metadata.get("labels")
        item.pop("status", None)
        filtered_metadata = {
            "name": metadata.get("name"),
            "namespace": set_namespace or metadata.get("namespace"),
        }
        if labels:
            filtered_metadata["labels"] = labels
        annotations = metadata.get("annotations")
        if annotations:
            filtered_metadata["annotations"] = annotations
        item["metadata"] = filtered_metadata

    parts = []
    for index, item in enumerate(items):
        if index:
            parts.append("---\n")
        parts.append(yaml.safe_dump(item, sort_keys=False))
    content = "".join(parts)
    return {"status": "ok", "filename": filename, "content": content}


def run_export(
    namespace: str,
    out_dir: str | Path,
    group: str = "eda.nokia.com",
    set_namespace: str | None = None,
    on_result: Callable[[str, str, Path | None], None] | None = None,
) -> dict:
    """
    Export EDA CRs to YAML files under out_dir. Uses kubectl only.

    If on_result is provided, it is called as each resource is processed:
      on_result("ok", filename, path) for success
      on_result("error", message, None) for failure
    Results are still returned so the caller can print a summary.

    Returns dict with:
      written: list of (filename, absolute_path)
      errors: list of error messages (one per failed resource)
    """
    out_path = Path(out_dir).resolve()
    out_path.mkdir(parents=True, exist_ok=True)

    try:
        resources = get_eda_crd_resources(group)
    except subprocess.CalledProcessError as e:
        msg = (e.stderr or str(e) or "kubectl failed").strip() or "kubectl failed"
        err_msg = f"CRD list: {msg}"
        if on_result:
            on_result("error", err_msg, None)
        return {"written": [], "errors": [err_msg]}
    except json.JSONDecodeError as e:
        err_msg = f"kubectl returned invalid JSON: {e}"
        if on_result:
            on_result("error", err_msg, None)
        return {"written": [], "errors": [err_msg]}

    if not resources:
        err_msg = f"No CRDs for group {group}"
        if on_result:
            on_result("error", err_msg, None)
        return {"written": [], "errors": [err_msg]}

    written: list[tuple[str, Path]] = []
    errors: list[str] = []
    max_workers = os.cpu_count() or 1

    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = {
            executor.submit(_export_resource, resource, namespace, set_namespace): resource
            for resource in resources
        }
        for future in as_completed(futures):
            out = future.result()
            if out["status"] == "ok":
                filepath = out_path / out["filename"]
                filepath.write_text(out["content"], encoding="utf-8")
                written.append((out["filename"], filepath))
                if on_result:
                    on_result("ok", f"{out['filename']} (Success)", filepath)
            elif out["status"] == "error":
                msg = out["message"]
                if "forbidden" in msg.lower():
                    err_msg = f"{out['resource']} (Forbidden)"
                else:
                    err_msg = f"{out['resource']}: {msg}"
                errors.append(err_msg)
                if on_result:
                    on_result("error", err_msg, None)

    return {"written": written, "errors": errors}
