package doctor

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"strings"

	"github.com/tidbcloud/controlplane9/internal/host"
)

// podMetadata holds Kubernetes pod metadata fields.
type podMetadata struct {
	Name      string `json:"name"`
	Namespace string `json:"namespace"`
}

// podCondition holds a single pod condition.
type podCondition struct {
	Type   string `json:"type"`
	Status string `json:"status"`
}

// containerStatus holds a single container's status.
type containerStatus struct {
	Name         string `json:"name"`
	Ready        bool   `json:"ready"`
	RestartCount int    `json:"restartCount"`
}

// podStatus holds the status section of a pod.
type podStatus struct {
	Phase             string            `json:"phase"`
	Conditions        []podCondition    `json:"conditions"`
	ContainerStatuses []containerStatus `json:"containerStatuses"`
}

// podItem is a minimal representation of a Kubernetes pod from kubectl JSON output.
type podItem struct {
	Metadata podMetadata `json:"metadata"`
	Status   podStatus   `json:"status"`
}

type podList struct {
	Items []podItem `json:"items"`
}

// nodeMetadata holds Kubernetes node metadata fields.
type nodeMetadata struct {
	Name string `json:"name"`
}

// nodeUsage holds resource usage for a node.
type nodeUsage struct {
	CPU    string `json:"cpu"`
	Memory string `json:"memory"`
}

// nodeMetricsItem holds metrics for a single node.
type nodeMetricsItem struct {
	Metadata nodeMetadata `json:"metadata"`
	Usage    nodeUsage    `json:"usage"`
}

// nodeMetrics is a minimal representation of kubectl top node JSON output.
type nodeMetrics struct {
	Items []nodeMetricsItem `json:"items"`
}

// CheckPodStatus checks the status of all pods in the selected scope.
func CheckPodStatus(r *Result, selected *host.Session, allNamespaces bool) {
	args, scope := podQueryArgs(selected, allNamespaces)
	output, err := runKubectlWithTimeout(selected, args...)
	if err != nil {
		r.Warn("pod-status", "could not query pod status (cluster may not be running)", err.Error())
		return
	}

	var pods podList
	if err := json.Unmarshal(output, &pods); err != nil {
		r.Warn("pod-status", "could not parse pod status", err.Error())
		return
	}

	if len(pods.Items) == 0 {
		r.Warn("pod-status", "no pods found in "+scope, "cluster may not have services deployed")
		return
	}

	var notReady []string
	var crashLoop []string
	for _, pod := range pods.Items {
		if pod.Status.Phase != "Running" && pod.Status.Phase != "Succeeded" {
			notReady = append(notReady, fmt.Sprintf("%s (%s)", pod.Metadata.Name, pod.Status.Phase))
		}
		for _, cs := range pod.Status.ContainerStatuses {
			if cs.RestartCount > 5 {
				crashLoop = append(crashLoop, fmt.Sprintf("%s/%s (restarts: %d)",
					pod.Metadata.Name, cs.Name, cs.RestartCount))
			}
		}
	}

	if len(notReady) > 0 || len(crashLoop) > 0 {
		details := ""
		if len(notReady) > 0 {
			details += "not ready: " + strings.Join(notReady, ", ")
		}
		if len(crashLoop) > 0 {
			if details != "" {
				details += "\n"
			}
			details += "crash-looping: " + strings.Join(crashLoop, ", ")
		}
		r.Warn("pod-status", fmt.Sprintf("%d pod(s) not ready, %d crash-looping", len(notReady), len(crashLoop)), details)
		return
	}

	r.Pass("pod-status", fmt.Sprintf("all %d pod(s) running/succeeded", len(pods.Items)))
}

// CheckReadiness runs readiness probes for deployed services.
func CheckReadiness(r *Result, selected *host.Session, allNamespaces bool) {
	args, _ := podQueryArgs(selected, allNamespaces)
	output, err := runKubectlWithTimeout(selected, args...)
	if err != nil {
		r.Warn("readiness", "could not query pods for readiness check", err.Error())
		return
	}

	var pods podList
	if err := json.Unmarshal(output, &pods); err != nil {
		r.Warn("readiness", "could not parse pods for readiness check", err.Error())
		return
	}

	var unready []string
	total := 0
	for _, pod := range pods.Items {
		if pod.Status.Phase != "Running" {
			continue
		}
		for _, cond := range pod.Status.Conditions {
			if cond.Type == "Ready" {
				total++
				if cond.Status != "True" {
					unready = append(unready, pod.Metadata.Name)
				}
			}
		}
	}

	if len(unready) > 0 {
		r.Warn("readiness", fmt.Sprintf("%d/%d pod(s) not ready", len(unready), total),
			strings.Join(unready, ", "))
		return
	}
	if total == 0 {
		r.Warn("readiness", "no running pods found for readiness check", "")
		return
	}
	r.Pass("readiness", fmt.Sprintf("all %d pod(s) ready", total))
}

// CheckResources reports resource usage for the cluster nodes via the metrics API.
func CheckResources(r *Result, selected *host.Session) {
	output, err := runKubectlWithTimeout(selected, "get", "--raw", "/apis/metrics.k8s.io/v1beta1/nodes")
	if err != nil {
		r.Warn("resources", "could not query resource usage from the metrics API", err.Error())
		return
	}

	var metrics nodeMetrics
	if err := json.Unmarshal(output, &metrics); err != nil {
		r.Warn("resources", "could not parse node metrics response", err.Error())
		return
	}

	if len(metrics.Items) == 0 {
		r.Warn("resources", "no node metrics available", "")
		return
	}

	r.Pass("resources", fmt.Sprintf("%d node(s) reporting metrics", len(metrics.Items)))
}

// RunHealthChecks runs environment health checks when a cluster is running.
func RunHealthChecks(r *Result, selected *host.Session, allNamespaces bool) {
	if selected == nil {
		r.Warn("environment", "selected host unavailable", "no host/session contract resolved")
		return
	}
	if _, err := os.Stat(selected.KubeconfigPath); err != nil {
		if os.IsNotExist(err) {
			r.Pass("environment", "no cluster running (health checks skipped)")
			return
		}
		r.Warn("environment", "could not access selected kubeconfig", err.Error())
		return
	}

	reachabilityArgs := []string{"get", "namespace", selected.Namespace, "-o", "name"}
	if allNamespaces {
		reachabilityArgs = []string{"get", "namespaces", "-o", "name"}
	}
	if _, err := runKubectlWithTimeout(selected, reachabilityArgs...); err != nil {
		r.Warn("environment", "selected host is not reachable via kubectl", err.Error())
		return
	}
	r.Pass("environment", fmt.Sprintf("selected host reachable (%s)", selected.Mode))
	CheckPodStatus(r, selected, allNamespaces)
	CheckReadiness(r, selected, allNamespaces)
	CheckResources(r, selected)
}

func podQueryArgs(selected *host.Session, allNamespaces bool) ([]string, string) {
	if allNamespaces {
		return []string{"get", "pods", "--all-namespaces", "-o", "json"}, "all namespaces"
	}
	namespace := selected.Namespace
	if namespace == "" {
		namespace = "default"
	}
	return []string{"get", "pods", "-n", namespace, "-o", "json"}, "namespace " + namespace
}

func runKubectlWithTimeout(selected *host.Session, args ...string) ([]byte, error) {
	ctx, cancel := context.WithTimeout(context.Background(), doctorTimeout(selected))
	defer cancel()
	return host.RunKubectl(ctx, selected, nil, args...)
}
