package cluster

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

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

// K3dClient abstracts k3d operations for testing and host adapters.
type K3dClient interface {
	ClusterHealthy(ctx context.Context, name string) (bool, error)
	CreateCluster(ctx context.Context, name string, agents int, memory string, volumes, ports []string) error
	CreateClusterWithImage(ctx context.Context, name string, agents int, memory, k3sImage string, volumes, ports []string) error
	StartCluster(ctx context.Context, name string) error
	StopCluster(ctx context.Context, name string) error
	DeleteCluster(ctx context.Context, name string) error
	ClusterExists(ctx context.Context, name string) (bool, error)
	ImportImage(ctx context.Context, cluster, image string) error
	ExportKubeconfig(ctx context.Context, name, outputPath string) error
}

// ExecK3dClient implements K3dClient by shelling out to the k3d binary.
type ExecK3dClient struct{}

// NewExecK3dClient returns a K3dClient that uses the k3d CLI.
func NewExecK3dClient() K3dClient {
	return &ExecK3dClient{}
}

// K3dAvailable returns true if the k3d binary is on PATH.
func K3dAvailable() bool {
	_, err := exec.LookPath("k3d")
	return err == nil
}

// EnsureK3d checks if k3d is installed and installs it if not.
// If version is empty, installs the latest version.
func EnsureK3d(ctx context.Context, version string) error {
	if K3dAvailable() {
		if version == "" {
			return nil
		}
		installed, err := k3dVersion(ctx)
		if err != nil {
			return fmt.Errorf("check installed k3d version: %w", err)
		}
		if strings.Contains(installed, version) {
			return nil
		}
		// Version mismatch — reinstall
	}

	return installK3d(ctx, version)
}

func k3dVersion(ctx context.Context) (string, error) {
	cmd := exec.CommandContext(ctx, "k3d", "version")
	output, err := verbose.RunCommand(cmd)
	if err != nil {
		return "", err
	}
	return string(output), nil
}

func installK3d(ctx context.Context, version string) error {
	if runtime.GOOS == "windows" {
		return fmt.Errorf("automatic k3d install not supported on Windows — install manually: https://k3d.io")
	}

	cmd := exec.CommandContext(ctx, "curl", "-fsSL", "https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh")
	curlOut, err := verbose.RunCommand(cmd)
	if err != nil {
		return fmt.Errorf("download k3d install script: %w\n%s", err, string(curlOut))
	}

	install := exec.CommandContext(ctx, "bash")
	if version != "" {
		tag := "v" + strings.TrimPrefix(version, "v")
		install.Env = append(install.Environ(), "TAG="+tag)
	}
	install.Stdin = strings.NewReader(string(curlOut))
	output, err := verbose.RunCommand(install)
	if err != nil {
		return fmt.Errorf("install k3d: %w\n%s", err, string(output))
	}

	if !K3dAvailable() {
		return fmt.Errorf("k3d installed but not found on PATH")
	}
	return nil
}

// ClusterHealthy checks if a k3d cluster exists and has running servers.
// Uses `k3d cluster list` which shows SERVERS as "N/N" (running/total).
func (c *ExecK3dClient) ClusterHealthy(ctx context.Context, name string) (bool, error) {
	cmd := exec.CommandContext(ctx, "k3d", "cluster", "list", "--no-headers")
	output, err := verbose.RunCommand(cmd)
	if err != nil {
		return false, fmt.Errorf("k3d cluster list: %w\n%s", err, string(output))
	}

	for line := range strings.SplitSeq(string(output), "\n") {
		fields := strings.Fields(line)
		// Format: NAME  SERVERS  AGENTS  LOADBALANCER
		if len(fields) >= 2 && fields[0] == name {
			// SERVERS field is "N/N" — healthy if running == total and total > 0
			// e.g. "1/1" means 1 running out of 1
			parts := strings.SplitN(fields[1], "/", 2)
			if len(parts) == 2 && parts[0] == parts[1] && parts[0] != "0" {
				return true, nil
			}
			return false, nil
		}
	}
	return false, nil
}

func (c *ExecK3dClient) CreateCluster(ctx context.Context, name string, agents int, memory string, volumes, ports []string) error {
	args := []string{
		"cluster", "create", name,
		"--k3s-arg", "--disable=traefik@server:0",
		"--runtime-ulimit", "nofile=1048576:1048576",
		"--wait",
	}
	if len(ports) == 0 {
		args = append(args, "--no-lb")
	}
	if agents > 0 {
		args = append(args, "--agents", fmt.Sprintf("%d", agents))
	}
	if memory != "" {
		args = append(args, "--servers-memory", memory)
	}
	for _, volume := range volumes {
		args = append(args, "--volume", volume)
	}
	for _, port := range ports {
		args = append(args, "--port", port)
	}

	cmd := exec.CommandContext(ctx, "k3d", args...)
	output, err := verbose.RunCommand(cmd)
	if err != nil {
		return fmt.Errorf("k3d cluster create: %w\n%s", err, string(output))
	}
	return nil
}

// CreateClusterWithImage creates a cluster using a specific k3s image.
func (c *ExecK3dClient) CreateClusterWithImage(ctx context.Context, name string, agents int, memory, k3sImage string, volumes, ports []string) error {
	args := []string{
		"cluster", "create", name,
		"--k3s-arg", "--disable=traefik@server:0",
		"--runtime-ulimit", "nofile=1048576:1048576",
		"--wait",
	}
	if len(ports) == 0 {
		args = append(args, "--no-lb")
	}
	if agents > 0 {
		args = append(args, "--agents", fmt.Sprintf("%d", agents))
	}
	if memory != "" {
		args = append(args, "--servers-memory", memory)
	}
	if k3sImage != "" {
		args = append(args, "--image", k3sImage)
	}
	for _, volume := range volumes {
		args = append(args, "--volume", volume)
	}
	for _, port := range ports {
		args = append(args, "--port", port)
	}

	cmd := exec.CommandContext(ctx, "k3d", args...)
	output, err := verbose.RunCommand(cmd)
	if err != nil {
		return fmt.Errorf("k3d cluster create: %w\n%s", err, string(output))
	}
	return nil
}

func (c *ExecK3dClient) StartCluster(ctx context.Context, name string) error {
	cmd := exec.CommandContext(ctx, "k3d", "cluster", "start", name)
	output, err := verbose.RunCommand(cmd)
	if err != nil {
		return fmt.Errorf("k3d cluster start: %w\n%s", err, string(output))
	}
	return nil
}

func (c *ExecK3dClient) StopCluster(ctx context.Context, name string) error {
	cmd := exec.CommandContext(ctx, "k3d", "cluster", "stop", name)
	output, err := verbose.RunCommand(cmd)
	if err != nil {
		return fmt.Errorf("k3d cluster stop: %w\n%s", err, string(output))
	}
	return nil
}

func (c *ExecK3dClient) DeleteCluster(ctx context.Context, name string) error {
	cmd := exec.CommandContext(ctx, "k3d", "cluster", "delete", name)
	output, err := verbose.RunCommand(cmd)
	if err != nil {
		return fmt.Errorf("k3d cluster delete: %w\n%s", err, string(output))
	}
	return nil
}

func (c *ExecK3dClient) ClusterExists(ctx context.Context, name string) (bool, error) {
	cmd := exec.CommandContext(ctx, "k3d", "cluster", "list", "--no-headers")
	output, err := verbose.RunCommand(cmd)
	if err != nil {
		return false, fmt.Errorf("k3d cluster list: %w\n%s", err, string(output))
	}

	for line := range strings.SplitSeq(string(output), "\n") {
		fields := strings.Fields(line)
		if len(fields) > 0 && fields[0] == name {
			return true, nil
		}
	}
	return false, nil
}

func (c *ExecK3dClient) ImportImage(ctx context.Context, cluster, image string) error {
	cmd := exec.CommandContext(ctx, "k3d", "image", "import", image, "-c", cluster)
	output, err := verbose.RunCommand(cmd)
	if err != nil {
		return fmt.Errorf("k3d image import: %w\n%s", err, string(output))
	}
	return nil
}

// ExportKubeconfig writes the cluster kubeconfig to the given path.
func (c *ExecK3dClient) ExportKubeconfig(ctx context.Context, name, outputPath string) error {
	cmd := exec.CommandContext(ctx, "k3d", "kubeconfig", "get", name)
	output, err := verbose.RunCommand(cmd)
	if err != nil {
		return fmt.Errorf("k3d kubeconfig get: %w\n%s", err, string(output))
	}
	if err := os.MkdirAll(filepath.Dir(outputPath), 0o755); err != nil {
		return fmt.Errorf("create kubeconfig dir: %w", err)
	}
	if err := os.WriteFile(outputPath, output, 0o600); err != nil {
		return fmt.Errorf("write kubeconfig: %w", err)
	}
	return nil
}
